android核心技术与最佳实践笔记(一)
16lz
2021-01-26
第一章 android核心组件和应用框架 1.1 核心组件 android的四大核心组件:提供界面显示的activity,提供后台计算的service,提供进程间通信的intent和提供广播接收的broadcastReceiver。 1.1.1 activity组件 activity是实际与用户交互的组件。有几个子类需要注意: ListActivity, PreferenceActivity 和 TabActivity activity的类图如下: 分别介绍类的用途和实现原理: 1. ListActivity 可以用来实现列表功能。ListActivity提供了对基本的单行,双行列表的封装,同时还支持用户自定义列表。自定义列表是基于ListView来实现的。实现一个列表包括3步:选择或自定义列表项布局文件,是吸纳适配器并加载数据,为ListActivity设置适配器。 列表布局文件决定列表的布局风格,最基本的布局控件只有RelativeLayout和LinearLayout两个。 适配器的选择,android支持两种基本的适配器:基本适配器(BaseAdapter)和游标适配器(CursorAdapter)。基本适配器是最通用的适配器,游标适配器用来适配数据库的数据流的,其他的系统级适配器都是继承这两个适配器。 (1) 系统列表项布局文件 android目前实现的基本列表项布局文件,分别为:基于单行布局的simple_list_item_1布局文件, 基于简单双行布局的simple_list_item_2布局文件,基于单行单选布局的simple_list_item_single_choice布局文件,基于单行多选布局的simple_lsit_item_multiple_choice布局文件,类似树状图的simple_expandable_list_item_1和 simple_expandable_list_item_2布局文件等。 例如: 基于simple_list_item_1布局文件的一个实现: setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, mStrings));//mStrings为数组类型的数组,simple_list_item_1为列表布局风格的文件 下面介绍ArrayAdapter为例介绍适配器加载布局文件的方法: 上述ArrayAdapter加载布局文件的过程,可以看出,默认情况下,ArrayAdapter假定整个布局文件是一个TextView,只有指定了mFieldId,及通过如下方法初始化ArrayAdapter时,ArrayAdapter才认为加载了一个自定义布局。 ArrayAdapter的实现由很强的局限性,仅能显示单行的列表。下面介绍实现双行的列表的方法。 SimpleCursorAdapter的初始化过程如下: SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_item_2, c, new String[]{ Phone.TYPE, Phone.NUMBER }, new int[]{ android.R.id.text1, android.R.id.text2 }); SimpleCursorAdapter的ViewBinder的实现如下: 在ViewBinder的实现过程中,指定了布局文件,游标,数据项,控件ID等,但没有处理布局加载和数据绑定。下面是SimpleCursorAdapter的bindView()的实现,该方法揭示了布局加载和数据绑定的过程。 从bindView()的实现中可以看出,SimpleCursorAdapter的ViewBinder实际上是用户的一个自定义实现接口,当用户没有进行自定义实现时,SimpleCursorAdapter会通过传递的控件数量进行默认数据绑定。SimpleCursorAdapter还支持图片加载,不过此时游标获取的不再是文本数据,而是图片的URI. (2)系统适配器 android中,提供的适配器:BaseAdapter, CursorAdapter, ResourceCursorAdapter, SimpleCursorAdapter, ArrayAdapter 和 SimpleAdapter等。基本适配器类图如下: BaseAdapter适配器是最基本的适配器,是其他适配器的基类。BaseAdapter适配器可以适配ListView和Spinner等控件,通常会调用BaseAdapter的子类或通过BaseAdapter自定义适配器来实现视图与数据的适配功能。 在BaseAdapter适配器中,应用了观察者模式,当数据源发生变化时,可以通过显示控件自行刷新。 (3)自定义适配器 a . 基于BaseAdapter的自定义适配器 对于 基于BaseAdapter的自定义适配器,需要注意的是getView()方法的实现, getView()主要工作是列表项布局文件的加载和数据的绑定。 b. 基于CursorAdapter的自定义适配器 基于CursorAdapter的自定义适配器的实现重点在与bindView()方法和newView()方法。 其中bingView()方法用于绑定数据,newView()方法用于加载布局文件。实例如下: 考虑到加载列表项时多次操作findViewById()方法,对性能有所影响,因此在android中设计了ViewHolder,以其来进行优化。通过View的setTag()方法和getTag()方法可大幅度提高显示速率。 (4)复杂场景处理 在ListView中一些特殊的场景需要注意,如快速滚动时的显示问题,列表项中可单击空间的处理等。 2. PreferenceActivity 最要用于偏好设置,在布局上PreferenceActivity以PreferenceScreen为根布局,支持CheckBoxPreference等多种形式的偏好设置。这些偏好值默认存储于应用的SharedPreferences中,通过getSharedPreferences()可以获取SharedPreferences对象,通过Preference.OnPreferenceChangeListener监听器可以监听到偏好值的变化。 下面介绍几种偏好值的实现。 (1)CheckBoxPreference 不但提供二选一偏好的方法,还支持偏好的说明。下面是CheckBoxPreference布局文件的一个示例: (2)DialogPreference 目前仅仅是一个接口, (3)EditTextPreference 提供了支持输入框的偏好设置功能。通过getEditText()方法获得输入框的内容,通过getText()可获得SharedPreferences中存储的偏好值,通过setText()方法可以将偏好值保存在SharedPreferences中,具体如下: (4)ListPreference 当某个偏好有多个偏好值可选时,ListPreference就派上了用场,在使用ListPreference时需要注意entries和entryValues属性,其中entries表示界面的内容,而entryValues对应的是实际偏好值。 在ListPreference中,用户只能做单选操作,如果期望能够执行多选操作,则需要用到MultiSelectListPreference 。 MultiSelectListPreference的资源文件配置和ListPreference类似。 (4)RingtonePreference 是一个用于设置铃声的特殊偏好控件,目前Android提供的铃声类型包括ringtone, notification, alarm 和all等。其中all表示所有可用的铃声。下面是Ring头呢Preference的配置示例: (5)PreferenceCategory 其提供了偏好组的功能。下面是一个偏好配置示例: 3. TabActivity TabActivity的根布局控件为TabHost,它由TabWidget和通常基于FrameLayout的内容显示区域组成。示例如下: (1)自定义Tab 4. activity的加载模式 在android中,有4中加载模式:standard, singleTop , singleTask 和 singleInstance standard加载模式为android默认加载模式,在加载是会创建一个新的activity的实例,类似调用startActivity()方法时设置Intent的标志位为intent.FLAG_ACTIVITY_NEW_TASK。 发起standard加载模式的实例如下: Intent i = new Intent(); i.setFlags(I ntent.FLAG_ACTIVITY_NEW_TASK); startActivity(); singleTop 加载模式表示当前Activity的实例处于前台并可视时,该实例会收到发送过来的Intent消息,器接收方法如下: protected void onNewIntent(Intent intent){ super.onNewIntent(intent); ........ } singleTask加载模式表示当前Acitivity栈(Task)中当前Activity实例运行时,该实例会收到相应的Intent消息,接收方法类似于singleTop加载模式。发起singleTask加载模式的示例如下: Intent i = new Intent(); i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); startActivity(i); singleInstance加载模式表示该Acitivity以单子模式加载时,在当前Activity栈中唯一,接收方法类似于singleTop加载模式。 为保证Activity在系统运行时能正常加载,必须在AndroidManifest.xml中声明所有需要加载的Activity,方法如下: android:label="@string/app_name" android:launchMode="singleInstance" //忽略重力传感器和键盘隐藏等变化 android:configChanges="orientation|keyboardHidden" > 注意:通常情况下,当单个应用中包含的文件较多时,通常需创建子文件夹进行区分,如将实现Activity的文件放在ui文件夹下,将实现Service, Provider的文件放在service文件夹下,将实现的一些自定义视图放在view文件夹下,将实现一些工具文件放置在util文件夹下。这样声明的方法略有不同,具体如下: > ........ //注意ui前面的”.“表示当前目录 5. activity的属性配置 (1)配置变化属性 在android中存在一些基本的配置,如android:mcc, android:mnc, android:locale, android:touchscreen, android:keyboard, android:keyboardHidden, android:navigation, android:orientatiion, android:screenLayout, android:fontScale和android:uiMode等,用户可对这些属性进行配置,若实现与配置相关,则需监听这些属性的变化。 android:mcc 属性表示MCC(Mobile Country Code),即 SIM 卡中存储的 IMSI号中的国家代码布恩,当发生国际漫游时,该属性配置会发生变化。 android:mnc 属性表示MNC (Mobile Network Code), 即 SIM 卡中存储的 IMSI 号中的网络代码部分,当发生不同运营商网络间漫游时,该配置会发生变化,如在中国移动和中国联通的GSM网络间漫游。当然,运营商可以限制此类漫游。 android:locale 属性表示当前显示语言发生变化 android:touchscreen 属性表示触摸屏发生了变化,考虑到当前的实际情况,只有在支持多屏显示时,才需使用该属性。 android:keyboard 属性表示键盘类型发生了变化,如外接了蓝牙键盘等。 android:keyboardHidden 属性表示显示或隐藏键盘,对翻盖或滑盖终端有效。 android:navigation 属性表示导航键发生了变化,如从轨迹球变化为五向键,考虑到当前的实际情况,该配置显然不会发生变化。 android:orientation 属性表示屏幕方向 android:screenLayout 属性表示屏幕布局发生变化。在不支持多屏显示的情况下,该配置不会发生变化。 android:fontScale 属性表示字体发生了变化 android:uiMode 属性表示UI模式发生了变化,如变为车载模式,夜间模式等 默认情况下,当配置发生变化时,当前的Activity会在被销毁后重新生成,通过在AndroidManifest.xml 中为相应的Acitivity针对特定配置声明该属性,则可阻止特定配置变化时Activity被销毁后重新生成。当然,在某种情况下,开发者仍然需要监听到配置的变化,则可在Activity中实现如下方法: public void onConfigurationChanged(Configuration newConfig) android:configChanges 属性的设置方法: android:configChanges="orientation|keyboardHidden" (2)屏幕旋转属性 android:screenOrientatiion属性,在有重力传感器的情况下,必须考虑屏幕的适配情况,其属性值包括:unspecified, landscape, portrait, user, behind , sensor, nosensor等,其中 unspecified为默认值,旋转策略由系统决定; landscape表示横屏; portrait表示竖屏; user表示当前用户倾向的屏幕方向;behind表示屏幕方向和Activity栈中当前activity下面的activity相同; sensor 表示根据重力传感器确定屏幕方向; (3)主题属性 android:theme 属性表示当前activity的主题,通常用于设置标题栏,状态栏等,设置方法: android:theme="@android:style/Theme.NoTitleBar" (4)启动约束属性 android:exported属性表示启动约束,即是否允许被其他进程调用,如果值为false,则该Activity仅可被同一应用中的组件或拥有相应用户ID的应用的组件调用;若为true, 则可被其他进程调用。 android:exported属性的默认值与携带的Intent过滤器有关,如果没有携带任何Intent 过滤器,其值为false
1.1.2 Service组件 Service的类图如下: 将Service纳入编译系统,需要在AndroidManifest.xml中对Service进行显式声明,方法如下: 1. InputMethodService InputMethodService提供了一个输入法的标准实现,普通开发者不需要关心。一种输入法在界面上有3部分构成,及软输入视图(soft input view), 候选视图,和全屏模式。 2. IntentService IntentService 作为Service的子类,主要用于处理异步请求,防止服务阻塞。所有的请求将在一个工作进程中处理,工作结束后,线程也结束。在gallery3d应用中,CacheService即是IntentService的一个示例。 3. MediaScannerService MediaScannerService 主要在设备启动和SD卡挂载时执行多媒体文件的扫描工作。处于性能方面的考虑, android区分SD卡和手机内存空间。对于SD卡,MediaScannerService会收到 Action为ACTION_MEDIA_MOUNTED的Intent时进行扫描; 对于手机存储空间,MedaiScannerReceiver会在收到Action为ACTION_BOOT_COMPLETED的Intent(设备启动完毕)时进行扫描。另外,在下载文件时,也可能启动媒体扫描服务。 媒体扫描服务相关的类如下如: 目前MediaScannerService扫描的多媒体格式定义在MediaFile.java 文件中。当系统开始扫描时,媒体扫描服务会广播一个Action为ACTION_MEDIA_SCANNER_STARTED的Intent, 然后创建一个MediaScanner执行扫描;当扫描结束后,广播一个Action为ACTION_MEDIA_SCANNER_FINISHED的Intent。 在一个特殊的场合,如录音应用和下载场景中,如果希望产生的多媒体文件接口被加入到数据库中,可以直接调用媒体扫描服务进行单个文件的扫描,但对于删除的文件,无法通过如下方式进行同步: sendBroadcase(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); 4. RecognitionService RecognitionService是一个抽象服务,仅在开发者希望实现一个新的语音识别器时,才可能用到。为了实现一个新的语音识别器,必须实现如下抽象方法: protected abstract void onStartListening( Intent recognizerIntent, Callback listener ) protected abstract void onCancel( Callback listener ) protected abstract void onStopListening( Callback listener ) 5. 绑定服务和启动服务 服务的运行有两种发起方式,即绑定服务和启动服务。当通过绑定服务的方式运行服务时,一旦绑定解除,服务即被销毁,当进行多次绑定时,只有所有绑定均解除,服务才会销毁; 当以启动服务的方式运行服务时,服务并不会随着绑定组件的销毁而销毁,而是服务自我销毁,这种方式适用于文件下载,文件上传等请求后自行运行的场景。 (1)绑定服务 为了绑定一个服务,需要设置 ServiceConnection 和标志位 ,方法如下: public abstract boolean bindService(Intent service, ServiceConnection conn, int flags) ServiceConnection可以监控服务的状态,在进行服务绑定时,其标志位可以为BIND_AUTO_CREATE, BING_DEBUG_UNBIND和 BING_NOT_FOREGROUND等。其中BIND_AUTO_CREATE表示当收到绑定请求时,如果服务尚未创建,则即可创建,在系统内存不足,需要先销毁优先级组件来释放内存,且只有驻留该服务的进程成为被销毁对象时,服务才可被销毁;BING_DEBUG_UNBIND通常用于调试场景中判断绑定的服务是否正确,但其会引起内存泄露,因此非调试目的不建议使用; BING_NOT_FOREGROUND表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位在Froyo中引入。 绑定服务的示例: Intent intent = new Intent(); intent.setClassName("com.android.providers.media", "com.android.providers.media.MediaScannerService"); bindService(intent, mMediaScannerConnection, BIND_AUTO_CREATE); 注意绑定服务是以异步的方式运行的。绑定服务必须在当前的上下文环境中进行,在某些场景中,如果无法绑定成功,则可能需要在应用级的上下文环境进行,方法如下: getApplicationContext().bingService(......); 如果解除绑定,方法如下: public abstract void unbindService( ServiceConnection conn ) (2)启动服务 启动服务: public abstract ComponentName startService(Intent service) 自我停止服务: public final void stopself() 被动停止服务: public abstract boolean stopService (Intent service) 6. 服务的声明周期 两种运行方式下的服务的声明周期: 当外部组件调用其上下文的startService()方法时,即可启动相应的服务。在服务的 onStartCommand()方法中,会返回一个唯一的整数标示符启动请求。启动服务的实例如下: Intent intent = new Intent( this , ExperimentService.class ); intent.putExtra(EXTRA_EXP_ID, which); intent.putExtra(EXTRA_RUN_ALL, all); startService(intent) ; Intent 传递过来的参数可以在onStartCommand()方法中进行处理,示例如下: public int onStartCommand(Intent intent, ing flags , int startId) { if(intent != null){ Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent.getExtras(); mServideHandler.sendMessage(msg); } return START_REDELIVER_INTENT; } 停止服务如下: stopService(intent) ; 当外部组件调用其上下文的bingService()方法时,也可绑定相应的服务。如果服务未启动,则调用onCreate()方法启动服务, 当不会调用onStartCommand()方法,只有在所有绑定均解除后,服务才自动停止运行。通过服务的onBind()方法,可以获得一个客户端与服务进行同行的IBinder接口。注意,绑定服务的android组件在销毁前应解除绑定,否则会造成内存泄露。绑定和解除绑定的实例如下: mContext.bindService( new Intent( IBluetoothHeadset.class.getName()), mConnection, 0 ); mContext.unbindService( mConnection ); 1.1.3 Intent 组件 intent用于进程内或进程间通信的机制,其底层的通信以Binder机制实现,在物理层上则通过共享内存的方式来实现。 主要用于广播和发起意图两中场景,属性有ComponentName, action , data, category, extras, flags等。通常情况下,在进行Intent 的匹配时,需要匹配Action, Data, Category 等3个属性。 从ComponentName 属性的明确性可以划分为,显式的intent和隐式的intent,所谓的显式的intent,即明确了目的地,不需要系统进行intent匹配的Intent。在应用内部进行组件调用时,应首选显示Intent., 举例如下; intent i = new Intent( context, AccountFolderList.class ) ; i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); context.startActivity(i); 所谓的隐式Intent, 即没有明确指明目的地,需要系统根据自己的信息进行匹配的Intent. 这类Intent同样用于应用间的相互小勇,有助于降低应用间的耦合性。举例如下: Intent intent = new Intent(Intent.ACTION_PICK); intent.setDadaAndType(Uri.EMPTY, "vnd.andorid.cursor.dir/track"); intent.putExtra("album", Long.valueOf(id).toString()); intent.putExtra("artist", mArtistId); startActivity(intent); 1 。 属性说明: (1)ComponentName 为处理Intent消息的android组件,可以是activity,服务等。通常使用方法: public Intent serClassName( Context packageContext, String className ) 其中,className可以为空,系统会更具intent携带的其他信息来定位相应的组件。 (2)action 表示Intent的类型,可以是查看,删除,发布或其他,最常用的是android.intent.action.MAIN。 android.intent.action.MAIN表示一个应用的入口,通常和 android.intent.category.LAUNCHER联合使用。二者同时使用表示应用程序的启动界面。 (3)data 表示Intent携带的数据,通常和MIME类型联合使用,表示应用可以打开的数据类型,用法如下: (4)category 表示intent的策略,目前最常用的如下: android.intent.category.DEFAULT ...LAUNCHER ...MONKEY ...OPENABLE ...BROWSABLE 对于隐式的intent, 如果创建是没有指定category属性,则系统会默认设置器属性为android.intent.category.DEFAULT, 这是如果在intent过滤器中没有指定category属性为 android.intent.category.DEFAULT,则会造成匹配失败。 (5)Extras 表示intent的附加信息,它在组件间传递信息时非常有用。目前Extras可以支持多种数据类型,如布尔,整型,字符串等,实例如下: Intent i = new Intent(this , AlarmAlertFullScreen.class); i.putExtra(Alarms.ALARM_INTENT_EXTRA, mAlarm); i.putExtra(SCREEN_OFF, true); startActivity(i); (6)Flags 表示Intent的标志位。Flags和Activity 的启动模式有密切的关系。 2 。 特殊场景 下面介绍一些Intent应用的特殊的场景,如开机自启动,网络监听,获取内容,SD卡挂载等。 (1) 开机自启动 接收开机自启动事件的Intent过滤器的设置方法如下: (2)网络监听 对网络进行监听的设置如下: 在接收到相关的信息后,可以从中获取网络的状态,方法如下; if(action.equals(ConnectivityManager.CONNECTIVITY_ACTION)){ NetworkInfo info = (NetworkInfo); intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); if(info != null && info.isConnected){ ... } } (3)获取内容 为了响应获取内容的事件,必须在intent过滤器中设置action属性为android.intent.action.GET_CONTENT, 设置category属性为android.intent.category.DEFAULT, 方法如下: (4)SD卡挂载 与SD卡挂载相关的Intent的action 属性包括: ACTION_MEDIA_REMOVED被移除, ACTION_MEDIA_UNMOUNTED卸载但不移除, ACTION_MEDIA_CHECKING, ACTION_MEDIA_NOFS表示SD卡存在,但文件系统不兼容或者尚未格式化, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED进入USB连接模式, ACTION_MEDIA_UNSHARED退出USB模式, ACTION_MEDIA_BAD_REMOVAL表示SD卡已被移除,但挂载点仍存在,可能发生了某种错误, ACTION_MEDIA_UNMOUNTABLE表示SD卡存在,但是无法挂载, ACTION_MEDIA_EJECT表示用于欲卸载SD卡,但SD卡上的部分内容尚处于打开状态 等属性值。 (5)返回桌面 为了返回桌面,需要设置category属性为Intent.CATEGORY_HOME, 方法如下: Intent i = new Intent(Intent.ACTION_MAIN); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.addCategory(Intent.GATEGORY_HOME); startActivity(i); 3. PendingIntent 的逻辑 与intent不同,其可设定执行次数,主要用于远程服务通信,闹钟,通知,短信,启动器中,一般的应用则很少采用,PendingIntent常用的方法如下: //启动Activity, 类似于startActivity(Intent); public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags) //启动Broadcast,类似于sendBroadcast(intent) public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags) //启动service,类似于 startService public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags) PendingIntent的标志位有FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT。 其中 FLAG_ONE_SHOT表示返回的PendingIntent仅能执行一次,执行完成后即自动取消; FLAG_NO_CREATE表示如果描述的PendingIntent不存在,并不创建相应的PendingIntent,而是返回NULL; FLAG_CANCEL_CURRENT表示如果相应的PendingIntent已经存在,则取消前者,然后创建新的PendingIntent, 这有利于保持数据为最新的,可用于即时通信的通知场景; FLAG_UPDATE_CURRENT表示更新PendingIntent。 手动取消PendingIntent的方法如下: public void cancel() 手动发送PendingIntent的方法 public void send() public void send(int code ) 另外, PendingIntent具有类似于Intent的fillIn方法,还支持为intent 追加数据,方法如下: public void send(Context context, int code , Intent intent) 当需要在发送完成时进行后继处理时,需要为PendingIntent 设置回调相关的接口和处理句柄,具体方法如下: public void send(int code , OnFinished onFinished, Handler handler) public void send(Context context, int code , Intent intent, OnFinished onFinished, Handler handler) Handler表示回调所处的线程,如果设置为NULL, 则表示回调将由当前进程的线程池执行。下面是OnFinished接口的一个实现: mFinish = new PendingIntent.OnFinished(){ public void onSendFinished(PendingIntent pi, Intent intent, int resultCode, String resultData, Bundle resultExtras){ mFinishResult = true; if(intent != null) { mResultAction = intent.getAction(); } } } 4. 返回结果 在通常情况下,Intent通信仅是单向的,到哪对于特殊的场景,比如希望拍照后返回响应的文件路径这种情况,android同样支持,方法如下: public void startActivityForResult( Intent intent, int requestCode ) 其中,requestCode的值用来表示当前的计算,被调用的Activity将成为调用方的子Activity,但注意,如果reguestCode的值小于零,则调用方的Activity无法收到计算结果,等同于调用了startActivity()。另外,能否获得计算结果与携带的Intent有关,当Intent的action 为ACTION_MAIN和ACTION_VIEW等时,调用方会收到RESULT_CANCELED而替代计算结果。举例如下: public final static int NEW_PLAYLIST = 4; Intent intent = new Intent(); intent.setClass(this, CreatePlaylist.class); startActivityForResult(intent, NEW_PLAYLIST); 被调用的Activity在完成计算后,将计算结果通过setResult()方法返回调用方的Activity即可。 public final void setResult (int resultCode) 其中,resultCode的值可以为RESULT_CANCELED, RESULT_OK, RESULT_FIRST_USER, 实例如下: setResult(RESULT_OK, (new Intent()).serData(uri));//必须随后调用finish()方法,释放当前Activity,这样调用方的activity才能收到返回结果。当计算完成后,在调用方的onActivityResult()中可以收到计算结果。具体方法如下: protected void onActivityResult(int requestCode , int resultCode, Intent data) 处理返回结果如下; switch(requestCode){ case NEW_PLAYLIST: if(resultCode == RESULT_OK) Uri uri = intent.getData(); break; } 1,1,4 BroadcastReceiver 组件 广播的本质是基于Intent记性的,广播的接收实现如下: public class AlertReceiver extends BroadcastReceiver { public void onReceive(Context context , Intent intent){} } 通常在BroadcastReceiver中并不会执行复杂的计算,后台计算一般在服务中执行。将BroadcastReceiver纳入编译系统的示例如下: 若BroadcastReceiver作为组件的私有类,那么可以通过上下文环境的如下方法实现BroadcastReceiver的注册和解除: public abstract Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) public abstract Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) public abstract void unregisterReceiver(BroadcastReceiver receiver)
1.2 应用框架解析 android设计框架来进行相关的管理,主要有Service框架,Activity管理机制,Broadcast机制,对话框框架,标题栏框架, 状态栏框架,通知机制和ActionBar框架等。 1.2.1 Service 框架 服务作为执行应用后台运算和框架层运算的基本组件。根据通信的方式和应用场景,服务有不同的类型。从通信的方式来看,服务可分为本地服务和远程服务,其中远程服务根据通信方式又可分为基于AIDL的服务和基于Message的服务两种。远程服务是android中跨进程通信的主要形式之一。从应用的场景看,服务可分为应用服务和系统服务。 1. 本地服务 如果服务没有跨进程的需求,应将服务设计为本地服务,如下实现了一个本地服务的最基本的形式: public class HelloService extends Service { public IBinder onBind(Intent arg0) { return null; } } 在本地服务中,必须实现onBind()方法。如果不需要绑定服务(即调用bindService()方法),可以返回null。一个具有绑定服务的本地服务实现如下: public class LocalService extends Service{ public class LocalBinder extends Binder{ LocalService getService(){ return LocalService.this; } } public IBinder onBind(Intent intent){ return mBinder; } private final IBinder mBinder = new LocalBinder(); } 默认情况下,Service依然运行在主线程中,而非另开线程,若希望有大量运算量的后台计算,则在实现本地服务时,必须在Service中创建当独的线程来执行相应的运算。 2. 基于AIDL的远程服务 基于AIDL的远程服务,本质上沿袭了分布式计算的思想。android实现分布式计算,并能够跨语言调用。实现基于AIDL的远程服务需要4个步骤: (1)创建AIDL文件。 (2)将AIDL文件纳入编译系统 (3)实现接口方法 (4)绑定服务客户端 完成编码工作后,android会在编译过程中,自动为相应的AIDL文件生成对应的桩(Stub),简化开发的难度。 分别介绍实现基于AIDL的远程服务的4个步骤: (1)创建AIDL文件 实现简单,本身是一个以”I“开头的接口文件。下面是一个实现实例:” interface ITestService{ boolean getSthEnabled(); void setSthEnabled(boolean on); } (2)将AIDL文件纳入编译系统 为了生成响应的桩,必须将AIDL文件纳入编译系统,其在frameworks/base/Android.mk中实现: LOCAL_SRC_FILES += \ core/java/android/os/ITestService.aidl \ 在应用层,以IMediaPlaybackService为例,其在Android.mk中实现通常如下: LOCAL_SRC_FILE := $(call all-java-files-under, src) \ src/com/android/music/IMediaPlaybackService.adil 在框架层和应用层将AIDL文件纳入编译系统,两者写法不同。 (3)实现接口方法 只需要继承相应接口的Stub子类即可,但必须实现接口所定义的所有方法,实例如下; public class ITestServiceImpl extends ITestService.stub{ public boolean getSthEnabled() { ............ } public void setSthEnabled(boolean on){ ............... } } 为了便于客户端绑定,通常会将桩封装到一个服务中,方法如下: public class TestService extends Service{ public IBinder onBind(Intent arg0){ return new ITestServiceImpl(getApplicationContext()); } } (4)绑定服务客户端 为了与服务进行通信,必须在客户端绑定远程服务,在应用层的实现中,如果是跨进程调用的,必须将相应的ITestService文件复制到客户端所在的进程中。假设服务位于com.miaozl.text包中,在客户端实现进程调用时,方法如下: private ITestService mTest = null; public void onCreate(){ Intent intent = new Intent(); intent.setComponent( new ComponentName("com.miaozl.test" , "com.miaozl.test.service.ITestService") ); bindService(intent, mTestConnection, Context.BIND_AUTO_CREATE); } 如果是在本地进程中,实现如下: bindService( new Intent(this, ITestService.class), mTestConnection, BIND_AUTO_CREATE ) 而mTestConnection的是实现则不区分是本地应用调用还是夸进程调用,具体如下: private ServiceConnection mTestConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName name, IBinder service){ mTest=ITestService.Stub.asInterface(service);//绑定方法 } public void onServiceDisconnected(ComponentName name){ mTest = null; } } 注意: 绑定服务是以异步的方式进行的,对于必须为同步的场景,是无法实现绑定服务的。 3. 基于Messenger的远程服务 基于Messenger的远程服务同样是跨进程的,其本质是将本地服务和Messager结合,以便实现进程间的通信。基于Messenger实现远程服务的实例如下: public class AlertService extends Service{ final Messenger mMessenger = new Messenger(new ServiceHandler()); private final class ServiceHandler extends Handler{ public ServiceHandler(Looper looper){ super(looper); } public void handleMessage(Message msg){ processMessage(msg); } } void processMessage(Message msg){ ......... } public IBindler onBind(Intent intent){ //服务必须是实现的方法 return mMessenger.getBinder();
} } 通过Messenger,服务的调用者可以方便地发送Message,实例如下: Messenger mService = null; private ServiceConnection mConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName className, IBinder service){ mService = new Messenger(service); Message msg = Message.obtain(null, MessengerService.MSG_SET_VALUE, this.hashCode(), 0); mService.send(msg); } } 4. 系统服务 系统服务主要由3部分构成: *Service.java , I*.aidl , *Manager.java 。另外还需要在SystemServer.java增加框架层封装,在ContextImpl.java增加应用层接口。 为了实现系统服务,需要实现5部分的内容: 接口文件,客户端文件,桩文件(系统自动实现),服务端文件,系统调用接口。 (1)接口文件, 通常,接口文件仅用到了基本的数据类型,如果需要用到复杂的数据类型,则需要对数据进行序列化。如下为IAlarmManager.aidl文件的具体实现: interface IAlarmManager{ void set(int type, long triggerAtTime, in PendingIntent operation); void setRepeating(ing type, long triggerAtTime, long interval, in PendingIntent operation); void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); void setTime(long millis); void setTimeZone(String zone); void romove(in PendingIntent operation); } 在AIDL文件中,通常定义了客户端需要调用的接口中的方法。 (2)客户端文件 在android系统中,客户端文件对系统服务的额调用非常简单,其调用方法类似于普通方法调用,只是需要捕获RemoteException异常。下面是AlarmManager的部分实现: public class AlarmManager { private final IAlarmManager mService; AlarmManager(IAlarmManager service){ mService = service; } public void set(int type, long triggerAtTime, PendingIntent operation){ try{ mService.set(type, triggerAtTime, operation); }catch (RemoteException ex){} } } (3)服务端文件 服务端的实现是实现系统服务的最重要的工作,下面是AlarmManagerService的部分实现: class AlarmManagerService extends IAlarmManager.Stub{ public void set(int type, long triggerAtTime, PendingIntent operation){ setRepeating(type, triggerAtTime, 0 , operation); } } (4)系统调用接口 为了方便应用层进行调用,需要在ContextImpl.java中实现统一的接口封装,并在Context.java中定义如下接口: public static final String ALARM_SERVICE = "alarm"; 在ContextImpl.java中实现统一接口封装的代码如下: class ContextImpl extends Context{ private static AlarmManager sAlarmManager; public Object getSystemService(String name){ if(ALARM_SERVICE = "service”){ return getAlarmManager(); } }
private AlarmManager getAlarmManager(){ synchronized(sSync){ if(sAlarmManager == null){ IBinder b = ServiceManager.getService(ALARM_SERVICE); IAlarmManager service = IAlarmManager.Stub.asInterface(b);// ? sAlarmManager = new AlarmManager(service); } } return sAlarmManager; } } 完成以上工作后,如果需要闹钟服务,可按照下列方式执行调用: AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 注意:框架层的变动会导致SDK的变动,所以需要更具设计者的需求选择@hide或make update-api来更新current.xml 5. 服务配置 为了对服务进行配置,必须在AndroidManifest.xml中对服务进行配置,其方法如下: 对于远程服务,若希望被其他进程调用,则必须开放相应的权限,开放权限的方法如下: android:exported;"true" /> 如果服务具有一些敏感的信息,需要对其进行权限配置,这在对安全要求较高的应用中十分重要。权限配置方法如下: 如果希望服务运行在单独的进程中,则可以应用如下的方法进行配置:
1.2.2 Activity管理机制 android的管理是通过Activity栈和Task来进行的。 1. Activity栈 android的管理主要通过android栈来进行的。当一个activity启动时,系统会根据配置或调用方式,将activity压入一个特定的栈中,系统处于运行状态,当按下Back键或触发finish()方法时,activity会从栈中被压出,进而被销毁,到那个有新的activity压入栈时,如果原activity仍可见,则原activity的状态变为暂停状态,如果activity完全被遮挡,则其状态变为停止。 2.Task Task与activity栈有密切的关系。一个Task对应一个activity栈,Task是根据用户体验组成的运行期逻辑单元,其与应用的区别在于,Task中的activity可以由不同的应用组成。在实际的终端使用中,在主界面长按Home键弹出一个网格界面即是当前运行的Task而非应用。
Task的定义为与 framework/base/services/java/com/androd/server/am目录下的TaskRecord.java中,一个Task由tasked , affinity, clearOnBackground, intent , affinityIntent, origActiivty, realAcitvity, numActivities, lastActiveTime, rootWasReset, stringName等属性构成。 在activity中,有不少属性与Task 相关,如 android:allowTaskReparenting, android:taskAffinity等。 (1)Task间移动配置 android:allowTaskReparenting 属性用来配置是否允许activity从启动它的Task移动到和该Activity设置的TaskI亲和性相同的Task中, (2)Task状态设置 android:alwaysRetainTaskState 属性用于配置是否保留Activity所在的Task状态, 默认为false clearTaskOnLaunch 当Task从主界面重新启动时,是否需要清除除根activity外的的所有activity,默认为false finishOnTaskLaunch 当Task从主界面重新启动时,特定的activity是否需要被销毁,默认为false (3)Task亲和性 Task亲和性,由android:taskAffinity属性定义。如果希望activity启动时,运行在特定的Task中,必须显式设置这个属性。 注意:只有通过标志为FLAG_ACTIVITY_NEW_TASK的intent启动activity时,该activity的android:taskAffinity属性才有效,系统才会将就要有相同Task亲和性的Task 切换到前台,然后启动该activity,否则该activity忍让运行咋启动它的task中。
1.2.3 Broadcast机制 广播涉及顺序广播,无序广播,广播接收器等概念。 1. 顺序广播 当广播需要以类似消息链的方式进行时,应采用顺序广播,顺序广播的接收器可以抛弃或继续传递消息。具体事例如下: Intent LlcpLinkIntent = new Intent(); LlcpLinkIntent.setAction(NfcAdapter.ACTION_LLCP_LINK_STATE_CHANGED); LlcpLinkIntent.putExtra(NfcAdapter.EXTRA_LLCP_LINK_STATE_CHANGED, NfcAdapter.LLCP_LINK_STATE_ACTIVATED); mContext.sendOrderedBroadcast(LlcpLinkIntent, NFC_PERM); 2. 无序广播 无序广播是异步的,广播接收器除了接收广播外,无法对无序广播的行为产生影响。下面示例: Intent intent = new Intent(Intent.ACTION_ARIPLANE_MODE_CHANGED); intent.putExtra("state", enabling); mContext.sendBroadcast(intent); 3.广播接收器 为了接收广播,必须在AndroidManifest.xml中配置广播接收器或通过java实现广播接收器,配置方法: 代码中配置: IntentFilter bootFiler = new IntentFilter( Intent.ACTION_BOOT_COMPLETED ); mContext.registerReceiver( monitor, bootFiler );
1.2.4 对话框框架 在android中,目前有4中创建对话框的方式,分别为AlertDialog, ProgressDialog, DatePickerDialog, TimePickerDialog。 其中AlertDialog是最通用的对话框形式, ProgressDialog用于描述进度信息,后两者主要用于日期和时间的场景中。 1. AlertDialog 默认情况下,通过setMessage()来设置显示的文字信息,通过setView()加载视图。创建对话框通过AlertDialog.Builder进行。 创建对话框的一般步骤: >定义对话框的ID,备用; >在onCreateDialog()中创建对话框 >通过showDialog()显示对话框 >通过dismissDialog()隐藏对话框 在某场景下,由于正在执行无法中断的计算,弹出的对话框不希望被用户取消,在可使用下列方法设置: public void setCancelable(boolean flag) //使用于所有对话框 由于Dialog管理机制问题,Dialog具备记忆功能,这在需要数据更新的场景中,稍麻烦,解决方法是在 onPrepareDialog() 方法中进行数据更新。 注意: 由于无法在对话框队列中记忆对话框状态,dismissDialog()方法必须和showDialog()成对出现,若无showDialog()与之配对会发生异常。 2. ProgressDialog 进度对话框,常用于耗时的操作,应将Progress Dialog 放在主线程之外执行,否则极易出现androidANR消息。常见的形式如下: ProgressDialog mWaitDialog = new ProgressDialog(); mWaitDialog.setMessage(getString(R.string.waiting)) ; mWaitDialog.setIndeterminate(true); mWaitDialog.setCancelable(false); 完整的进度对话框还需要定义一个ID, showDialog(iD); dismissDialog(ID); 若开发着不希望用户通过Back键手动销毁对话框,可进行设置 mWaitDialog.setCancelable(true); 若希望能监听进度对话框取消的消息,可如下实现: mWaitDialog.setOnCancelListener( new OnCancelListener() ){ public void onCancel(DialogInterface dialog){ ....... } } 进度条支持两种风格的进度显示:一种是进度条,一种是环形转动。这两种进度显示对应的风格分贝为 ProgressDialog.STYLE_HORIZONTAL 和 ProgressDialog.STYLE_SPINNER, 默认的风格为后者,设置方法: mProgressDialog.setProgressStyle( ProgressDialog.STYLE_HORIZONTAL ); ProgressDialog支持的进度复读为 0 ~~~10000 其同时支持主进度和辅进度,其本质是是实现对ProgressBar的封装。 3. DatePickerDialog 日期对话框,创建时通常需要设置初始年,月,日和监控日期变化的回调函数。 DatePickerDialog(this, mDateSetListener, mYear, mMonth, mDay); 下面是日期对话框回调函数的实现: private DatePickerDialog.onDateSetListener mDateSerListener = new DatePickerDialog.OnDateSerListener(){ public void OnDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth){ mYear = year; mMonth = monthOfYear; mDay = dayOfMonth; updateDisplay(); } }; 代码中更新日期对话框的显示,方法: dialog.updateTime(mYear, mMonth, mDay); 4. TimePickerDialog 方法类似DatePickerDialog 1.2.5 标题栏框架 目前标题栏显示支持进度显示,允许用户隐藏,自定义标题栏。目前android标题栏支持FEATURE_NO_TITLE, FEATURE_PROGRESS, FEATURE_LEFT_ICON, FEATURE_RIGHT_ICON, FEATURE_INDETERMINATE_PROGRESS, 等多种定制。 1. 隐藏标题栏 两种实现方式,在AndroidManifest.xml中和代码中实现 xml中,在activity中添加 android:theme="@android:style/Theme.NoTitleBar" 代码中, requestWindwoFeature(android.view.Window.FEATURE_NO_TITLE); 2. 自定义标题栏 注意标题栏的FEATURE_CUSTOM_TITLE 不能和FEATURE_LEFT_ICON, FEATURE_NO_TITLE等同时使用,下面是设置方法: requestWindowFeature( Window.FEATURE_CUSTOM_TITLE ); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_1 ); 3. 进度显示 android中支持主进度和辅进度两种进度显示,辅进度在本地应用中较少使用。在网络环境中,如在进行流媒体播放时,可以用主进度表示播放进度,用辅进度标示下载进度。二者显示范围为 0~~100。 如下示例: requestWindowFeature(Window.FEATURE_PROGRESS); setProgressBarVisibility(true); setProgress(progressHorizontal.getProgress() * 100); setSecondaryProgress(progressHorizontal.getSecondaryProgress() * 100); 实际开发中,无法使用明确的进度显示,如向服务器发送请求后,等待反馈的过程,这时就用到了不定进程显示,下面是不定进程显示示例: requestWindowFeature( Window.FEATURE_INDETERMINATE_PROGRESS ); setProgressBarIndeterminateVisibility(true); 4. 图标显示 标题栏又标题,进度条,左标题,右标题构成。下面以左标题为例介绍: requestWindowFeature( Window.FEATURE_LEFT_ICON ); getWindow().setFeatureDrawableResource( Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark );
1.2.6 状态栏框架 与传统终端状态栏一样,android状态栏提供电量信息,蜂窝信息,SMS, MMS, 邮件,WIFI信号,蓝牙信号,闹钟等系统的状态信息。另外状态栏还有通知栏的功能。 其中,起主要作用的是 StatusBarPolicy, 它承担着接收系统发来的Intent的信息,更新状态显示的功能,他是服务StatusBarManagerService的客户端。 StatusBarManagerService在创建时,会加载config_statusBarIcons数组。在framework\base\core\res\res\values目录下,config.xml中定义了config_statusBarIcons数组确定了状态图标的加载顺序。 整个状态栏框架是通过StatusBarService来实现。在StatusBarService初始化时,初始化了一个用于显示statusbar的StatusBarView。 在StatusBarView中定义了状态栏的实现布局,而具体的布局问及那是在framework\base\packages\systemui\res\layout\status_bar.xml实现的。 1. 状态栏的隐藏 两种方式: AndroidManifest.xml中实现: android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" 代码中实现: getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ); requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题栏 上述隐藏方式值适合静态场景,在隐藏标题栏后再动态显示状态栏已经超出以上两种方法的能力。 此时可通过鞋面方法动态的隐藏和系那是状态栏: getWindow().addFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN ); //隐藏 getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN ); //显示 2. 电量显示 当StatusBarPolicy 收到Action为ACTION_BATTERY_CHANGED的Intent时,StatusBarPolicy会通知StatusBarManager进行电量图标的更新。同时还响应ACTION_BATTERY_LOW, ACTION_BATTERY_OKAY, ACTION_POWER_CONNECTED的intent。 3. 蜂窝信息 android对蜂窝协议的支持十分充分,目前支持GSM, UMTS, CDMA, 4G等。 4.WIFI信息 对于wifi信号,android可以响应Action为 WifiManager.NETWORK_STATE_CHANGED_ACTION, WifiManager.WIFI_STATE_CHAGED_ACTION, WifiManager.RSSI_CHANGED_ACTION的intent, 相应的更新方法: updateWifi() 5.蓝牙信息 对蓝牙信息,android可以响应BluetoothAdapter, BluetoothHeadset, BluetoothA2dp 和 BluetoothPbap的状态变化,相应的更新方法: updateBluetooth();
1.2.7 通知机制 多种方式向用户反馈系统状态信息,Toast提醒,通知栏提醒,对话框提醒的等。其中Toast常用于页面的显示,通知栏提醒常用于交互事件的通知,一般非常重要的通知以对话框的形式给出。 Toast 和Notification 均由框架层的 NotificationManagerService维护 1.Toast 简单提示用户信息,常只显示文本。也可以自定义。 自定义位置: toast.setGravity( Gravity.TOP|Gravity.LEFT, 0, 0 ); 自定义视图: LinearLayout dialog = (LinearLayout)LayoutInflater.from(this).inflate(R.layout.retry_sending_dialog, null); Toast undeliveredDialog = new Toast(this); undeliveredDialog.setView(dialog); undeliveredDialog.setDuration(Toast.LENGTH_LONG); undeliveredDialog.show(); 注意: Toast不能在AsyncTask的doInBackground()方法中运行,如果要实现类似的效果,可在Handler 中进行处理。 2. Notification 适合于交互事件的通知,常用于短消息,即时消息,下载,外围设备的状态变化的场景中。 Notification支持文字显示,振动,三色灯,振铃音等提示形式,默认情况下,仅显示消息标题,消息内容和时间。下面是一个基本实现: NotificationManager nm = (NotificationManager)getSystemService( NOTIFICATION_SERVICE ); CharSequence from = "Joe"; CharSequence message = "kthx.meet u for dinner. cul"; PendingIntent contentIntent = PendingIntent.getActivity(this, 0 , new Intent(this, IncomingMessageView.class), 0); String tickerText = getString(R.string.incoming_message_ticker_text, message); Notification notif = new Notification(R.drawable.stat_sample, tickerText, System.currentTimeMillis()); notif.setLatestEventInfo(this, from, message, contentIntent); notif.vibrate = new Long[]{100, 250, 100, 500}; nm.notify(R.string.incoming_message_ticker_text, notify); (1) Notification 管理 android通过标示符来管理Notification,发起一个Notification的方法如下; notificationManager.notify( notificationId, mNotification ); 取消Notification的方法有很多,如果希望用户单击后Notificatino即被清除,则相应的方法如下: notification.flags |= FLAG_AUTO_CANCEL; 如果希望手动清除某项,相应的方法: mNotificationMgr.cancel( LOW_MEMORY_NOTIFICATION_ID ); 若希望清除所有Notification时,相应的方法: mNotificationMgr.cancelAll();
(2)振动提醒 通常用于比较紧迫的场景,示例如下: Notification n = new Notification(); n.vibrate = new Long[]{ 0, 700, 500, 1000 };//振动方式:延迟0ms,然后振动700ms,接着振动1000ms mNM.notify( 1, n ); 若希望设置为默认的振动方式,相应的方法: notification.defaults |= Notification.DEFAULT_VIBRATE; (3)三色灯提醒 只有设置了Notification的标志位为FLAG_SHOW_LIGHTS,才能支持三色灯提醒。创建三色灯提醒的Notification示例如下: Notification n = new Notification(); n.flags |= Notification.FLAG_SHOW_LIGHTS; n.ledARGB = 0xff0000ff; n.ledOnMS = 300; n.ledOffMS = 300; mNM.notify( 1, n ); 若希望设置默认三色灯提醒,相应的方法: notification.defaluts |= Notification.DEFAULT_LIGHTS; (4)振铃声提醒 Notification支持默认铃声,自定义铃声,android多媒体数据库等多种提醒方式,相应的配置方法: notification.defaults |= Notification.DEFAULT_SOUND; //默认铃声 notification.sound = Uri.prase( "file:///sdcard/notification/ringer.mp3" ); //自定义铃声 notification.sound = Uri.withAppendedPath( Audio.Media.INTERNAL_CONTENT_URI, "6" ); //基于android多媒体数据库的提醒方式 (5)提醒标志位 Notification支持FLAG_SHOW_LIGHTS三色灯提醒, FLAG_ONGOING_EVENT发起事件, FLAG_INSISTENT振铃音将持续到Notification取消或Notification窗口打开 , FLAG_ONLY_ALERT_ONCE发起Notification后,振铃音或震动均只执行一次, FLAT_AUTO_CANCEL用户单击后自动消失, FLAG_NO_CLEAR全部清除时,Notification才会清除,FLAG_FOREGROUND_SERVICE表示正运行的服务 等多种标志位提醒 (6)自定义视图 自定义视图的布局文件中,仅支持FrameLayout, LinearLayout, RelativeLayout等布局控件。自定义视图步骤如下: 1》 创建自定义视图, 考虑到通知栏的兼容性,自定义视图需避免复杂的设计 2》 获取远程视图对象, 操作自定义视图,必须获取远程视图对象,并将远程视图对象和Notification关联起来,方法如下: RemoteViews expandedView = new RemoteViews(Constants.THIS_PACKAGE_NAME, R.layout.status_bar_ongoing_event_progrss_bar); expandedView.setTextViewText( R.id.descriptioni, item.description ); expandedView .setProgressBar( ...... ); Notification n = new Notification(); expandedView .setImageResource(R.id.appIcon, android.R.drawable.stae_sys_download); n.flags |= Notification.FLAG_ONGOING_EVENT; n.contentView = expandedView; 3》设置PendingIntent, 目前Notification支持多种Intent来响应单击事件,清除事件,处理紧急情况的全屏事件等。 为了在Notification被单击时能响应事件,需要设置Notification的contentIntent变量,响应的方法: Intent intent = new Intent(Contents.ACTION_LIST); intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id)); n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 在执行清除全部的Notification 操作时,通过设置Notification的deleteInten变量可以响应这一件事,响应的方法: Intent deleteIntent = new Intent(); deleteIntent.setClass(context, AlertReceiver.class); deleteIntent.setAction(DELETE_ACTION); notification.deleteIntent = PendingIntent.getBroadcast(context, 0 , deleteIntent, 0); 为了响应紧急实际事件,需要设置Notification的fullScreenIntent 变量,响应的方法: 4》 发起Notification 发起方法: private static final int HELLO_ID = 1; mNotificationManager.notify( HELLO_ID, notification); 1.2.8 搜索框架 为了实现搜素,开发者,需要完成的4方面的工作: 实现搜素配置的文件; 实现显示搜索结果的activity; 实现执行搜素的算法; 发起搜素 1. 实现搜素的配置文件 搜素配置文件存储于res\xml\目录下,并命名为 searchable.xml,搜素配置文件的属性定义 如下: http://www.cnblogs.com/over140/archive/2011/12/29/2305650.html 含义解释链接 目前android支持的搜素模式包括 showSearchLabelAsBadge, showSearchIconAsBadge(已被弃用), queryRewriteFromData, queryRewriteFromText, 。根据需要进行属性设置。如下为android的一个属性配置文件: android:searchMode="showSearchLabelAsBadge" android:voiceLanguageModel="free_form" android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" android:voicePromptText="@string/search_invoke" android:searchSuggestAuthority="com.example.android.apis.SuggestionProvider" android:searchSuggestSelection="?" /> 2. 实现显示搜索结果的Activity 为了接收搜索管理器返回的搜素关键字,在AndroidManifest.xml中必须配置相应的Intent过滤器和元数据。实例如下: android:label="@string/search_query_results"> android:resource="@xml/searchable" /> 如果应用中的所有Activity都支持搜素,那么可以在AndroidManifest.xml中进行如下配置: android:value="SearchQueryResult" /> 接收数据的方法如下: Intent queryIntent = getIntent(); String queryAction = queryIntent.getAction(); if( Intent.ACTION_SEARCH.equals(queryAction) ){ String queryString = queryIntent.getStringExtra(SearchManager.QUERY);//获取关键字 } 3. 实现执行搜素的算法 在执行搜素时,由于搜素的目标和环境不同很难规范,故在Android中,提供了一个支持数据库搜索的内容提供器,即SearchRecentSuggestionsProvider。 如果希望android执行全局搜索时,数据能够被扫描到,需要配置相关的阅读权限。下面是彩信中的一个实现: android:readPermission="android.permission.GLOBAL_SEARCH" /> android:readPermission="android.permission.GLOBAL_SEARCH" /> 4. 发起搜素 可以通过搜素按键或菜单等入口发起搜素。默认情况下, 调用onSearchRequested()方法即可发起搜素。 希望自定义搜素请求,可重载onSearchRequested()方法接口即可。 为了调用搜素对话框,可使用startSearch()方法。
1.2.9 ActionBar框架 actionbar的布局: Action项的执行和普通菜单一样,也是 通过onOptionsItemSelected()方法进行的。还引入了 通过setDisplayHomeAsUpEnable()方法可以激活ActionBar中应用图标对单击事件的响应,响应的方法如下: actionbar.setDisplayHomeAsUpEnabled(true); 通过ActionBar还可以设置自定义的视图,即ActionView,本质是菜单的一种实现。 在ActionBar中,应用图标对单击事件的响应也是通过onOptionsItemSelected()方法进行的,其对应的ID为 android.R.id.home. ActionBar通常需要和Fragment交互。 1. 隐藏ActionBar 两种方式: AndroidManifest.xml中设置activity的theme属性可隐藏ActionBar,方法如下: 代码中实现; ActionBar actionbar = getActionBar(); actionbar.hide(); actionbar.show(); 2. action项管理 Acion本质是特殊的菜单项,有图标和文字组成。Action项有4中属性可配置,分别为: SHOW_AS_ACTION_ALWAYS总作为action项显示 SHOW_AS_ACTION_NEVER永远不作为action项显示 SHOW_AS_ACTION_IF_ROOM控件足够时显示 SHOW_AS_ACTION_WITH_TEST显示action项的文字部分 (1)利用配置文件配置action项 Action项在菜单配置文件中的配置和普通菜单项的区别在于需要设置showAsAction属性: <?xml version="1.0" encoding="utf-8" ?> (2)利用java代码配置action项 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ menu.add("Menu la").setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } 3. ActionView ActionView即在ActionBar上出现的Widget,用于实现一些快捷的操作,实现有两种方式: 无论是加载布局文件还是加载视图类,均可通过配置文件实现。下面是加载布局文件的实例: /> 下面是加载视图类的实例: 其他属性同上,将 actionLayout 属性去掉,添加: android;actionViewClass="android.widget.SearchView" 为了操作ActionView,先获得其句柄: SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView(); 4. 添加Tab页 ActionBar可以显示Tab页,在Activity中进行Fragment间切换,每个Tab可以包含的元素有标题和图标,类似于TabWidget,向ActionBar中添加Tab页的步骤为: (1)创建ActionBar.TabListener, 并实现其方法 (2)设置ActionBar的导航模式为 NAVIGATION_MODE_TABS (3)创建Tab页 (4)添加Tab页 下面是设置导航模式,创建并添加Tab页的示例: final ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); Fragment artistsFragment = new ArtistsFragment(); //创建Fragment //创建并添加Tab页,以及设置TabListener actionBar.addTab(actionBar.newTab().setText(R.string.tab_artists).setTabListener(new TabListener(artistsFragment))); 为了监听Tab的选择,创建TabListener并实现其方法如下: private class MyTabListener implements ActionsBar.TabListener{ private TabContentFragment mFragment; public MyTabListener( TabContentFragment fragment){ mFragment = fragment; } public void onTabSelected(Tab tab, FragmentTransaction ft){ ft.add(R.id.fragment_content, mFragment, null); } public void onTabUnselected(Tab tab, FragmentTransaction ft){ ft.remove( mFragment ); } public void onTabReselected(Tab tab, FragmentTransaction ft){ ......... } } 通过ActionBar的 getSelectedNavigationIndex()方法和getSelectedTab()方法可以获知当前选中的是哪个Tab页。 5. 下拉菜单 下拉菜单主要是基于SpinnerAdapter来处理数据的。实现的步骤如下: (1)设置导航模式 (2)实现并加载资源文件 (3)创建并设置onNavigationListener 设置导航模式的方法,调用setNavigationMode()方法将导航模式设置为NAVIGATION_MODE_LIST即可,具体方法: ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar. NAVIGATION_MODE_LIST); 资源的实现方式有数组和资源文件两种,下面是资源文件的实现示例: - Mercury
- Venus
加载资源文件的方法: SpinnerAdapter mSpinnerAdapter = ArrayAdapter.cresteFromResource(this, R.array.action_list, android.R.layout.simple_spinner_dropdown_item); 为监听选择,需创建并设置OnNavigationListener 。创建OnNavigationListener的方法如下: mOnNavigationListener = new OnNavigationListener(){ String[] strings = getResources().getStringArray(R.array.action_list); public boolean onNavigationItemSelected(int position, long itemId){ ..... return true; } } 设置OnNavigationListener 是通过setListNavigationCallbacks()方法实现的,示例如下: actionBar.setListenerNavigationCallbacks(mSpinnerAdapter, mOnNavigationListener);
第二章 资源框架详解 2.1 布局文件 1. 加载布局 可通过setContentView()隐式加载,若希望显式加载,可铜鼓getLayoutInflater()方法来获取LAYOUT_INFLATER_SERVICE服务将布局文件实例化,也可以获取已有的LayoutInflater对象的副本来实例化布局文件。布局文件实例化的方法: 》通过getLayoutInFlater()方法,具体如下: View demo = getLayoutInflater().Inflater(R.layout.demo , null); 》通过系统服务,具体如下: LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View demo = inflater.Inflater(R.layout.demo, null); 》通过已有LayoutInflater对象的副本,具体如下: LayoutInflater inflater = from(conext); View demo = inflater.Inflater(R.layout.demo, null); 2. 密度的逻辑 三种,dp, px, dip ,其中dip最常用,dip的设置与分辨率无关,与屏幕密度有关 3. 特殊标签 在android布局文件中,除了普通的UI空间标签外,还有几种特殊的标签:viewStub, requestFocus, merge 和 include (1)viewStub标签,实际是一种特殊的控件,默认情况下,其所包含的控件是不可见的,并不占任何内存空间,开发者可通过setVisibility()和 inflate()加载viewStub标签所包含的布局,实例如下:android:layout=”@layout/otacall_card“ android:layout_width="" android:layout_height="" /> 其布局文件是通过android:layout属性引用外部布局文件。加载其所含的布局的实例如下: ViewStub otaCallCardStub = (ViewStub)mInCallScreen.findViewById(R.id.otaCallCardStub); otaCallCardStub.inflate(); (2)requestFocus标签, 可以使相应的UI控件获得焦点,使用时在UI控件的内部。
1.1.2 Service组件 Service的类图如下: 将Service纳入编译系统,需要在AndroidManifest.xml中对Service进行显式声明,方法如下:
1.2 应用框架解析 android设计框架来进行相关的管理,主要有Service框架,Activity管理机制,Broadcast机制,对话框框架,标题栏框架, 状态栏框架,通知机制和ActionBar框架等。 1.2.1 Service 框架 服务作为执行应用后台运算和框架层运算的基本组件。根据通信的方式和应用场景,服务有不同的类型。从通信的方式来看,服务可分为本地服务和远程服务,其中远程服务根据通信方式又可分为基于AIDL的服务和基于Message的服务两种。远程服务是android中跨进程通信的主要形式之一。从应用的场景看,服务可分为应用服务和系统服务。 1. 本地服务 如果服务没有跨进程的需求,应将服务设计为本地服务,如下实现了一个本地服务的最基本的形式: public class HelloService extends Service { public IBinder onBind(Intent arg0) { return null; } } 在本地服务中,必须实现onBind()方法。如果不需要绑定服务(即调用bindService()方法),可以返回null。一个具有绑定服务的本地服务实现如下: public class LocalService extends Service{ public class LocalBinder extends Binder{ LocalService getService(){ return LocalService.this; } } public IBinder onBind(Intent intent){ return mBinder; } private final IBinder mBinder = new LocalBinder(); } 默认情况下,Service依然运行在主线程中,而非另开线程,若希望有大量运算量的后台计算,则在实现本地服务时,必须在Service中创建当独的线程来执行相应的运算。 2. 基于AIDL的远程服务 基于AIDL的远程服务,本质上沿袭了分布式计算的思想。android实现分布式计算,并能够跨语言调用。实现基于AIDL的远程服务需要4个步骤: (1)创建AIDL文件。 (2)将AIDL文件纳入编译系统 (3)实现接口方法 (4)绑定服务客户端 完成编码工作后,android会在编译过程中,自动为相应的AIDL文件生成对应的桩(Stub),简化开发的难度。 分别介绍实现基于AIDL的远程服务的4个步骤: (1)创建AIDL文件 实现简单,本身是一个以”I“开头的接口文件。下面是一个实现实例:” interface ITestService{ boolean getSthEnabled(); void setSthEnabled(boolean on); } (2)将AIDL文件纳入编译系统 为了生成响应的桩,必须将AIDL文件纳入编译系统,其在frameworks/base/Android.mk中实现: LOCAL_SRC_FILES += \ core/java/android/os/ITestService.aidl \ 在应用层,以IMediaPlaybackService为例,其在Android.mk中实现通常如下: LOCAL_SRC_FILE := $(call all-java-files-under, src) \ src/com/android/music/IMediaPlaybackService.adil 在框架层和应用层将AIDL文件纳入编译系统,两者写法不同。 (3)实现接口方法 只需要继承相应接口的Stub子类即可,但必须实现接口所定义的所有方法,实例如下; public class ITestServiceImpl extends ITestService.stub{ public boolean getSthEnabled() { ............ } public void setSthEnabled(boolean on){ ............... } } 为了便于客户端绑定,通常会将桩封装到一个服务中,方法如下: public class TestService extends Service{ public IBinder onBind(Intent arg0){ return new ITestServiceImpl(getApplicationContext()); } } (4)绑定服务客户端 为了与服务进行通信,必须在客户端绑定远程服务,在应用层的实现中,如果是跨进程调用的,必须将相应的ITestService文件复制到客户端所在的进程中。假设服务位于com.miaozl.text包中,在客户端实现进程调用时,方法如下: private ITestService mTest = null; public void onCreate(){ Intent intent = new Intent(); intent.setComponent( new ComponentName("com.miaozl.test" , "com.miaozl.test.service.ITestService") ); bindService(intent, mTestConnection, Context.BIND_AUTO_CREATE); } 如果是在本地进程中,实现如下: bindService( new Intent(this, ITestService.class), mTestConnection, BIND_AUTO_CREATE ) 而mTestConnection的是实现则不区分是本地应用调用还是夸进程调用,具体如下: private ServiceConnection mTestConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName name, IBinder service){ mTest=ITestService.Stub.asInterface(service);//绑定方法 } public void onServiceDisconnected(ComponentName name){ mTest = null; } } 注意: 绑定服务是以异步的方式进行的,对于必须为同步的场景,是无法实现绑定服务的。 3. 基于Messenger的远程服务 基于Messenger的远程服务同样是跨进程的,其本质是将本地服务和Messager结合,以便实现进程间的通信。基于Messenger实现远程服务的实例如下: public class AlertService extends Service{ final Messenger mMessenger = new Messenger(new ServiceHandler()); private final class ServiceHandler extends Handler{ public ServiceHandler(Looper looper){ super(looper); } public void handleMessage(Message msg){ processMessage(msg); } } void processMessage(Message msg){ ......... } public IBindler onBind(Intent intent){ //服务必须是实现的方法 return mMessenger.getBinder();
} } 通过Messenger,服务的调用者可以方便地发送Message,实例如下: Messenger mService = null; private ServiceConnection mConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName className, IBinder service){ mService = new Messenger(service); Message msg = Message.obtain(null, MessengerService.MSG_SET_VALUE, this.hashCode(), 0); mService.send(msg); } } 4. 系统服务 系统服务主要由3部分构成: *Service.java , I*.aidl , *Manager.java 。另外还需要在SystemServer.java增加框架层封装,在ContextImpl.java增加应用层接口。 为了实现系统服务,需要实现5部分的内容: 接口文件,客户端文件,桩文件(系统自动实现),服务端文件,系统调用接口。 (1)接口文件, 通常,接口文件仅用到了基本的数据类型,如果需要用到复杂的数据类型,则需要对数据进行序列化。如下为IAlarmManager.aidl文件的具体实现: interface IAlarmManager{ void set(int type, long triggerAtTime, in PendingIntent operation); void setRepeating(ing type, long triggerAtTime, long interval, in PendingIntent operation); void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); void setTime(long millis); void setTimeZone(String zone); void romove(in PendingIntent operation); } 在AIDL文件中,通常定义了客户端需要调用的接口中的方法。 (2)客户端文件 在android系统中,客户端文件对系统服务的额调用非常简单,其调用方法类似于普通方法调用,只是需要捕获RemoteException异常。下面是AlarmManager的部分实现: public class AlarmManager { private final IAlarmManager mService; AlarmManager(IAlarmManager service){ mService = service; } public void set(int type, long triggerAtTime, PendingIntent operation){ try{ mService.set(type, triggerAtTime, operation); }catch (RemoteException ex){} } } (3)服务端文件 服务端的实现是实现系统服务的最重要的工作,下面是AlarmManagerService的部分实现: class AlarmManagerService extends IAlarmManager.Stub{ public void set(int type, long triggerAtTime, PendingIntent operation){ setRepeating(type, triggerAtTime, 0 , operation); } } (4)系统调用接口 为了方便应用层进行调用,需要在ContextImpl.java中实现统一的接口封装,并在Context.java中定义如下接口: public static final String ALARM_SERVICE = "alarm"; 在ContextImpl.java中实现统一接口封装的代码如下: class ContextImpl extends Context{ private static AlarmManager sAlarmManager; public Object getSystemService(String name){ if(ALARM_SERVICE = "service”){ return getAlarmManager(); } }
private AlarmManager getAlarmManager(){ synchronized(sSync){ if(sAlarmManager == null){ IBinder b = ServiceManager.getService(ALARM_SERVICE); IAlarmManager service = IAlarmManager.Stub.asInterface(b);// ? sAlarmManager = new AlarmManager(service); } } return sAlarmManager; } } 完成以上工作后,如果需要闹钟服务,可按照下列方式执行调用: AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE); 注意:框架层的变动会导致SDK的变动,所以需要更具设计者的需求选择@hide或make update-api来更新current.xml 5. 服务配置 为了对服务进行配置,必须在AndroidManifest.xml中对服务进行配置,其方法如下:
1.2.2 Activity管理机制 android的管理是通过Activity栈和Task来进行的。 1. Activity栈 android的管理主要通过android栈来进行的。当一个activity启动时,系统会根据配置或调用方式,将activity压入一个特定的栈中,系统处于运行状态,当按下Back键或触发finish()方法时,activity会从栈中被压出,进而被销毁,到那个有新的activity压入栈时,如果原activity仍可见,则原activity的状态变为暂停状态,如果activity完全被遮挡,则其状态变为停止。 2.Task Task与activity栈有密切的关系。一个Task对应一个activity栈,Task是根据用户体验组成的运行期逻辑单元,其与应用的区别在于,Task中的activity可以由不同的应用组成。在实际的终端使用中,在主界面长按Home键弹出一个网格界面即是当前运行的Task而非应用。
Task的定义为与 framework/base/services/java/com/androd/server/am目录下的TaskRecord.java中,一个Task由tasked , affinity, clearOnBackground, intent , affinityIntent, origActiivty, realAcitvity, numActivities, lastActiveTime, rootWasReset, stringName等属性构成。 在activity中,有不少属性与Task 相关,如 android:allowTaskReparenting, android:taskAffinity等。 (1)Task间移动配置 android:allowTaskReparenting 属性用来配置是否允许activity从启动它的Task移动到和该Activity设置的TaskI亲和性相同的Task中, (2)Task状态设置 android:alwaysRetainTaskState 属性用于配置是否保留Activity所在的Task状态, 默认为false clearTaskOnLaunch 当Task从主界面重新启动时,是否需要清除除根activity外的的所有activity,默认为false finishOnTaskLaunch 当Task从主界面重新启动时,特定的activity是否需要被销毁,默认为false (3)Task亲和性 Task亲和性,由android:taskAffinity属性定义。如果希望activity启动时,运行在特定的Task中,必须显式设置这个属性。 注意:只有通过标志为FLAG_ACTIVITY_NEW_TASK的intent启动activity时,该activity的android:taskAffinity属性才有效,系统才会将就要有相同Task亲和性的Task 切换到前台,然后启动该activity,否则该activity忍让运行咋启动它的task中。
1.2.3 Broadcast机制 广播涉及顺序广播,无序广播,广播接收器等概念。 1. 顺序广播 当广播需要以类似消息链的方式进行时,应采用顺序广播,顺序广播的接收器可以抛弃或继续传递消息。具体事例如下: Intent LlcpLinkIntent = new Intent(); LlcpLinkIntent.setAction(NfcAdapter.ACTION_LLCP_LINK_STATE_CHANGED); LlcpLinkIntent.putExtra(NfcAdapter.EXTRA_LLCP_LINK_STATE_CHANGED, NfcAdapter.LLCP_LINK_STATE_ACTIVATED); mContext.sendOrderedBroadcast(LlcpLinkIntent, NFC_PERM); 2. 无序广播 无序广播是异步的,广播接收器除了接收广播外,无法对无序广播的行为产生影响。下面示例: Intent intent = new Intent(Intent.ACTION_ARIPLANE_MODE_CHANGED); intent.putExtra("state", enabling); mContext.sendBroadcast(intent); 3.广播接收器 为了接收广播,必须在AndroidManifest.xml中配置广播接收器或通过java实现广播接收器,配置方法:
1.2.4 对话框框架 在android中,目前有4中创建对话框的方式,分别为AlertDialog, ProgressDialog, DatePickerDialog, TimePickerDialog。 其中AlertDialog是最通用的对话框形式, ProgressDialog用于描述进度信息,后两者主要用于日期和时间的场景中。 1. AlertDialog 默认情况下,通过setMessage()来设置显示的文字信息,通过setView()加载视图。创建对话框通过AlertDialog.Builder进行。 创建对话框的一般步骤: >定义对话框的ID,备用; >在onCreateDialog()中创建对话框 >通过showDialog()显示对话框 >通过dismissDialog()隐藏对话框 在某场景下,由于正在执行无法中断的计算,弹出的对话框不希望被用户取消,在可使用下列方法设置: public void setCancelable(boolean flag) //使用于所有对话框 由于Dialog管理机制问题,Dialog具备记忆功能,这在需要数据更新的场景中,稍麻烦,解决方法是在 onPrepareDialog() 方法中进行数据更新。 注意: 由于无法在对话框队列中记忆对话框状态,dismissDialog()方法必须和showDialog()成对出现,若无showDialog()与之配对会发生异常。 2. ProgressDialog 进度对话框,常用于耗时的操作,应将Progress Dialog 放在主线程之外执行,否则极易出现androidANR消息。常见的形式如下: ProgressDialog mWaitDialog = new ProgressDialog(); mWaitDialog.setMessage(getString(R.string.waiting)) ; mWaitDialog.setIndeterminate(true); mWaitDialog.setCancelable(false); 完整的进度对话框还需要定义一个ID, showDialog(iD); dismissDialog(ID); 若开发着不希望用户通过Back键手动销毁对话框,可进行设置 mWaitDialog.setCancelable(true); 若希望能监听进度对话框取消的消息,可如下实现: mWaitDialog.setOnCancelListener( new OnCancelListener() ){ public void onCancel(DialogInterface dialog){ ....... } } 进度条支持两种风格的进度显示:一种是进度条,一种是环形转动。这两种进度显示对应的风格分贝为 ProgressDialog.STYLE_HORIZONTAL 和 ProgressDialog.STYLE_SPINNER, 默认的风格为后者,设置方法: mProgressDialog.setProgressStyle( ProgressDialog.STYLE_HORIZONTAL ); ProgressDialog支持的进度复读为 0 ~~~10000 其同时支持主进度和辅进度,其本质是是实现对ProgressBar的封装。 3. DatePickerDialog 日期对话框,创建时通常需要设置初始年,月,日和监控日期变化的回调函数。 DatePickerDialog(this, mDateSetListener, mYear, mMonth, mDay); 下面是日期对话框回调函数的实现: private DatePickerDialog.onDateSetListener mDateSerListener = new DatePickerDialog.OnDateSerListener(){ public void OnDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth){ mYear = year; mMonth = monthOfYear; mDay = dayOfMonth; updateDisplay(); } }; 代码中更新日期对话框的显示,方法: dialog.updateTime(mYear, mMonth, mDay); 4. TimePickerDialog 方法类似DatePickerDialog 1.2.5 标题栏框架 目前标题栏显示支持进度显示,允许用户隐藏,自定义标题栏。目前android标题栏支持FEATURE_NO_TITLE, FEATURE_PROGRESS, FEATURE_LEFT_ICON, FEATURE_RIGHT_ICON, FEATURE_INDETERMINATE_PROGRESS, 等多种定制。 1. 隐藏标题栏 两种实现方式,在AndroidManifest.xml中和代码中实现 xml中,在activity中添加 android:theme="@android:style/Theme.NoTitleBar" 代码中, requestWindwoFeature(android.view.Window.FEATURE_NO_TITLE); 2. 自定义标题栏 注意标题栏的FEATURE_CUSTOM_TITLE 不能和FEATURE_LEFT_ICON, FEATURE_NO_TITLE等同时使用,下面是设置方法: requestWindowFeature( Window.FEATURE_CUSTOM_TITLE ); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title_1 ); 3. 进度显示 android中支持主进度和辅进度两种进度显示,辅进度在本地应用中较少使用。在网络环境中,如在进行流媒体播放时,可以用主进度表示播放进度,用辅进度标示下载进度。二者显示范围为 0~~100。 如下示例: requestWindowFeature(Window.FEATURE_PROGRESS); setProgressBarVisibility(true); setProgress(progressHorizontal.getProgress() * 100); setSecondaryProgress(progressHorizontal.getSecondaryProgress() * 100); 实际开发中,无法使用明确的进度显示,如向服务器发送请求后,等待反馈的过程,这时就用到了不定进程显示,下面是不定进程显示示例: requestWindowFeature( Window.FEATURE_INDETERMINATE_PROGRESS ); setProgressBarIndeterminateVisibility(true); 4. 图标显示 标题栏又标题,进度条,左标题,右标题构成。下面以左标题为例介绍: requestWindowFeature( Window.FEATURE_LEFT_ICON ); getWindow().setFeatureDrawableResource( Window.FEATURE_LEFT_ICON, R.drawable.ic_list_bookmark );
1.2.6 状态栏框架 与传统终端状态栏一样,android状态栏提供电量信息,蜂窝信息,SMS, MMS, 邮件,WIFI信号,蓝牙信号,闹钟等系统的状态信息。另外状态栏还有通知栏的功能。 其中,起主要作用的是 StatusBarPolicy, 它承担着接收系统发来的Intent的信息,更新状态显示的功能,他是服务StatusBarManagerService的客户端。 StatusBarManagerService在创建时,会加载config_statusBarIcons数组。在framework\base\core\res\res\values目录下,config.xml中定义了config_statusBarIcons数组确定了状态图标的加载顺序。 整个状态栏框架是通过StatusBarService来实现。在StatusBarService初始化时,初始化了一个用于显示statusbar的StatusBarView。 在StatusBarView中定义了状态栏的实现布局,而具体的布局问及那是在framework\base\packages\systemui\res\layout\status_bar.xml实现的。 1. 状态栏的隐藏 两种方式: AndroidManifest.xml中实现: android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" 代码中实现: getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN ); requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题栏 上述隐藏方式值适合静态场景,在隐藏标题栏后再动态显示状态栏已经超出以上两种方法的能力。 此时可通过鞋面方法动态的隐藏和系那是状态栏: getWindow().addFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN ); //隐藏 getWindow().clearFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN ); //显示 2. 电量显示 当StatusBarPolicy 收到Action为ACTION_BATTERY_CHANGED的Intent时,StatusBarPolicy会通知StatusBarManager进行电量图标的更新。同时还响应ACTION_BATTERY_LOW, ACTION_BATTERY_OKAY, ACTION_POWER_CONNECTED的intent。 3. 蜂窝信息 android对蜂窝协议的支持十分充分,目前支持GSM, UMTS, CDMA, 4G等。 4.WIFI信息 对于wifi信号,android可以响应Action为 WifiManager.NETWORK_STATE_CHANGED_ACTION, WifiManager.WIFI_STATE_CHAGED_ACTION, WifiManager.RSSI_CHANGED_ACTION的intent, 相应的更新方法: updateWifi() 5.蓝牙信息 对蓝牙信息,android可以响应BluetoothAdapter, BluetoothHeadset, BluetoothA2dp 和 BluetoothPbap的状态变化,相应的更新方法: updateBluetooth();
1.2.7 通知机制 多种方式向用户反馈系统状态信息,Toast提醒,通知栏提醒,对话框提醒的等。其中Toast常用于页面的显示,通知栏提醒常用于交互事件的通知,一般非常重要的通知以对话框的形式给出。 Toast 和Notification 均由框架层的 NotificationManagerService维护 1.Toast 简单提示用户信息,常只显示文本。也可以自定义。 自定义位置: toast.setGravity( Gravity.TOP|Gravity.LEFT, 0, 0 ); 自定义视图: LinearLayout dialog = (LinearLayout)LayoutInflater.from(this).inflate(R.layout.retry_sending_dialog, null); Toast undeliveredDialog = new Toast(this); undeliveredDialog.setView(dialog); undeliveredDialog.setDuration(Toast.LENGTH_LONG); undeliveredDialog.show(); 注意: Toast不能在AsyncTask的doInBackground()方法中运行,如果要实现类似的效果,可在Handler 中进行处理。 2. Notification 适合于交互事件的通知,常用于短消息,即时消息,下载,外围设备的状态变化的场景中。 Notification支持文字显示,振动,三色灯,振铃音等提示形式,默认情况下,仅显示消息标题,消息内容和时间。下面是一个基本实现: NotificationManager nm = (NotificationManager)getSystemService( NOTIFICATION_SERVICE ); CharSequence from = "Joe"; CharSequence message = "kthx.meet u for dinner. cul"; PendingIntent contentIntent = PendingIntent.getActivity(this, 0 , new Intent(this, IncomingMessageView.class), 0); String tickerText = getString(R.string.incoming_message_ticker_text, message); Notification notif = new Notification(R.drawable.stat_sample, tickerText, System.currentTimeMillis()); notif.setLatestEventInfo(this, from, message, contentIntent); notif.vibrate = new Long[]{100, 250, 100, 500}; nm.notify(R.string.incoming_message_ticker_text, notify); (1) Notification 管理 android通过标示符来管理Notification,发起一个Notification的方法如下; notificationManager.notify( notificationId, mNotification ); 取消Notification的方法有很多,如果希望用户单击后Notificatino即被清除,则相应的方法如下: notification.flags |= FLAG_AUTO_CANCEL; 如果希望手动清除某项,相应的方法: mNotificationMgr.cancel( LOW_MEMORY_NOTIFICATION_ID ); 若希望清除所有Notification时,相应的方法: mNotificationMgr.cancelAll();
(2)振动提醒 通常用于比较紧迫的场景,示例如下: Notification n = new Notification(); n.vibrate = new Long[]{ 0, 700, 500, 1000 };//振动方式:延迟0ms,然后振动700ms,接着振动1000ms mNM.notify( 1, n ); 若希望设置为默认的振动方式,相应的方法: notification.defaults |= Notification.DEFAULT_VIBRATE; (3)三色灯提醒 只有设置了Notification的标志位为FLAG_SHOW_LIGHTS,才能支持三色灯提醒。创建三色灯提醒的Notification示例如下: Notification n = new Notification(); n.flags |= Notification.FLAG_SHOW_LIGHTS; n.ledARGB = 0xff0000ff; n.ledOnMS = 300; n.ledOffMS = 300; mNM.notify( 1, n ); 若希望设置默认三色灯提醒,相应的方法: notification.defaluts |= Notification.DEFAULT_LIGHTS; (4)振铃声提醒 Notification支持默认铃声,自定义铃声,android多媒体数据库等多种提醒方式,相应的配置方法: notification.defaults |= Notification.DEFAULT_SOUND; //默认铃声 notification.sound = Uri.prase( "file:///sdcard/notification/ringer.mp3" ); //自定义铃声 notification.sound = Uri.withAppendedPath( Audio.Media.INTERNAL_CONTENT_URI, "6" ); //基于android多媒体数据库的提醒方式 (5)提醒标志位 Notification支持FLAG_SHOW_LIGHTS三色灯提醒, FLAG_ONGOING_EVENT发起事件, FLAG_INSISTENT振铃音将持续到Notification取消或Notification窗口打开 , FLAG_ONLY_ALERT_ONCE发起Notification后,振铃音或震动均只执行一次, FLAT_AUTO_CANCEL用户单击后自动消失, FLAG_NO_CLEAR全部清除时,Notification才会清除,FLAG_FOREGROUND_SERVICE表示正运行的服务 等多种标志位提醒 (6)自定义视图 自定义视图的布局文件中,仅支持FrameLayout, LinearLayout, RelativeLayout等布局控件。自定义视图步骤如下: 1》 创建自定义视图, 考虑到通知栏的兼容性,自定义视图需避免复杂的设计 2》 获取远程视图对象, 操作自定义视图,必须获取远程视图对象,并将远程视图对象和Notification关联起来,方法如下: RemoteViews expandedView = new RemoteViews(Constants.THIS_PACKAGE_NAME, R.layout.status_bar_ongoing_event_progrss_bar); expandedView.setTextViewText( R.id.descriptioni, item.description ); expandedView .setProgressBar( ...... ); Notification n = new Notification(); expandedView .setImageResource(R.id.appIcon, android.R.drawable.stae_sys_download); n.flags |= Notification.FLAG_ONGOING_EVENT; n.contentView = expandedView; 3》设置PendingIntent, 目前Notification支持多种Intent来响应单击事件,清除事件,处理紧急情况的全屏事件等。 为了在Notification被单击时能响应事件,需要设置Notification的contentIntent变量,响应的方法: Intent intent = new Intent(Contents.ACTION_LIST); intent.setClassName(Constants.THIS_PACKAGE_NAME, BluetoothOppReceiver.class.getName()); intent.setData(Uri.parse(BluetoothShare.CONTENT_URI + "/" + item.id)); n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 在执行清除全部的Notification 操作时,通过设置Notification的deleteInten变量可以响应这一件事,响应的方法: Intent deleteIntent = new Intent(); deleteIntent.setClass(context, AlertReceiver.class); deleteIntent.setAction(DELETE_ACTION); notification.deleteIntent = PendingIntent.getBroadcast(context, 0 , deleteIntent, 0); 为了响应紧急实际事件,需要设置Notification的fullScreenIntent 变量,响应的方法: 4》 发起Notification 发起方法: private static final int HELLO_ID = 1; mNotificationManager.notify( HELLO_ID, notification); 1.2.8 搜索框架 为了实现搜素,开发者,需要完成的4方面的工作: 实现搜素配置的文件; 实现显示搜索结果的activity; 实现执行搜素的算法; 发起搜素 1. 实现搜素的配置文件 搜素配置文件存储于res\xml\目录下,并命名为 searchable.xml,搜素配置文件的属性定义 如下: http://www.cnblogs.com/over140/archive/2011/12/29/2305650.html 含义解释链接 目前android支持的搜素模式包括 showSearchLabelAsBadge, showSearchIconAsBadge(已被弃用), queryRewriteFromData, queryRewriteFromText, 。根据需要进行属性设置。如下为android的一个属性配置文件:
1.2.9 ActionBar框架 actionbar的布局: Action项的执行和普通菜单一样,也是 通过onOptionsItemSelected()方法进行的。还引入了 通过setDisplayHomeAsUpEnable()方法可以激活ActionBar中应用图标对单击事件的响应,响应的方法如下: actionbar.setDisplayHomeAsUpEnabled(true); 通过ActionBar还可以设置自定义的视图,即ActionView,本质是菜单的一种实现。 在ActionBar中,应用图标对单击事件的响应也是通过onOptionsItemSelected()方法进行的,其对应的ID为 android.R.id.home. ActionBar通常需要和Fragment交互。 1. 隐藏ActionBar 两种方式: AndroidManifest.xml中设置activity的theme属性可隐藏ActionBar,方法如下: 代码中实现; ActionBar actionbar = getActionBar(); actionbar.hide(); actionbar.show(); 2. action项管理 Acion本质是特殊的菜单项,有图标和文字组成。Action项有4中属性可配置,分别为: SHOW_AS_ACTION_ALWAYS总作为action项显示 SHOW_AS_ACTION_NEVER永远不作为action项显示 SHOW_AS_ACTION_IF_ROOM控件足够时显示 SHOW_AS_ACTION_WITH_TEST显示action项的文字部分 (1)利用配置文件配置action项 Action项在菜单配置文件中的配置和普通菜单项的区别在于需要设置showAsAction属性: <?xml version="1.0" encoding="utf-8" ?>
第二章 资源框架详解 2.1 布局文件 1. 加载布局 可通过setContentView()隐式加载,若希望显式加载,可铜鼓getLayoutInflater()方法来获取LAYOUT_INFLATER_SERVICE服务将布局文件实例化,也可以获取已有的LayoutInflater对象的副本来实例化布局文件。布局文件实例化的方法: 》通过getLayoutInFlater()方法,具体如下: View demo = getLayoutInflater().Inflater(R.layout.demo , null); 》通过系统服务,具体如下: LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View demo = inflater.Inflater(R.layout.demo, null); 》通过已有LayoutInflater对象的副本,具体如下: LayoutInflater inflater = from(conext); View demo = inflater.Inflater(R.layout.demo, null); 2. 密度的逻辑 三种,dp, px, dip ,其中dip最常用,dip的设置与分辨率无关,与屏幕密度有关 3. 特殊标签 在android布局文件中,除了普通的UI空间标签外,还有几种特殊的标签:viewStub, requestFocus, merge 和 include (1)viewStub标签,实际是一种特殊的控件,默认情况下,其所包含的控件是不可见的,并不占任何内存空间,开发者可通过setVisibility()和 inflate()加载viewStub标签所包含的布局,实例如下: