解析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 
    …… 
}

相关链接: 我的Android开发相关文章

转自http://blog.csdn.net/flowingflying/article/details/9384389