解析Intent,寻找匹配Activity
如果给出component名字(包名、类名)是explicit intent,否则是implicit intent。对于explicit intent,关键就是component 名字,在<intent-fliter>中声明的其他属性被忽略。对于implicit intent,则根据action,category和data来进行匹配。然而一个intent fliter中可以声明多个actions,多个categories,多个data属性,因此可以满足不同的intent。
对于implicit intent设置了action,则该action必须存在于<intent-fliter>之下的某个<action nandroid:name=”……”>。但是和《Pro Android 4.0》所写的不一样,经过测试,我们发现intent-fliter下必须存在action的声明。
1)对于设置了action的intent,不能匹配intent-fliter下没有定义action的情况;
2)对于没有设置action的intent,也不能匹配intent-fliter下没有定义action的情况;
3)对于没有设置action的intent,可以匹配intent-fliter下任何定义的action,但是intent应提供足够的过滤条件,例如data的Uri(测试显示使用category过滤不能匹配)。
至少在Android 4.2版本下的测试如是。因此,我们应该在inter-fliter中声明action name,intent应该提供action name,如特殊情况不能提供,需要提供data属性进行过滤。但这种特殊情况不应该出现,换言之,我们必须做到action匹配。
如果implicit intent设置了data schema,必须能与intent fliter某个data schema匹配;如果intent fliter下设置了data schema,implicit intent必须提供有效的data URI,不能为空。对于URI,除了schema外,还可以对authority(即domain)有要求,在manifest中定义为如下。
<data android:scheme="wei" android:host="flowingflying.com"/>
匹配Uri为:wei://flowingflying.com/….,还可以通过android:path,android:pathPrefix,android:pathpattern做进一步限制,具体可以查看http://developer.android.com/guide/topics/manifest/data-element.htm。
同样,对于data mimetype,也必须双方进行匹配,不存在不能视为匹配。通过setType()进行设置,如intent.setType(“text/html”);此外,对于subType,可以用*作为通配,例如intent.setType(“text/*”);
ACTION_PICK
Activity A通过intent唤起Activity B,在某种情况下,我们希望B在结束时能换回信息给A以便A确定下一步的处理工作,这同时也是利用B作为UI的一种方式。一种常用的方式是B是list列表的UI,下面是一个小例子,我们不具体实现B的list UI,只是模拟选择item后设置返回值和返回信息,返回信息由Intent进行携带,本例将自动关闭B。
Activity B步骤1:在AndriodManifest.xml中B的intent fliter中增设PICK的action
<activity …… >
<intent-filter>
… …
<data android:scheme="wei" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Activity B步骤2:在B中设置返回值和返回信息,本例返回信息模拟一个NotePad的Uri,并自动关闭B
......
private void testPick(int itemIndex){
//由于intent fliter可以带有多个action,需要先过滤,只有PICK的action才进行返回值操作
if(Intent.ACTION_PICK.equals(this.getIntent().getAction())){
//设置返回值RESULT_OK,并同时通过intent携带信息传递,本例通过data传递Uri
setResult(Activity.RESULT_OK /*返回值*/,
new Intent().setData(getNoteUri(itemIndex))/*携带信息的intent*/);
//自动关闭Activity B,只有B结束时,返回值才传递给A。当然也可以手工结束B,如按系统返回键。
finish();
}
}
private Uri getNoteUri(int itemIndex){
return Uri.parse("content://com.google.provider.NotePad/notes/" + itemIndex);
}
Activity A:
private static final int PICK_REQUEST_CODE = 1000;
private void invokePick(Activity activity){
//设置唤起B的intent,action为PICK,本例中的setData只是为implicit的匹配
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setData(Uri.parse("wei://flowingflying.com/notes"));
//不使用startActivity,因为startActivity类似开启一个modal dialog,无法进行callBack,因此采用startActivityForResult()
activity.startActivityForResult(intent, PICK_REQUEST_CODE /*request code*/);//由于可以PICK方式唤起多个不同的Activity,所有的callBack都触发onActivityResult()方法,采用request code用于进行区分。
}
//下面是重写Activity的onActivityResult()方法,当B结束时被触发,其中第一个参数requestCode为startActivityForResult()中所携带的,可以判断是从那个Activity中返回;第二个参数是B的返回值,有系统值RESULT_OK(-1)、RESULT_CANCELED(1),或者用户自定义值,自定义值必须从RESULT_FIRST_USER(1)开始;第三个参数是B通过intent携带的返回信息
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent outputIntent) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, outputIntent);
//本例只处理Activity B的返回情况,其request code为PICK_REQUEST_CODE){
if(requestCode != PICK_REQUEST_CODE){
Log.d("WEI","Some one else called this. not us");
return;
}
//如果返回值非OK,即RESULT_CANCELED,不处理
if(resultCode != Activity.RESULT_OK){
Log.d("WEI","Result is not OK");
return;
}
//读intent携带的信息
Uri uri = outputIntent.getData();
Log.d("WEI","uri is " + uri.toString());
//根据携带信息进行处理:本例B返回一个NotePad的content Uri,用之打开NotePad。NotePad在模拟器上是没有的,我们可以通过sample进行导入,具体参见本博最后的附
startActivity(new Intent(Intent.ACTION_EDIT,uri));
}
ACTION_GET_CONTENT
ACTION_GET_CONTENT和ACTION_PICK非常相似,ACTION_PICK是给出URI,系统给出一个或者多个匹配的选择给用户,执行后,唤起的Activity返回值给唤起方。 而ACTION_CET_CONTENT不是基于URI,而是根据MIME Type。
本例使用NotePad,请参考本博最后的附:为模拟器安装NotePad。我们先看看sample NotePad的Manifest.xml文件。该文件展现了多个intent-fliter共存的情况,分别针对应用启动,PICK唤起和GET_CONTENT唤起三种情况。
<activity android:name="NotesList" android:label="@string/title_notes_list">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
</activity>
代码编写和PICK相似:
private static final int REQUEST_CODE_GET_CONTENT = 101;
private void invokeGetContent(Activity activity){
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("vnd.android.cursor.item/vnd.google.note"); //GET_CONTENT针对MIME TYPE
activity.startActivityForResult(intent, REQUEST_CODE_GET_CONTENT);
}
@Override //接收Acivity的返回信息
protected void onActivityResult(int requestCode, int resultCode, Intent outputIntent) {
if(resultCode != Activity.RESULT_OK){
Log.d("Wei","Result is not OK");
return;
}
switch(requestCode){
case REQUEST_CODE_PICK:
…… //对ACTION_PICK的处理,参考上一例子
break;
case REQUEST_CODE_GET_CONTENT:
Uri uriContent = outputIntent.getData();
Log.d("Wei","uri is " + uriContent.toString());
startActivity(new Intent(Intent.ACTION_EDIT,uriContent));
break;
default:
showInfo("Some one else called this. not us");
break;
}
}
Pending Intent
Intent可以作为PendingIntent中的一个参数。PengdingIntent,顾名思义,不是马上调用的,可以在某个事件触发后才唤起。例如在通知中使用,得到用户点击通知时才调起,见Android学习笔记(五四):通知Notification(上);又例如警告,如地理位置匹配时调起,见Android学习笔记(五六):位置Location。即时唤起方进程(应用)已经不存在,PendingIntent仍得以很好地保存,很方便进程结束后或其他进程调用。
我们做个小实验,看看PendingIntent如何建立:
public static PendingIntent getActivity (Context context, int requestCode, Intent intent, int flags)
public static PendingIntent getActivities (Context context, int requestCode, Intent[] intents, int flags)
Intent intent = new Intent(this,IntentBasicViewActivity.class); //普通的intent
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(),0,intent,0);
showInfo("intent is " + intent); //showInfo()同时在LogCat和UI窗口中显示
showInfo("pending intent is " + pi);
PendingIntent pi1 = PendingIntent.getActivity(getApplicationContext(),0,intent,0);
showInfo("pending intent is " + pi1);
PendingIntent pi2 = PendingIntent.getActivity(getApplicationContext(),1,intent,0);
showInfo("pending intent is " + pi2);
为何使用getActivity如此奇特的方式。普通的intent可以通过startActivity(intent),startService(intent)和sendBroadcast(intent)来唤起Activity,开启服务和唤起broadcast receiver。由于最终要通过PendingIntent中的intent参数实际去唤起对应的Activity、服务或广播接收器,需要告之系统到底是Activity、服务还是广播。相应的也会有:
getBroadcast(Context context, int requestCode, Intent intent, int flags)
getService(Context context, int requestCode, Intent intent, int flags)
至于为何用getxxxx这种方式,据《Pro Android 4.0》是这样解释:Android会存储PendingIntent,并可以多次重用(具体取决于flag参数的设置),如果需要使用相同的intent,需要获得相同的PendingIntent,所有用get。如果要进行区分,则可设置不同的requestCode。
对于flag参数,有:
【1】FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。
【2】FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null。
【3】FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。
【4】FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras。
【说明】这是其中几个flag的解释,参考自http://blog.csdn.net/hudashi/article/details/7060837,更多的请阅读http://developer.android.com/reference/android/app/PendingIntent.html#FLAG_CANCEL_CURRENT
如果我们获得一个PendingIntent,要唤起当中的Intent,可以使用
pi.send(RESULT_CODE); //PendingInten多个send方法
附:为模拟器安装NotePad
从Android SDK Manager上下载sample代码,然后根据下图的操作将之加入模拟器。
为了在PICK和GET_CONTENT调用中有更好的用户体验,当用户选择了item后,NotePad activity能够自动关闭。我们在NotesList.java中加入以下一行代码:
protected void onListItemClick(ListView l, View v, int position, long id) {
……
// Handles requests for note data
if (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {
// Sets the result to return to the component that called this Activity.
// result contains the new URI
setResult(RESULT_OK, new Intent().setData(uri));
//Wei Add the following: close NoteList automatically
finish(); //wei Add
……
}