第一章 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<String>(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布局文件的一个示例:
<CheckBoxPreference
android:key="bt_discoverable"
android:title="@string/bluetooth_visibility"
android:dependency="bt_checkbox"
android:summaryOn="@string/bluetooth_is_discoverable"//偏好说明
android:summaryOff="@string/bluetooth_not_discoverable"
android:persistent="false" />
(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,方法如下:
<activity android:name="TestActivity"
android:label="@string/app_name"
android:label="@string/app_name"
android:launchMode="singleInstance"
//忽略重力传感器和键盘隐藏等变化
android:configChanges="orientation|keyboardHidden" >
</activity>
注意:通常情况下,当单个应用中包含的文件较多时,通常需创建子文件夹进行区分,如将实现Activity的文件放在ui文件夹下,将实现Service, Provider的文件放在service文件夹下,将实现的一些自定义视图放在view文件夹下,将实现一些工具文件放置在util文件夹下。这样声明的方法略有不同,具体如下:
<activity android:name=".ui.TestActivity"
> ........ </activity>//注意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进行显式声明,方法如下:
<service android:name=".service.TestService" ></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类型联合使用,表示应用可以打开的数据类型,用法如下:
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:android:name="android.intent.category.DEFAULT" />
<data android:mineType="image/*" />
<data android:mineType="video/*" />
</intent-filter>
(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过滤器的设置方法如下:
<receiver android:name=“.receiver.ImServiceAutoStarter”
android:process="android.process.im" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
(2)网络监听
对网络进行监听的设置如下:
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
在接收到相关的信息后,可以从中获取网络的状态,方法如下;
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, 方法如下:
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.OPENABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mineType="image/*" />
</intent-filter>
(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纳入编译系统的示例如下:
<receiver android:name="AlertReceiver">
<intent-filter>
<action android:name="android.intent.action.EVENT_REMINDER" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
<action adnroid:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
<data android:scheme="content" />
</intent-filter>
</receiver>
若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(){
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中对服务进行配置,其方法如下:
<service android:name=".service.EmailBroadcastProcessorService" />
对于远程服务,若希望被其他进程调用,则必须开放相应的权限,开放权限的方法如下:
<service android:name=".service.EmailBroadcastProcessorService"
android:exported;"true" />
如果服务具有一些敏感的信息,需要对其进行权限配置,这在对安全要求较高的应用中十分重要。权限配置方法如下:
<service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
android:permission="android.permission.MASTER_CLEAR"
android:exporte="true" />
如果希望服务运行在单独的进程中,则可以应用如下的方法进行配置:
<service android:name="android.os.MessengerSerivce"
android:process=":messengerService" /> <!--设置进程名-->
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等属性构成。
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实现广播接收器,配置方法:
<receiver android:name=".receiver.StartupReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
代码中配置:
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)振动提醒
(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,搜素配置文件的属性定义 如下:
目前android支持的搜素模式包括 showSearchLabelAsBadge, showSearchIconAsBadge(已被弃用), queryRewriteFromData, queryRewriteFromText, 。根据需要进行属性设置。如下为android的一个属性配置文件:
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="#string/search_hint" <!--在没有录入文字时,显示在搜索框中的文字,提示搜素的内容-->
android:searchMode="showSearchLabelAsBadge"
android:voiceLanguageModel="free_form" <!--语音识别系统使用的语言模式,free_form自由模式-->
android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" <!--关键字,启动语音搜索的功能-->
android:voicePromptText="@string/search_invoke" <!--显示在语音输入对话框中的信息-->
android:searchSuggestAuthority="com.example.android.apis.SuggestionProvider" <!--本属性值必须符合AndroidManifest.xml文件中<provider>元素的android:authorities属性提供的权限-->
android:searchSuggestSelection="?" <!--作为参数传递给查询函数-->
/>
2. 实现显示搜索结果的Activity
为了接收搜索管理器返回的搜素关键字,在AndroidManifest.xml中必须配置相应的Intent过滤器和元数据。实例如下:
<activity android:name=".app.SearchQueryResults"
android:label="@string/search_query_results">
<intent-filter>
<action android:name="android.intent.action.SEARCH" /> <!--搜素Action-->
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable" />
<!--配置文件-->
</activity>
如果应用中的所有Activity都支持搜素,那么可以在AndroidManifest.xml中进行如下配置:
<application ...>
<meta-data android:name="android.app.default_searchable"
android:value="SearchQueryResult" />
<!--显示搜索结果的Activity-->
</application>
接收数据的方法如下:
Intent queryIntent = getIntent();
String queryAction = queryIntent.getAction();
if( Intent.ACTION_SEARCH.equals(queryAction) ){
String queryString = queryIntent.getStringExtra(SearchManager.QUERY);//获取关键字
}
3. 实现执行搜素的算法
在执行搜素时,由于搜素的目标和环境不同很难规范,故在Android中,提供了一个支持数据库搜索的内容提供器,即SearchRecentSuggestionsProvider。
如果希望android执行全局搜索时,数据能够被扫描到,需要配置相关的阅读权限。下面是彩信中的一个实现:
<provider android:name="SuggestionsProvider"
android:readPermission="android.permission.READ_SMS"
android:authorities="com.android.mms.SuggestionsProvider" >
<path-permission android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH" />
<path-permission android:pathPrefix="/search_suggest_shortcut"
android:readPermission="android.permission.GLOBAL_SEARCH" />
</provider>
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,方法如下:
<activity android:theme="@android:style/Theme.Holo.NoActionBar" >
代码中实现; 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" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_save"
android:icon="@drawable/ic_menu_save"
android:title="@string/menu_save"
android:showAsAction="ifRoom|withText" />
</menu>
(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,用于实现一些快捷的操作,实现有两种方式:
无论是加载布局文件还是加载视图类,均可通过配置文件实现。下面是加载布局文件的实例:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_search"
android:title="Search"
andrid:showAsAction="ifRoom"
android:actionLayout="@layout/searchview"
/>
</menu>
下面是加载视图类的实例:
其他属性同上,将 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);
资源的实现方式有数组和资源文件两种,下面是资源文件的实现示例:
<resources>
<string-array name="action_list">
<item>Mercury
</item>
<item>Venus</item>
</string-array>
</resources>
加载资源文件的方法:
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标签所包含的布局,实例如下:
<ViewStub android:id="@+id/otaCallCardStub"
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控件的内部。
<Button android:id="....." ....
android:text="..." >
<requestFocus />
<Button>
(3)merge 标签
merge主要用于优化根布局文件或在使用viewStub标签和include标签时,减少引用冗余。注意, merge标签仅能作为布局文件的根布局, 如果希望布局加载器(LayoutInflater)加载以merge标签为跟布局的布局文件,需要使用View inflater()指定一个ViewGroup作为其容器,并设置attachToRoot 为ture。
》优化根布局
考虑到Activity 的根布局为FrameLayout控件(ID为content),如果期望的布局文件的根布局也为
FrameLayout,则可利用merge标签直接将相应的子控件放置在Activity的根布局中,示例如下:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView .....>
</merge>
》减少冗余
下面以启动器为例,是通过include标签应用外部布局文件all_apps.xml的情况如下:
<include layout="@layout/all_apps" />
布局文件all_apps.xml的实现,如下:
<merge xmlns:android="http://schemas.android.com/apk/res/android"
>
<include layout="@layout/all_apps_2d" />
</merge>
(4)include标签
作用类似于C语言中的include关键字,用于引用外部布局文件,其复用布局文件的作用。
2.2 值文件
包括多种类型的布局文件,存在于res\values目录下,简体中文目录为 res\vlaue-zh-rCN, 繁体中文目录为res\values-zh-rTW , 命名格式为 values-<语言>-<方言>
1. 字符串文件
<string name="voice_mode_off"
translatable="false">2</string>
其中translatable="flase" ,表示字符串资源不必本地化,即不需要翻译成其他的语言,全部使用默认的。
字符串资源的加载:
(1)字符串资源的引用
在android中,分系统资源和应用资源,两种资源引用方式不同,系统资源并不全部对应用层开放,
对应用层开放的系统资源在framework\base\core\res\res\vlaues\目录下的public.xml中定义。不对应用层开放的系统资源加载方式如下:
getString(com.android.internal.R.string.using); //采用”@ com.android.internal.R./ “方式
其他资源与字符串加载类似。应用本身的字符串资源在xml 文件中直接加载,或 R.string.***
引用的框架层的字符串资源在xml配置文件中的加载方式:
android:text="@android>:string/cancel" <!--采用 @android:string/...的方式-->
java文件中加载框架层字符串资源方式: android.R.string.cancel
(2)数据交换
对于复杂的字符串数据,android支持XLIFF和通配符。
在一个字符串标签中,android仅支持一个通配符,其应用方法如下:
<string name="text">text "%1"</string>
在java中,加载通配符的方法: text.setText(getString(R.string.hello).replace("%1", String.values(curPage)));
android对XLIFF的支持如下:
<string name="anr_process">Process <xliff:g id="process">%1$s</xmliff:g> is not responding.<string>
XLIFF的命名空间为 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
在java中加载字符串的方法: res.getString(com.android.internal.R.string.anr_process, name1.toString(), name2.toString());
(3)下划线的实现
android中的文字特效目前只支持加粗和斜体两种,如果要实现下划线,可借助HTML语法。 如下:
testView.setText(Html.fromHtml("<u>"+helloStr+"</u>"));
(4)语言切换的实现
语言切换时通过资源管理器进行的,方法如下;
Resources resources = getResources();
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();//通过此类得到屏幕的一下显示信息,如分辨率,字体,大小
config.locale = Locale.SIMPLIFIED_CHINESE;
resources.updateConfiguration(config, dm);
通过类似的设置,开发者可切换屏幕的密度,MCC,MNC,屏幕方向等。
2. 字符串数组文件
字符串数组长用于下拉框等场景,如不希望字符串数组随语言环境的变化而变化,可将其 translatable 属性设为 false
在xml文件中的加载
android:entryValues="array/preferences_alert_type_values"
代码中的加载: ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.colors, android.R.layout.simple_spinner_item);
3. 配置文件
常用于应用的设置,实现如下:
<resources>
<bool name="conflg_sf_limitedAlpha" >false</bool>
</resources>
代码中加载: mLimitedAlphaCompositing = context.getResources().getBoolean(com.android.internal.R.bool.config_sf_limitedAlpha);
4. 属性文件
属性文件通常由多个标签组成。 declar-styleable标签用于声明一个属性组,一个属性组可以由多个属性组成。如下
<declar-styleable name="Animation">
</declar-styleable>
加载属性组的方法:
TypedArray a = context.obtainStyledAttributes(attris, com.android.internal.R.styleable.Animation);
...
a.recycle();//用于随后的复用
eat-comment标签用于声明标签上的内容为注释
另外,attr标签用于声明属性。一个属性包括属性名和属性格式两部分。声明属性的实例如下;
<resources>
<attr name="buttonStyle" format="reference" />
<attr name="buttonStyleSmall" format="reference" />
<attr name="buttonStyleInset" format="reference" />
<attr name="buttonStyleToggle" format="reference" />
</resources>
2.3 创建菜单
根据应用场景的不同,可以分为选项菜单(OptionsMenu),上下文菜单(ContextMenu),子菜单(SubMenu)等。
2.3.1 选项菜单
两种实现方式,基于xml资源文件实现和java代码实现。基于xml资源实现时,当加载的菜单项较多时,android会自动将不能完全显示的菜单放置在More扩展选项菜单中,注意,在More扩展菜单中,无法显示图标。
1. 加载xml资源文件实现菜单
在res\menu\目录下创建菜单资源文件,实例alarm_list_menu.xml 如下:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_item_desk_clock">
........</menu>
在代码中加载:
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.alarm_list_menu, menu);
return super.onCreateOptionsMenu(menu);
}
菜单执行过程:
public boolean onOptionsItemSelected(MenuItem item){
switch(item.getItemId()){
...... }
}
若希望在菜单显示之前做些处理,那么需要关注onPrepareOptionsMenu()方法。下面是一个实现过程。
public boolean onPrepareOptionsMenu(Menu menu){
menu.findItem(DISPLAY_MODE_LAUNCH).setVisible(mDisplayMode != DISPLAY_MODE_LAUNCH);
return true;//若希望显示菜单的内容,需要返回true,否者返回false。
}
2. 代码中实现
通过Menu类来管理菜单,添加菜单:(在onCreateOptionsMenuz()方法中添加)
public MenuItem add(CharSequence title);
public MenuItem add(int titleRes);
public MenuItem add(int groupId, int itemId, int order, CharSequence title);
为菜单设置图标:
public MenuItem setIcon(Drawable icon);
public MenuItem setIcon(int iconRes);
移除菜单方法: removeItem(), 清除所有菜单项: clear()
移除菜单方法: removeItem(), 清除所有菜单项: clear()
执行菜单的隐藏的方法: menu.findItem(R.id.clear_history_menu_id).setVisible(Browser.canClearHistory(this.getContentResolver()));
是菜单失效的方法: menu.findItem(R.id.transfer_menu_clear_all).setEnabled(false);
将菜单与Intent关联起来:
menu.findItem(R.id.dial_context_menu_id).setIntent(new Intent(Intent.ACTION_VIEW, Uri.prase(WebView.SCHEME_TEL + extra)));
对菜单项的操作可通过MenuItem类进行。
3. 菜单组
添加菜单组时,默认的菜单组的ID为NONE,移除菜单组的方法: removeGroup(),还支持菜单组的显示,隐藏,检查等,方法如下:
public void
setGroupCheckable
(int group, boolean checkable, boolean exclusive);
public void
setGroupVisible
(int group, boolean visible);
public void
setGroupEnabled
(int group, boolean enabled);
2.3.2 上下文菜单
上下文菜单不支持快捷键和图标,上下文菜单可用于多种视图,但通常用于列表。通过
registerForContextMenu()
注册上下文菜单或直接为视图安装监听器可以是吸纳视图和上下文菜单的关联,以列表为例,具体如下:
registerForContextMenu(getListView());//注册上下文
getListView().OnCreateContextMenuListener()//安装监听器
同样有两种方法实现,在创建上下文菜单时,需特别注意ContextMenuInfo。 ContextMenuInfo提供了菜单的附加信息,如选中的菜单项在整个上下文菜单中的位置等。
还可为上下文菜单设置标题,上下文菜单的标题支持图标形式。常用方法如下:
public ContextMenu setHeaderTitle(int titleRes);
public ContextMenu setHeaderIcon(int iconRes);
public void clearHeader();
1. 代码实现
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, DELETE_ID, 0, R.string.menu_delete);
}
2. 通过资源文件实现上下文菜单
通过Menu Inflater来实现的。通常需要在创建上下文菜单时获取当前视图的信息,这些信息一般通过ContextMenuInfo 传递给开发者,其没有类似onPreparOptionsMenu(), 但也可以实现,一定要在菜单资源你文件加载完成(即执行 getMenuInflater().inflater())后进行。如下
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo){
menu.setHeaderTitle(R.string.ontext_menu_title); //菜单标题
getMenuInflater().inflater(R.menu.context_menu, menu); //加载资源文件
AdatperContextMenuInfo info = (AdatperContextMenuInfo) menuInfo ;
Cursor c = (Cursor) mAlarmsList.getAdapter().getItem((int) info.position); //获取光标 。。。。。。
}
执行与选项菜单类似,在 onContextItemSelected()方法中实现。
2.3.3 子菜单
子菜单不支持图标和二级子菜单。子菜单的执行在其父菜单的执行方法中执行,对于上下文菜单的子菜单,执行方法为onContextItemSelected(), 对于选项菜单的子菜单,实现方法为: onOptionsItemSelected().
1. 资源文件
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Normal 1" />
<item android:id="@+id/submenu"
android:title="Emotions">
<menu>
<menu>
<item/>...
</menu>
</item>
</menu>
2. 代码实现
也是通过Menu类实现:
SubMenu addSubMenu(final CharSequence title);
创建子菜单:(在onCreateContextMenu()中实现)
menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLSIT, 0, R.string.add_to_playlist);
判断子菜单的存在及获取方法如下: public boolean
hasSubMenu
() l; public SubMenu
getSubMneu
();
2.3.4 弹出菜单
2.3.4 弹出菜单
弹出菜单(PopupMenu),实现如下:
对一个button键,设置弹出菜单,代码实现:
public class PopupMenu1 extends Activity{
protected void onCreate(Bundle savedInstanceState){
supter.onCreate(savedInstanceState);
setContextView(R.layout.popup_menu_1);//加载包含此button的布局文件
}
public void onPopupButtonClick(View button){//button事件点击方法
PopupMenu popup = new PopupMenu(this, button);
popup.getMenuInflater().inflate(R.menu.popup, popup.getMenu());//加载菜单
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickLisener(){
public boolean onMenuItemClick(MenuItem item){
....... ; return true;
}
});
popup.show();
}
}
菜单被销毁时,通过Popupmenu.OnDismissListener可以监听到菜单被销毁的信息。
2.4 断言的处理
在断言中,可以加载字体,HTML文件,图片等,断言中限制文件大小为1MB, 同样在res , raw下的资源文件也有这样的限制。对于超出限制的资源,系统会在运行时提出警告:
android中,断言的管理通过AssetManager进行,获得断言的方法:
AssetManager am = getAssets();// 在Activity中
断言的加载模式有ACCESS_BUFFER, ACCESS_RANDOM, ACCESS_STREAMING, ACCESS_UNKNOWN几种。打开断言文件时,可以设置断言的加载模式,方法如下:
public final InputStream open(String fileName , int accessMode)
1. 加载字体
android自带了sans, serif, 和 monospace等3中字体,系统默认为sans,在一个TextView中设置字体方式:
android:typeface="serif"
当系统引进新字体,放置在assets目录下,通过断言加载字体:
TextView tv = (TextView)findViewById(R.id.custom);
Typeface face = Typeface.createFromAsset(getAssets(), "fonts/Clockopia.ttf");
tv.setTypeface(face);
2 . 加载文件
支持多种文件的加载,场景不同,加载方式也不同。断言中,加载文件的一个常见的场景是基于JSP的HTML等进行的,主要用于基于WebView的应用,其Uri为 file:///android_asset/*.*
2.5 jar包和共享库
1. jar包的加载
源码中,jar包的加载如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_JAVA_LIBRARIES := libarity
LOCAL_SRC_FILES :=$(call all-java-files-under, src)
LOCAL_SDK_VERSION := current
LOCAL_PACKAGE_NAME := Calculator
include $(BUILD_PACKAGE)
########################################
include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := libarity:arity-2.1.2.jar
include $(BUILD_MULTI_PREBUILT)
#Use the following include to make our test apk
inlcude $(call all-makefiles-under, $(LOCAL_PATH))
2.共享库
对于基于源码进行编译的场景,需要现在android.mk中进行共享库的配置,方法如下:
LOCAL_JNI_SHARED_LIBRARIES := gl2jni
为了使用相应的共享库,在java文件中还需要进行相应的加载,方法如下:
public class GL2JNILib{
static {
System.loadLibrary("gl2jni");
}
public static native void init(int width, int height);
public static native void step();
}
2.6 系统资源
系统资源定义主要分布在framework\base\core\res\res目录下。
风格和主题是框架层与应用层在UI设计上的接口,其中应用层能引用的部分在Android.mk中定义在
public.xml中。为使应用层能引用系统UI资源,需要在public.xml中增加对该资源的声明。定义风格文件styles.xml,定义主题的文件为 themes.xml。
在android中,资源采用统一的标示符定义,对于属性资源,其标示符ID从0x01010000开始定义; 对于ID资源,其标示符ID从0x01020000开始定义;对于风格资源,其标示符从0x01030000开始定义; 对于字符串资源,其从0x01040000开始定义;对于维度资源,从0x01050000开始定义; 对于颜色资源,从0x01060000开始定义;对于数组资源,从0x01070000开始定义;对图片资源,从0x01080000开始定义;对于动画资源,从0x010a0000开始。
在启动其他应用前,需要在zygote进程中,将先加载的图片资源定义在arrays.xml中的preloaded_drawables数组中,将颜色资源定义在arrays.xml中的preloaded_color_state_lists数组中,状态栏加载的顺序定义在arrays.xml中的status_bar_icon_order数组中。
注意:
图标的前缀,对于普通图标,其命名为ic_*.png; 对于应用图标,其命名为 ic_launcher_*.png; 对菜单图标,命名为 ic_menu_*.png ;对状态栏图标,命名为 ic_stat_notify_*.png ; 对Tab标签图标,其命名为 ic_tab_*.png ; 对对话框图标,命名为 ic_dialog_*.png
android图片主要使用PNG格式的,
如果需要可拉伸的场景,需要通过draw9path来制作 *.9.png的图片
对于定义略缩图,应用图标,状态栏,快速导航等布局的一些宽高信息,定义在frameworks\base\core\res\res\vallues\目录下的dimens.xml中。