Android中隐式Intent以及Intent-filter详解 和匹配规则

时间:2022-07-04 18:28:24
  一、精要概述

摘要 隐式意图:没有明确指定组件名的Intent为隐式意图。 对于隐式意图,Android是怎样寻找到这个最合适的组件呢?记的前面我们在定义活动时,指定了一个intent-filter,Intent Filter(意图过滤器)其实就是用来匹配隐式Intent的,当一个意图对象被一个意图过滤器进

隐式意图:没有明确指定组件名的Intent为隐式意图。

  对于隐式意图,Android是怎样寻找到这个最合适的组件呢?记的前面我们在定 义活动时,指定了一个intent-filter,Intent Filter(意图过滤器)其实就是用来匹配隐式Intent的,当一个意图对象被一个意图过滤器进行匹配测试时,只有三个方面会被参考到:动作、数据 (URI以及数据类型)和类别。

  动作测试(Action test)

  一个意图对象只能指定一个动作名称,而一个过滤器可能列举多个动作名称。如果意图对象或过滤器没有指定任何动作,结果将如下:

  • 如果过滤器没有指定任何动作,那么将阻塞所有的意图,因此所有的意图都会测试失败。没有意图能够通过这个过滤器。

  • 另一方面,只要过滤器包含至少一个动作,一个没有指定动作的意图对象自动通过这个测试

  类别测试(Category test)

  对于一个能够通过类别匹配测试的意图,意图对象中的类别必须匹配过滤器中的类别。这个过滤器可以列举另外的类别,但它不能遗漏在这个意图中的任何类别。

  原则上一个没有类别的意图对象应该总能够通过匹配测试,而不管过滤器里有什么。大 部分情况下这个是对的。但有一个例外,Android把所有传给 startActivity()的隐式意图当作他们包含至少一个类别:"android.intent.category.DEFAULT" (CATEGORY_DEFAULT常量)。因此,想要接收隐式意图的活动必须在它们的意图过滤器中包 含"android.intent.category.DEFAULT"。(带"android.intent.action.MAIN" 和"android.intent.category.LAUNCHER"设置的过滤器是例外)

  数据测试(Data test)

  当一个意图对象中的URI被用来和一个过滤器中的URI比较时,比较的是URI的 各个组成部分。例如,如果过滤器仅指定了一个scheme,所有该scheme的URIs都能够和这个过滤器相匹配;如果过滤器指定了一个scheme、 主机名但没有路经部分,所有具有相同scheme和主机名的 URIs都可以和这个过滤器相匹配,而不管它们的路经;如果过滤器指定了一个scheme、主机名和路经,只有具有相同scheme、主机名和路经的 URIs才可以和这个过滤器相匹配。当然,一个过滤器中的路径规格可以包含通配符,这样只需要部分匹配即可。

 

  数据测试同时比较意图对象和过滤器中指定的URI和数据类型。规则如下:

  a. 一个既不包含URI也不包含数据类型的意图对象仅在过滤器也同样没有指定任何URIs和数据类型的情况下才能通过测试。

  b. 一个包含URI但没有数据类型的意图对象仅在它的URI和一个同样没有指定数据类型的过滤器里的URI匹配时才能通过测试。这通常发生在类似于mailto:和tel:这样的URIs上:它们并不引用实际数据。

  c. 一个包含数据类型但不包含URI的意图对象仅在这个过滤器列举了同样的数据类型而且也没有指定一个URI的情况下才能通过测试。

  d. 一个同时包含URI和数据类型(或者可从URI推断出数据类型)的意图对象可以通过测试,如果它的类型和过滤器中列举的类型相匹配的话。如果它的URI和 这个过滤器中的一个URI相匹配或者它有一个内容content:或者文件file: URI而且这个过滤器没有指定一个URI,那么它也能通过测试。换句话说,一个组件被假定为支持content:和file: 数据如果它的过滤器仅列举了一个数据类型。

二、理论介绍 摘要  对于明确指出了目标组件名称的Intent,我们称之为“显式Intent”。对于没有明确指出目标组件名称的Intent,则称之为“隐式Intent”。Android系统使用Intent Filter 来寻找与隐式Intent相关的对象. 隐式Intent往往用于不同应用程序之间的使用。由于开发人员往

  对于明确指出了目标组件名称的Intent,我们称之为“显式Intent”。对于没有明确指出目标组件名称的Intent,则称之为“隐式Intent”。Android系统使用Intent Filter 来寻找与隐式Intent相关的对象.

        隐式Intent往往用于不同应用程序之间的使用。由于开发人员往往并不清楚别的应用程序的组件名称,因此,隐式Intent便尤为重要。

        对于隐式Intent,由于没有明确的目标组件名称,所以必须由Android系统帮助应用程序寻找与Intent请求意图最匹配的组件。具体的选择方法是:Android将Intent的请求内容和一个叫做intent-filter比较,Intent Filter中包含系统中所有可能的待选组件。如果Intent Filter中某一组件匹配隐式Intent请求的内容,那么Android就选择该组件作为该隐式Intent的目标组件.

        Android 如何知道应用程序能够处理某种类型的Intent 请求呢?这需要应用程序在AndroidManifest.xml中声明自己所含组件的过滤器(即可以匹配哪些Intent请求)。一个没有声明Intent Filter的组件只能响应指明自己名字的显式Intent请求,而无法响应隐式Intent请求。在配置文件中,常见的intent-filter声明如下:

[java] view plain copy
  1. <intent-filter android:label="@string/resolve_edit">   
  2.         
  3.     <action android:name="android.intent.action.VIEW"/>    
  4.         
  5.     <action android:name="android.intent.action.EDIT"/>    
  6.         
  7.     <action android:name="com.android.notes.action.EDIT_NOTE"/>    
  8.         
  9.     <category android:name="android.intent.category.DEFAULT"/>   
  10.         
  11.     <data android:mimeType="vnd.android.cursor.item/vnd.google.note"/>    
  12.         
  13. </intent-filter>  


     而一个声明了intent-filter的组件既可以响应显式Intent请求,也可以响应隐式Intent请求。在通过和intent-filter比较来解析隐式Intent请求时,Android将以下三个因素作为选择的参考标准。

Action

Data

Category

        而Entra和Flag在解析收到Intent时是并不起作用的。

Intent Filter

  应用程序的组件为了告诉Android自己能响应、处理哪些隐式Intent请求,可以声明一个甚至多个intent-filter。每个Intent Filter描述该组件所能响应Intent请求的能力——组件希望接收什么类型的请求行为,什么类型的请求数据。

  如何为组件声明自己的IntentFilter? 常见的方法是在AndroidManifest.xml文件中用属性<Intent-Filter>描述组件的Intent Filter。

  前面我们提到,隐式Intent和IntentFilter进行比较时的三要素是Intent的动作、数据以及类别。实际上,一个隐式Intent请求要能够传递给目标组件,必要通过这三个方面的检查。如果任何一方面不匹配,Android都不会将该隐式Intent传递给目标组件。接下来我们讲解这三方面检查的具体规则。

 

1.动作测试

        <intent-filter>元素中可以包括子元素<action>,比如:

[java] view plain copy
  1. <intent-filter>   
  2.         
  3. <actionandroid:name="com.example.project.SHOW_CURRENT"/>    
  4.         
  5. <actionandroid:name="com.example.project.SHOW_RECENT"/>    
  6.         
  7. <actionandroid:name="com.example.project.SHOW_PENDING"/>    
  8.         
  9. </intent-filter>  


  一条<intent-filter>元素至少应该包含一个<action>,否则任何Intent请求都不能和该<intent-filter>匹配。

  如果Intent请求的Action和<intent-filter>中个某一条<action>匹配,那么该Intent就通过了这条<intent-filter>的动作测试。

  如果Intent请求或<intent-filter>中没有说明具体的Action类型,那么会出现下面两种情况。

  (1) 如果<intent-filter>中没有包含任何Action类型,那么无论什么Intent请求都无法和这条<intent-filter>匹配;

  (2) 反之,如果Intent请求中没有设定Action类型,那么只要<intent-filter>中包含有Action类型,这个Intent请求就将顺利地通过<intent-filter>的行为测试。

2.类别测试

        <intent-filter>元素可以包含<category>子元素,比如:

[java] view plain copy
  1. <intent-filter. . . >    
  2.         
  3.      <categoryandroid:name="android.Intent.Category.DEFAULT"/>    
  4.         
  5.      <categoryandroid:name="android.Intent.Category.BROWSABLE"/>    
  6.         
  7. </intent-filter>  

        只有当Intent请求中所有的Category与组件中某一个Intent-filter的<category>完全匹配时,才会让该Intent请求通过测试,Intent-filter中多余的<category>声明并不会导致匹配失败。

       android.intent.category.DEFAULT的作用

        每一个通过 startActivity()方法发出的隐式 Intent 都至少有一个 category,就是 "android.intent.category.DEFAULT",所以只要是想接收一个隐式Intent 的 Activity 都应该包括 "android.intent.category.DEFAULT" category,不然将导致 Intent匹配失败。

3.数据测试

        数据在<intent-filter>中的描述如下:

[java] view plain copy
  1. <intent-filter. . . >    
  2.        
  3. <data android:type="video/mpeg"android:scheme="http". . . />    
  4.        
  5. <data android:type="audio/mpeg"android:scheme="http". . . />    
  6.        
  7. </intent-filter>  


     <data>元素指定了希望接受的Intent请求的数据URI和数据类型,URI被分成三部分来进行匹配:scheme、authority和path。其中,用setData()设定的Intent请求的URI数据类型和scheme必须与Intent-filter中所指定的一致。若Intent-filter中还指定了authority或path,它们也需要相匹配才会通过测试。

        每个数据<data>元素可以指定一个URI和一个数据类型(MIME媒体类型)。有一些单独的属性-模式,主机,端口和路径-URI的每个部分:

[java] view plain copy
  1. scheme://host:port/path   
  2.         
  3. content://com.example.project:200/folder/subfolder/etc  

        模式是"内容",主机是"com.example.project",端口是"200",路经是"folder/subfolder/etc"。主机和端口一起组成URI鉴权(authority);如果未指定主机,端口会被忽略。

        这些属性都是可选的,但彼此有依赖关系:一个授权要有意义,必须指定一个模式。一个路经要有意义,必须同时指定模式和鉴权。

        当一个意图对象中的URI被用来和一个过滤器中的URI规格比较时,它实际上比较的是上面提到的URI的各个部分。比如,如果过滤器仅指定了一个模式,所有那个模式的URIs和这个过滤器相匹配;如果过滤器指定了一个模式、鉴权但没有路经,所有相同模式和鉴权的URIs可以匹配上,而不管它们的路经;如果过滤器指定了一个模式、鉴权和路经,只有相同模式、鉴权和路经的URIs可以匹配上。当然,一个过滤器中的路径规格可以包含通配符,这样只需要部分匹配即可。

        数据<data>元素的类型属性指定了数据的MIME类型。这在过滤器里比在URI里更为常见。意图对象和过滤器都可以使用一个"*"通配符指定子类型字段-比如,"text/*"或者"audio/*"-指示任何匹配的子类型。

        数据测试同时比较意图对象和过滤器中指定的URI和数据类型。规则如下:

        a. 一个既不包含URI也不包含数据类型的意图对象仅在过滤器也同样没有指定任何URIs和数据类型的情况下才能通过测试。

        b .一个包含URI但没有数据类型的意图对象仅在它的URI和一个同样没有指定数据类型的过滤器里的URI匹配时才能通过测试。这通常发生在类似于mailto:和tel:这样的URIs上:它们并不引用实际数据。

        c. 一个包含数据类型但不包含URI的意图对象仅在这个Intent-filter列举了同样的数据类型而且也没有指定一个URI的情况下才能通过测试。

        d. 一个同时包含URI和数据类型(或者可从URI推断出数据类型)的意图对象可以通过测试,如果它的类型和过滤器中列举的类型相匹配的话。如果它的URI和这个过滤器中的一个URI相匹配或者它有一个内容content:或者文件file: URI而且这个过滤器没有指定一个URI,那么它也能通过测试。换句话说,一个组件被假定为支持content:和file: 数据如果它的过滤器仅列举了一个数据类型。(这个是网上copy过来,)

    PS:如果一个意图可以通过不止一个活动或服务的过滤器,用户可能会被询问要激活那个组件。这个倒是经常遇到。例如,当你手机上安装多个浏览器的时候,调用浏览器,回弹出对话框,询问你到底启动哪一个应用。如果没有发现目标对象将会出现异常。

三、实例教程

当自己创建日志后,在NotesList中会显示出来。当点击这个ListView中的某一个item时,响应的代码为:

[java] view plain copy
  1. @Override   
  2. protected void onListItemClick(ListView l, View v, int position, long id) {    
  3.     Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), id);    
  4.     Log.e(TAG," onListItemClick() called,noteUri = " + noteUri);    
  5.            
  6.     String action = getIntent().getAction();    
  7.     if(Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)) {   
  8.         // The caller is waiting for us to return a note selected by    
  9.         // the user.  The have clicked on one, so return it now.    
  10.         setResult(RESULT_OK,newIntent().setData(noteUri));    
  11.     }else{    
  12.         // Launch activity to view/edit the currently selected item    
  13.         startActivity(newIntent(Intent.ACTION_EDIT, noteUri));    
  14.     }   
  15. }  


 其中,代码startActivity(new Intent(Intent.ACTION_EDIT, noteUri));就是启动一个隐式的Activity。此时,Android系统就出来接管这个隐式的Intent,它会根据intent-filter来匹配将要启动的组件。显然,此时的Intent中的action 为:android.intent.action.EDIT。此时,从AndroidManifest.xml文件中进行分析,发现文件中有两个组件intent-filter中均包含<action android:name="android.intent.action.EDIT" />

[java] view plain copy
  1. <activity android:name="NotesList"  
  2.       android:label="@string/title_notes_list">  
  3.       <intent-filter>  
  4.           <action android:name="android.intent.action.MAIN" />  
  5.           <category android:name="android.intent.category.LAUNCHER" />  
  6.       </intent-filter>  
  7.       <intent-filter>  
  8.           <action android:name="android.intent.action.VIEW" />  
  9.          <action android:name="android.intent.action.EDIT" />  
  10.           <action android:name="android.intent.action.PICK" />  
  11.           <category android:name="android.intent.category.DEFAULT" />  
  12.           <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />  
  13.       </intent-filter>  
  14.     ... ... ... ...  
  15.   </activity>  
  16.   
  17.  <activity android:name="NoteEditor"  
  18.       android:theme="@android:style/Theme.Light"  
  19.       android:configChanges="keyboardHidden|orientation">  
  20.       <!-- This filter says that we can view or edit the data of  
  21.            a single note -->  
  22.       <intent-filter android:label="@string/resolve_edit">  
  23.           <action android:name="android.intent.action.VIEW" />  
  24.          <action android:name="android.intent.action.EDIT" />  
  25.           <action android:name="com.android.notes.action.EDIT_NOTE" />  
  26.           <category android:name="android.intent.category.DEFAULT" />  
  27.           <data android:mimeType="vnd.android.cursor.item/vnd.google.note" />  
  28.       </intent-filter>  
  29.   ... ... ... ...  
  30.   </activity>  


为神马NotesList组件中的intent-filter也包涵 一个<action android:name="android.intent.action.EDIT" />?估计是Google的程序员在copy代码时,出现的差错。这个action本没有作用。

那么NoteEditor组件是如何正确的被启动的呢??

在上一篇博文里面谈到,在通过和intent-filter比较来解析隐式Intent请求时,Android将以下三个因素作为选择的参考标准。 Action,Data,Category。当Action和Category一致时,只能从Date来进行区分咯!

于是,intent-filter中的<data android:mimeType="vnd.android.cursor.item/vnd.google.note" /><data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />开始浮出水面了。这两个date是神马区别呢?

在NotesList.Java中的代码,添加Log信息。

[java] view plain copy
  1. @Override  
  2. protected void onListItemClick(ListView l, View v, int position, long id) {  
  3. Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), id);  
  4. Log.e(TAG," onListItemClick() called,noteUri = " + noteUri);  
  5. ... ... ... ...  


打印的Log信息如下:

NotesList(4103): onListItemClick() called,noteUri = content://com.example.notepad.provider.NotePad/notes/2

Android系统获取到这个Intent后,去掉开始的content:标识和后面的资源路径(/notes)以及资源ID(2)得到com.google.provider.NotePad,然后就加载这个content provider。紧接着,就开始通过NotePadProvider.java中的getType(Uri),返回数据的MIME类型,其返回值为vnd.android.cursor.item/vnd.google.note,这个就与组件NoteEditor匹配上了。