Intent属性
- Component Name
Intent可以被分为两类,即显性和隐性。如果我们在Intent中特别指定了目标方的“Component Name”,比如:“com.example.project.HelloActivity”;同时指定它所在的PackageName,如“com.example.project”,那么系统就会直接将此Intent发往这个特定的应用,而不需要做额外的匹配工作。
如果一个应用程序通过这种方式显性调用另一个应用进程,多半是这两个应用程序同属于一家研发公司,或者是同一进程中的两个组件。
- Category
如果说上面的Component Name是某人的名字,那么Category就好比国籍。Android系统中已经预设了一些“国家”,我们摘录其中的部分核心元素。另外,由于Intent的所有属性值实际上都只是一串字符,因而是可以自定义的。
- Action
表明要做什么,或者什么事件发生了(常用于广播的情况。比如设备开机时会有系统广播发出,如果应用程序希望实现开机自启动,就可以监听这个广播)。和Category一样,用户也可以自定义一项唯一的Action,如“com.ThinkingInAndroid.action.example”。Android系统
中预定义的部分常见Action。
- data
如果上面的Action中表明了某人去公安局出入境处“办理签证”的“动作”,那么这里的Data就作为“签证”业务的补充材料——比如这个人的名字、身份证件等。所以,Action理论上是围绕Data提供的数据来开展业务的。当然也有不需要Data补充信息的情况,如在ACTION_CALL的情况下,电话号码是必须作为Data来传递的;而针对Broadcast(如ACTION_SCREEN_ON)组件的Action,它们本身就蕴含了足够的信息,因而不需要Data的支持。
- Extras
Extras可以理解为Extra Data,它是对上面Data属性的补充。不过两者在数据的格式上有明显区别。Data采用了类似scheme://uri的表达方式;而Extras则是一种键值对实现。它们在表达不同场景的数据时有各自的优势,使用者应该“具体问题具体分析”。发送方通过一系列putXXX()方法将键值对存入Intent中,然后接收方就可以用相对应的getXXX()来获取到这些Extra数据。这些方法的内部会维护一个Bundle对象来保证进程间数据的准确传输。
- Flags
Flags和Activity中的LaunchMode功能基本相同,它规定了系统如何去启动一个Activity(比如指定即将启动的Activity应该属于哪一个Task)。举个例子,如果一开始Task栈从下往上的顺序是A-B-C,随后C通过带有Flag为FLAG_ACTIVITY_CLEAR_TOP的Intent来启动另一个Activity。如果该Activity在栈中已经存在,比如说是B,那么启动后的Task栈就变成了A-B;否则该Activity直接入栈,而不会先清理栈顶。
Intent的匹配规则
Intent是和Intent-filter配套使用的。具体而言,Intent-filter是每个组件的属性标签,它们在AndroidManifest.xml声明时就已经“贴上”了。而Intent则是程序运行过程中产生的实时“需求”。系统接收到这些请求后与现有的Intent-filter进行匹配,然后选择最合适的组件元素以响应。
以“婚介所”为例子,Intent代表了女生的择偶意愿,而Intent-filter则是众男士的属性描述——年龄、长相、收入等。
图1描述了广播BroadcastReceiver的匹配流程,其他组件也类似。
-
组件注册
当应用程序安装到系统中时,Android会通过扫描它的AndroidManifest文件来得到一系列信息——这其中就包括了它的;而且系统还会根据组件的不同进行相应的细化归类。比如Activity和Service,它们响应Intent的场合是不同的(前者是startActivity,后者则是startService)。换句话说,当某人调用了startActivity后,显然系统只需在所有Activity中做匹配即可,而
不应该再去考虑Service中的Intent-filter。 -
发起方主动向系统提供Intent
这一步是在程序运行过程中发生的,此时系统已经掌握了所有组
件的信息。发起方根据自己的需求填写Intent,并按
照目标方是Activity,Service还是BroadcastReceiver来调用对应的
函数。如下所示:
Activity→对应startActivity();
Service →对应startService();
BroadcastReceiver→对应sendBroadcast();
系统会严格区分对待这些组件分类。所以利用
startActivity()是绝对不可能启动Service的——即便Intent和
intent-filter能成功匹配。
- 系统将Intent和对应组件类型(Activity,Service等)里所有的intent-filter进行匹配,以寻找最佳的结果。
隐性Intent的匹配规则
影响Intent匹配规则的只有3个关键因素,即:
- Category
- Action
- Data (URI和数据类型都要同时匹配)
而其余两个属性Extras和Flags则只有在选中的组件运行后才能起作用。
一个组件可以同时声明多个intent-filter。而在匹配过程中,只要它包含的任何一个filter通过了测试,它就会被选中。
在匹配测试中,系统遵循“子集”的概念。
换句话说,Intent中的3个关键因素至少要是该组件所包含的其中一个
intent-filter的子集(Data有点特殊,下面有详细解释),才能通过
验证,如图2所示。
-
每个Component(Activity、Service、BroadcastReceiver)都可以有若干个intent-filter。
-
每个filter里的上述3种属性都可以不是唯一的。
-
匹配时,Intent中的3种属性都需要通过测试。
Category匹配规则
-
如果一个Intent中指定了1到N个category项
则这个intent对象中的1到N个category,在 xxx 中,需要全有,一一匹配则通过,否则失败;注意:
<intent-filter >
中的category可以比intent对象中的category多,只要intent中的全都匹配到,就可以通过 -
如果一个Intent中没有指定任何category项
系统会自动为其添加一个“android.intent.category.DEFAULT”。所以,在这种情况下,如果你想让你的activity接收这些intent对象,就必须在其<intent-filter>
中添加<category android:name="android.intent.category.DEFAULT" />
注意:如果
<intent-filter>
里已经带有“android.intent.action.MAIN”和“android.intent.category.LAUNCHER”,就可以不用再另外添加DEFAULT了,这是一个例外。
Action 匹配规则
-
如果一个Intent中指定了action项
- 如果这个action至少匹配
<intent-filter> xxx </intent-filter>
中的一个action,则匹配通过,否则失败; - 如果
<intent-filter> xxx </intent-filter>
中没有任何action项,则直接匹配失败;
- 如果这个action至少匹配
-
如果一个Intent中没有指定任何action项
- 如果
<intent-filter> xxx </intent-filter>
中至少有一个action,则匹配通过; - 如果
<intent-filter> xxx </intent-filter>
中没有任何action项,则直接匹配失败;
- 如果
Data 匹配规则
每个<data>
项目都可以指定一个URI和一个MimeType(数据类型)。
<intent-filter><data android:mimeType="video/mpeg" android:scheme="http" ... /><data android:mimeType="audio/mpeg" android:scheme="http" ... />...
</intent-filter>
MimeType
MIMEType的目的很简单,就是指明某段数据是什么格式类型,以保证程序能正确解析处理。比如电子邮件中的附件,如果不特别说明,接收方客户端就没办法知道它们是图片、文本还是应用程序。这样的结果就是用户找不到合适的途径来对附件进行解析。国际标准中已经预设了很多常见的文件类型,举例如下。
注意:type和subtype中以x-开头的属于非标准格式(未向IANA注
册);而subtype中以vnd开头的则是厂商自定义的(vendorspecific)。
例如:
- application/vnd.oasis.opendocument.text ##OpenDocument文本
- application/vnd.ms-excel ##MicrosoftExcel文件
- application/x-latex ##LaTeX文件
- audio/x-caf ##Apple公司的CAF音频文件
URI
每个URI都可包含scheme, host, port, path
这几属性,其格式为
<scheme>://<host>:<port>/<path>
-
举例:content://com.example.project:200/folder/subfolder/etc
-
<scheme>
为 content -
<host>
为com.example.project -
<port>
为200 -
<path>
为 folder/subfolder/etc
-
其中,scheme不仅包括了传统的“http”等网络协议,还有“content”来表示本地ContentProvider所提供的数据。
“host”是主机的名称,“port”指明通信的端口,它们统称为“Authority”;而且如果host不存在,后面的端口号也会被忽略。
最后一部分是文件的路径,它是该资源在host中的具体位置。
-
注意:每个
<data>
项目中的URI,scheme, host, port, path这几个属性,不是全都需要指定,但有一个线性的依赖要求:- 如果
<scheme>
没有被指定,则后面的<host>
被忽略。 - 如果
<host>
没有被指定,则后面的<port>
被忽略。 - 如果
<scheme>
和<host>
都没有被指定,则后面的<path>
被忽略。
- 如果
匹配规则
只有intent-filter
中存在的那部分属性(比如某filter中只指定了mimeType,那么只匹配mimeType),才需要进行匹配。
(1)Intent中既没有指定数据类型,也没有填写URI
在这种情况下,只有intent-filter中也同样没有指定数据和URI,才可能
通过测试。
(2)Intent中没有指定数据类型(且无法从URI中推断出),但有
URI。
数据类型有可能从URI中推断出来——如果是就属于第四种情况。前面已经说过,“只有filter中存在的那部分属性,才需要进行匹配”,因而这种情况下仅当其 URI 与的 URI 格式匹配、且同样未指定 MIME 类型时,才可能通过测试。
(3) Intent中只指定了数据类型,没有URI。
这种情况下仅当列出相同的 MIME 类型且未指定 URI 格式时,才可能通过测试。
(4)Intent中同时指定了数据类型(或可以从URI推断出)和URI。
这时数据类型和URI都必须通过测试。具体来说,仅当 MIME 类型与
<intent-filter>
中列出的类型匹配,同时 Intent 的 URI 与<intent-filter>
中的 URI也要 匹配,或者如果 Intent 具有 content: 或 file: URI且<intent-filter>
未指定 URI,则 Intent 会通过测试的 URI 部分。
URI匹配规则
- 如果
<intent-filter>
中的这个URI只有一个<scheme>
,则Intent对象中拥有相同<scheme>
的URI,全部通过。 - 如果
<intent-filter>
中的这个URI指定了<scheme>
和<authority>
,但是没有指定<path>
,则Intent对象中拥有相同<scheme>
和<authority>
的URI,不管其<path>
是什么值,全部通过。 - 如果
<intent-filter>
中的这个URI指定了<scheme>
、<authority>
和<path>
,则Intent对象中拥有相同<scheme>
、<authority>
和<path>
的URI,才能通过。 - 注意:
<path>
中可以使用 *通配符
实例
Note Pad Example 记事本
在其manifest文件中, 记事本程序定义了三个activity, 每个有至少一个intent filter. 它还定义了一个content provider来管理笔记数据. manifest 文件如下:
<manifest XMLns:android="http://schemas.android.com/apk/res/android"package="com.example.android.notepad"><application android:icon="@drawable/app_notes"android:label="@string/app_name" ><provider android:name="NotePadProvider"android:authoritIEs="com.google.provider.NotePad" /><activity android:name="NotesList" android:label="@string/title_notes_list"><intent-filter> // NotesList-filter-1<action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><intent-filter> //NotesList-filter-2<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> //NotesList-filter-3<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><activity android:name="NoteEditor"android:theme="@android:style/Theme.Light"android:label="@string/title_note" ><intent-filter android:label="@string/resolve_edit"> //NoteEditor -filter-1<action android:name="android.intent.action.VIEW" /><action android:name="android.intent.action.EDIT" /><action android:name="com.android.notepad.action.EDIT_NOTE" /><category android:name="android.intent.category.DEFAULT" /><data android:mimeType="vnd.android.cursor.item/vnd.google.note" /></intent-filter><intent-filter> //NoteEditor -filter-2<action android:name="android.intent.action.INSERT" /><category android:name="android.intent.category.DEFAULT" /><data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /></intent-filter></activity><activity android:name="TitleEditor"android:label="@string/title_edit_title"android:theme="@android:style/Theme.Dialog"><intent-filter android:label="@string/resolve_title"> //TitleEditor -filter-1<action android:name="com.android.notepad.action.EDIT_TITLE" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.ALTERNATIVE" /><category android:name="android.intent.category.SELECTED_ALTERNATIVE" /><data android:mimeType="vnd.android.cursor.item/vnd.google.note" /></intent-filter></activity></application>
</manifest>
根据上面的AndroidManifest文件,可以看到NotePad共包含了3种Activity,即NotesList(用于显示已经保存的文章),NoteEditor(用于编辑文章)和TitleEditor(用于编辑文章的标题)。
下面我们通过列举几个不同的Intent来分析系统的匹配情况。
- Intent 1
action: android.intent.action.MAIN
category: android.intent.category.LAUNCHER
Category测试:根据子集原则,只有NotesList-filter-1通过了
检查。
Action测试:依据上面所分析的Action测试第3点,NotesListfilter-1通过了检查。
Data测试:属于第一种情况,即intent里既没有指定类型,也无URI。而另外,filter也是同样的情况,因此根据“只有filter里有的那部分属性才需要检查”的原则,NotesList-filter-1最终通过了匹配。这个Intent1将对应NoteList。
- Intent 2
action: android.intent.action.VIEW
data: content://com.google.provider.NotePad/notes
Category测试:Intent中没有指定Category,系统会自动为其加上DEFAULT值。因为所有filter都写上了这个默认值,因而全部通过测试。
Action测试:只有NotesList-filter-2和NoteEditor-filter-1通过测试。
Data测试:表面上属于第二种情况,即只指定了URI而没有类型。但实际上从这个例子中的content可以推断出type,因而属于第四种情况。推断出的类型为"vnd.android.cursor.dir/vnd.google.note"(可以参见下一小节对推断过程的源码解析),因而最终通过测试的是NotesList-filter-2。
- Intent3
action: android.intent.action.GET_CONTENT
data type: vnd.android.cursor.item/vnd.google.note
Category测试:Intent中没有指定Category,系统会自动为其加上DEFAULT值。因为所有filter都写上了这个默认值,因而全部通过测试。
Action测试:只有NotesList-filter-3通过测试。
Data测试:只有Type,而没有URI,属于第三种情况。因为NotesList-filter-3也是同样的情况,而且它们的类型也是匹配的,所以最终通过测试。
- Intent4
action: android.intent.action.INSERT
data: content://com.google.provider.NotePad/notes
Category测试:同Intent2。
Action测试:只有NotesEditor-filter-2符合要求。
Data测试:同Intent2。
- Intent5
action: com.android.notepad.action.EDIT_TITLE
data: content://com.google.provider.NotePad/notes/ID
Category测试:同Intent2。
Action测试:只有TitleEditor-filter-1符合要求。
Data测试:同样可以推断出类型,
即"vnd.android.cursor.item/vnd.google.note",因而上面的
TitleEditor-filter-1最终通过测试。
类型推断
在前一节中,我们提到当Intent中只包含URI时,是有可能推断出
Type的。那么这个过程是在什么时候做的,为什么NotePad中的
“content://com.google.provider.NotePad/notes”最终可以得出
Type=“vnd.android.cursor.dir/vnd.google.note”?
实际上对Type的推断在startActivity起始就执行了,具体而言是
在execStartActivity中。
/*frameworks/base/core/java/android/content/Intent.java*/
public String resolveTypeIfNeeded(ContentResolver resolver)
{
if (mComponent != null) {
return mType; /*如果已经指定了Component Name,那就没必要
再解析类型了*/
}return resolveType(resolver);
}
resolveTypeIfNeeded——通过函数
名也可以看出,它负责从Intent中解析出相应的类型。
函数resolveTypeIfNeeded先判断ComponentName是否为空,是就
直接返回结果,否则继续调用如下方法:
public String resolveType(ContentResolver resolver) {if (mType != null) {return mType; //已经指定了类型,当然不需要再从URI中推断了}if (mData != null) {if ("content".equals(mData.getScheme())) {/*URI中的scheme必须是content才能
推断出类型。言下之意,像“http”这种网络协议是没有办法推测的*/return resolver.getType(mData); /*利用ContentResolver来解析*/}}return null;
}
由此可见,最终的类型推断是由ContentResolver来完成的。即:
public final String getType(Uri url) {IContentProvider provider =acquireExistingProvider(url); /*需要找到一个ContentProvider来完成解析。这给了我们一个提
示,getType有可能是每个Provider自己来完成的,而不是系统统一处理的*/if (provider != null) {//provider存在,直接由它完成解析try {return provider.getType(url);} catch (RemoteException e) {//异常处理}}if (!SCHEME_CONTENT.equals(url.getScheme())) {//又判断了一次是否为“content”return null;}try {String type = ActivityManagerNative.getDefault().getProviderMimeType(url);return type;} …}
如果已经有现成的Provider,那么直接由它来解析类型;如果没
有可用的Provider,那么就要先动用AMS来找到相对应的Provider。这
和startActivity中利用Intent来找到Activity类似——这里是通过
URI来匹配Content Provider。因为Intent中的URI是“content://com.google.provider. NotePad/notes”,Authority是
com.google.provider.NotePad,所以最终会匹配到NotePad这个
Provider。
所以getType的核心步骤有两个。
(1)根据URI在AMS中找到对应的Content Provider,在这个例子中也就是NotePadProvider。
(2)根据provider中的getType()函数来解析类型。
因而可以肯定的是,每个provider都必须实现getType()方法;并且这
个接口还应该是抽象的,验证如下:
public abstract class ContentProvider implements
ComponentCallbacks2 {
…
public abstract String getType(Uri uri); //确实是抽象接口
…
}
最后来看看NotePadProvider是如何解析类型的。
@Overridepublic String getType(Uri uri) {switch (sUriMatcher.match(uri)) {case NOTES:case LIVE_FOLDER_NOTES:return NotePad.Notes.CONTENT_TYPE;case NOTE_ID:return NotePad.Notes.CONTENT_ITEM_TYPE;default:throw new IllegalArgumentException("Unknown URI "+ uri);}}
UriMatcher一共添加了3种模式,
当uri="content://com.google.provider.NotePad/notes"时,匹配的类
型是CONTENT_TYPE =“vnd.android.cursor.dir/vnd.google.note”。
根据之前对MIME类型的讲解,vnd表示用户自定义的类型。
当uri="content://com.google.provider.NotePad/notes/ID"时,匹配
的类型是:CONTENT_ITEM_TYPE=“vnd.android.cursor.item/vnd.google.note”。