原文链接:http://developer.android.com/guide/topics/intents/intents-filters.html
原编辑者:我是一棵菠菜
现编辑者:Snowxwyo 2012年7月17日 (二) 16:00 (CST)
Android中的三个核心组件activities,services,broadcast receivers是通过Intent彼此联系、触发的。Intent是同一个或不同的应用中的组件之间的消息传递的媒介。Intent本身是一种‘被动’的数据结构,它抽象地描述了即将被执行的动作又或是在广播中已经发生的并且正在被通知的某个事件的描述。下面列出几种为每一种组件传递Intent的独立机制:
在以上的每一种情况中,Android系统都会找到合适的activity,service或者一套的broadcast reicervers去响应Intent,必要的时候也可以通过Intent初始化他们。广播事件中的Intent只是传递给brodcast receivers.startActivity()只是把Intent传递给activity,而不是service或是broadcast receiver,以此类推。以上我们知道这些传递消息的系统是没有任何重叠的。
本文档从描述Intent本身开始。接下来将描述Android用来匹配Intent和组件的规则-他是如何解决哪一个组件应该接受哪一个Intent的事情。因为Intent并不明确地指定目标组件,所以传递的过程要通过intent filters的过滤以便传递给那些潜在的目标中合适的目标。
Intent对象-Intent Objects
Intent本身是一个消息的集合。他包含那些传递给接收Intent的组件的信息(such as the action to be taken and the data to act on)和传递给Android系统的信息(例如应该绑定到Intent上的组件的类别和如何启动目标activity的指南)主要的,他包含了以下几点:
组件名-Component name
组件的名字是应该可以处理Intent,为其指明目标的。这一段所描述的就是关于ComponentName的--他是目标组件的类名的联合体(例如"com.example.project")。组件名字中所包含的包名的部分不需要必须与manifest文件中的包名相匹配。
组件的名字是非强制性的。如果他是固定的,那么Intent就会被传递给指定名字的类的实例,如果他不是固定的,那么Android就会通过Intent中的其他信息找到合适的目标--可以查看本文档以后���提到的#Intent解决方法-Intent Resolution|Intent解决方法-Intent Resolution。
组件名字可以通过setComponent()), setClass())或setClassName())来设置,通过getComponent()来读取。
动作-Action
一个字符串命名了将要被执行的动作,或在广播intents事件中,已经发生并被报告的动作。Intent类定义了许多动作常量,包括如下:
你可以访问Intent|Intent类的定义查看一系列的代表一般行为动作的常量。其余行为动作的定义可以在Android API文档中的其他地方找到。你也可以在应用中自定义这样的常量串,这些常量要以包名作为���缀,例如"com.example.project.SHOW_COLOR".
action的名字能够很好的说明intent有着怎样的机构--特别是#数据-Data|数据-Data和#Extras|Extras--就像方法的名字决定了参数和返回值。所以,使用一个明确的action的名字是一个很好的主意。另外,要为你的Intent定义一个完整的协议,而不是孤立的定义action。
我们用setAction())来设置Intent中的action并用getAction())来读取。
数据-Data
数据运行的URI和其MIME类型。不同的action被配与不同的data说明。例如,如果是ACTION_CALL,那么他的data就是号码的URI--ACTION_VIEW 并且data是http: URI,那么接收Intent的activity将下载并显示URI所指向的内容。
当为某个组件匹配一个可以处理数据的Intent的时候,通常除了要了解Data的URI以外,重要的是要知道Data的类型(MIME type)。例如,一个可以展示图片的组件不应该被调用来播放音频。
在很多情况下,Data的类型可以从URI中推测出来,特别是URI所展示的内容:指出了Data被用在什么位置及被哪种content provider控制(参考separate discussion on content providers)。但是Data的类型也可以在Intent中明确的设定。setData())方法设置Data的URI,setType())设置Data的类型(MIME type),setDataAndType())两者一起设置,getData())读取URI,getType())读取类型
分类-Category
Category是这样一个String:他包含了需要处理Intent的组件的种类的信息。很多Category的描述能够放在Intent里。就像Action那样,Intent也定义了一些Category常量,如下图表
参考Intent可以查看全部Category的列表
Extras是传递给目标组件的键值对信息。就像一些action匹配着特别的data uri,一些action匹配着特别的Extras。例如"time-zone"指示新时区的信息,"state"标识耳机设备是否插好的信息,除此还有SHOW_COLOR的action,颜色值将被设置在一个键-值对信息中。
Intent有一系列的get..()方法来取出数值。这些方法相对应的存在于Bundle类中。事实上,Extras也可以使用putExtras())和getExtras())来操作数据。
标志位-Flags
::Flags有着很多种类。很多用来通知Android System如何运行一个activity(例如某个activity应该属于哪个任务)和运行以后如何处理(例如,flag是否属于当前活动activity)。所有这些flag都是在Intent中定义的。
Android system 和平台本身的应用会使用Intent发送系统本身的广播并且激活系统定义的组件。如何构造一个Intent并且激活一个系统组件,请参考list of intents。
Intent解决方法-Intent Resolution
Intent可以被分为两组:
Android把explicit intent传递给指定的目标类。在Intent中没有什么比指定目标组件的名字更重要的了,因为它决定了是哪个组件来接收intent。
对于implicit intent就要使用另一套策略了。在没有指定目标类的情况下,Android System必须找到最合适的组件去处理intent--单个的activity或者是service又或者是一套的broadcast receivers。为了实现以上,Android会把intent的内容拿来和��可能合适的组件相关联的intent过滤器和结构相比较。过滤器对外声明了组件的作用并且限定了组件做能处理的intent的界限。intent过滤器使组件可以接受implicit intents成为了可能。如果一个组件没有intent过滤器,那么他只能接收explicit intent。拥有intent过滤器的组件可以接收两种intent。
Intent过滤器要对intent的三个内容进行校验
- data(both URI and data type)
Extra和Flag这一方面并不发挥作用。
Intent过滤器-Intent filters
activity,service 和 broadcast receiver为了通知系统他们可以处理哪些不为五隐含的intent,通常他们都有一个或是更多的intent过滤器。每个过滤器描述了组件的一个能力也限定一些组件可以接收的intent.实际上过滤器是筛进组件想要的intent,而不是筛出不需要的intent--仅仅是不需要的隐含的intent。显性的intent总是被传递给指定的目标,无论过滤器里设置了什么条件。
一个组件会把过滤器们分开,让他们各自去做自己的事情。例如,NoteEditor activity拥有两个过滤器,一个是为了启动用于查看和编辑的note,另一个是为了启动一个新的,空白的note用于填写和保存。(所有的Note Pad's过滤器在#记事本案例-Note Pad Example|记事本案例-Note Pad Example都有描述)。
过滤器和安全-Filters and security
一个Intent过滤器的安全性不可靠。当它打开一个组件准备接收确定类型的隐含intents时,它并不能阻止目标组件中的显性intents。即使一个过滤器阻止了一个组件被要求处理的确定动作和数据源,别人依然可以使用不同的动作和数据源绑定到一个显示的intent上,并将其命名为与目标组件相同的名字。
一个Intent过滤器就是IntentFilter类的一个实例。然而,由于Android System必须在加载组件之前知道这个组件的功能,所以Intent过滤器通常不在java代码里设置,而是在AndroidManifest.xml利用来设置。(一个例外可能就是broadcast receivers的过滤器,他们是通过Context.registerRecerver())动态注册的,他们作为InterFilter对象被直接创建)。
在Intent中filter与action,data,category是平行关系。一个implicit intent要想通过intent filter必须要在action,data,category三个方面通过校验。如果其中一个没有通过,Android System就不会把intent传递给组件。然而,由于一个组件拥有很多的intent过滤器,所以只要通过其中一个就可以了。
检验的细节如下描述:
Action test
manifest文件中在元素下,列出了子元素。例如:
1 2 3 4 5 6 ; |
<intent-filter . . . > <action android:name="com.example.project.SHOW_CURRENT" /> <action android:name="com.example.project.SHOW_RECENT" /> <action android:name="com.example.project.SHOW_PENDING" /> . . . </intent-filter>
; |
如范例所展示,当一个Intent对象只命名了一个动作,过滤器可能会列举多个。列表不能为空;一个过滤器至少需要包含一个<action>元素,否则它不会匹配任何intents。
为了通过这个测试,Intent对象中所指定的动作必须与过滤器中所列举的动作之一匹配。如果这个对象或过滤器没有指定一个动作,其结果如下:
分类测试-Category test
一个元素,同样也把分类当作子元素列举,如:
1 2 3 4 5 ; |
<intent-filter . . . > <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> . . . </intent-filter>
; |
注意,上文中讨论的动作和分类常量并不适用于manifest文件。而是使用了完全字符串值。例如,上面范例中的CATEGORY_BROWSABLE常量。相似地,ACTION_EDIT常量。
对于一个intent,想要通过分类测试,intent对象中的每一个分类都必须对应f过滤器中的一个分类。过滤器可以列举额外的分类,但不能忽略intent中的任何分类。
因此,从理论上来说,不考虑过滤器中的值,一个没有分类的intent对象应该始终能通过这个测试。大部分情况下是对的。然而,有一个例外,Android把所有传递给startActivity())的隐性intents都看作是至少有一个分类:CATEGORY_DEFAULT常量)。所以,想要接收隐性itnets对象的活动必须在intent过滤器中包含"android.intent.action.MAIN"和"android.intent.category.DEFAULT",但不需要这么做。)见下文中#使用Intent匹配-Using intent matching|使用Intent匹配-Using intent matching,更多关于这种过滤器的信息。
数据测试-Data test
如同动作与分类,intent过滤器中的数据指定也包含在了一个子元素当中。并且,在这种情况下,子元素可以多次出现,或不出现。例如:
1 2 3 4 5 ; |
<intent-filter . . . > <data android:mimeType"http" . . . /> <data android:mimeType"http" . . . /> . . . </intent-filter>
; |
每一个元素可以指定一个URI和一个数据类型(MIME媒体类型)。有多个属性——host,port——组成了URI的每一个部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 ; |
例如,在以下的URI中,
```content://com.example.project:200/folder/subfolder/etc```
其中,scheme为```"com.example.project"```,port是```"folder/subfolder/etc"```。host与port一起,组成了URI权限,如果没有指定host,那么port也将被无视。
这些属性都是可选的,但它们之间并不相互独立:为了有意义的授权,scheme必须被指定。为了使路径(path)有意义,scheme和权限(authority)都必须被指定。
当Intent对象中的URI与过虑器中指定的URI对比时,只对比在过滤器中提及的部分。例如,如果过滤器只指定了数据类型,所有拥有这个scheme的URIs都能匹配。如果过滤器指定了一个scheme和权限(authority),但没有路径(path),所有拥有相同scheme和权限(authority)的URIs将匹配,不考虑其路径(path)。如果过滤器指定了scheme,权限(authority)和路径(path),那么,只有在URIs相同的这三个属性时才匹配。然而,过滤器中的路径指定可以包含通用符,来要求路径的部分匹配。
```type```属性指定了数据的MIME类型。在过滤器中,它比URI更常见。Intent对象和过滤器都可以使用" "通用符作为子类型域——如,```"audio/ "```——指定任意一个匹配的子类型。
数据测试同时对比Intent对象中和过滤器中所指定的URI和数据类型。规则如下:
a.一个既不包含URI又没有指定数据类型的Intent对象只有在过滤器同样什么都没指定的情况下才能通过测试。
b.任意一个只包含了URI但没有数据类型(并且无法从URI中推断出其类型)只有在这个URI与过滤器中的一个URI相匹配,并且同样没有指定数据类型的情况下通过测试。当URI为```tel:```时即为这种情况,并不能确切的知道数据类开。
c.Intent对象中包含了一个数据类型但没有URI时,只有在过滤器列举了相同的数据类型,并且类似有没有指定URI时通过测试。
d.Intent对象同时包含了一个URI和一个数据类型(如数据类型可以由URI推断出)只有在该类型与过滤器中所列类型匹配时才能通过测试。它将通过URI部分的测试,要么URI与过滤器的中某个匹配,要么其包含了一个```"color:green">file:```URI并且过滤器没有指定一个URI。换句话说,如果过滤器中听指定了数据类型,那么一个组件默认的支持```file:```数据。
如果一个intent可以通过多个活动和服务的过滤器,那么用户将需要选择激活哪个组件。如果找不到目标,刚会抛出一个异常。
### 一般案例-Common cases ***
在上一个Data test提到的最后一条(d),表达出一种期望:组件可以从文件或content provider中获得数据。因此,他们的过滤器只需列出数据的类型而不需要给```file:``` schemes明确命名。下面是一个经典的例子。一个```<data>```元素,告诉Android,组件可以从content provider获得图片数据并且显示:
```java <data android:mimeType="image/* " />
; |
因为大多数可用的数据是由content provider配与的,过滤器只指定数据类型而不指定URI可能成为最常见的。
另一种通用的配置是过滤器指定scheme和数据类型。例如,一个<data>元素,告诉Android,组件可以从网络获得视频数据并显示:
1 ; |
<data android:scheme"video/* " />
; |
考虑一下当点击一个链接时,浏览器会做什么。首先,它会尝试去展示数据(因为他能够连接到网页)。如果它不能展示数据,它会把implicit intent与scheme和数据类型放在一起并且尝试启动一个可以完成任务的activity。如果没有合适的activity,它会要求下载器下载数据。把应用放在content provider的控制下,这样会有一个潜在的activities池响应调度。
大多数应用还可以重新启动,在不需要引入任何特殊的数据的情况下。初始化应用的Activities拥有action过滤器"android.intent.category.LAUNCHER":
1 2 3 4 ; |
<intent-filter . . . > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
; |
使用intent匹配-Using intent matching
Intents与intent filters匹配,不仅仅是为了找到激活的目标组件,而且也为了找到关于设备上的组件的相关信息。例如,Android系统填充应用程序启动器,顶层屏幕显示了用户可以启动的应用程序,通过寻找所有在intent filters中指定了"android.intent.category.LAUNCHER"分类(如前文中所展示的)的活动。然后将这些活动的图标和标签显示在启动器中。相似地,通过寻找filter中的"android.intent.category.HOME"来发现主屏幕上的应用。
你的应用程序可以使用intent匹配,也是通过类似的���法。PackageManager拥有一系列的resolve...()方法用来决定最佳的组件来响应这个intent。例如,queryIntentActivityies())返回了一个所有能通过intent的活动的列表,queryIntentService())类似地返回了一个服务列表。但这两个方法都不激活组件;他们只是列表了所有可以响应的组件。对于广播接收器也有类似的方法,queryBroadcastReceivers)。
记事本案例-Note Pad Example
记事本案例(Note Pad Example)应用程序使用户能够浏览一个记事本列表,查看列表中的单独项目,编辑,和添加新的项目到列表。本章节着重于manifest文档中intent filters的声明。(如果你处于离线,并使用SDK,你可以在<sdk>/samples/NotePad/index.html中找到关于这个范例应用程序,包括manifest文件的原代码。如果你是在线观看的本文档,所有源码文件在Tutorials and Sample Code中。)
在manifest文件中,记事本应用程序声明了三个活动,每一个至少含有一个intent filter。它同时还声明了一个内容提供者(content provider)来管理记事本数据。以下为manifest中的完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ; |
<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"@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>
<activity android:name="NoteEditor" android:theme="@android:style/Theme.Light" android:label="@string/title_note" > <intent-filter android:label="@string/resolve_edit"> <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> <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"> <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>
; |
第一个活动,NoteList,通过其操作于一个记事本路径(记事本列表)而不是某个单独的笔记的实际区别于其他活动。通常情况下,它将作为用户的首选接口服务于应用程序之中。通过它定义的三个intent filters,它可以完成以下三件事:
1 2 3 4 ; |
<intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
; |
这个过滤器申明了记事本应用程序的主接入点。标准的LAUNCHER分类描述了这个接入点应该被列举在应用程序启动器之中。
1 2 3 4 5 6 7 ; |
<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>
; |
这个过滤器申明活动可以在记事本路径下可做的事情。它允许用户查看并编辑这个路径(通过EDIT动作),或是从路径中选取一个特定的笔记(通过PICK动作)。
元素中的vnd.android.cursor.dir),这个内容提供者拥有记事本的数据(content:URI指定了这种类型的活动应该打开的额外数据。
在过滤器中也含有DEFAULT分类——只有两个例外:
Intents��确的指出了目标活动
组成了LAUNCHER分类的Intents
因此,MAIN动作和LAUNCHER分类的过滤器。(在有明确的intents时,Intent过滤器并不被查寻)。
1 2 3 4 5 ; |
<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>
; |
这个过滤器描述了活动可以返回一个用户所选择的笔记而不需要指定用户选择的笔记的路径。PICK动作。在两个案例中,活动都返回了用户选择的笔记的URI。(在每一个案例中,它被返回给了活动并调用startActivityForResult())来启动NoteList活动。)然而,这种情况下调用者指定了渴望的数据类型而不是用户将要取得数据的路径。
数据类型,vnd.google.note)得到一个一一对应的Cursor。
换句话说,对于前文中提到的GET_CONTENT过滤器,它指示了活动可以返回给调用者的数据类型。
拥有了这些能力,以下的intents能够将其分解给NotesList活动:
动作:android.intent.action.MAIN
动作:android.intent.action.MAIN
分类:
- * 启动一个没有数据被选择的活动。这个是启动器实际上用来填充其顶级列表的。所有在过滤器中匹配这个动作和分类的活动将被添加到这个列表中。
动作:android.intent.action.VIEW
数据:content://com.google.provider.NotePad/notes
- * 要求活动在content://com.google.provider.NotePad/notes路径下的所有笔记的列表。用户可以从列表中选择一个笔记,并且该活动将把此项目的URI返回给启动NoteList活动的活动。
动作:android.intent.action.GET_CONTENT
数据类型:vnd.android.cursor.item/vnd.google.note
* * 要求活动支持笔记本数据的单个项目。
第二个活动,NoteEditor,向用户使展示了单独一个笔记的键入并允许其被编辑。如两个intent过滤器所打描述的,它可以做两个事情:
1 2 3 4 5 6 7 ; |
<intent-filter android:label="@string/resolve_edit"> <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>
; |
第一个,也是唯一的目的,这个activity是想让用户和记事本进行互动--用户既可以查看又可以编辑它(EDIT的同义词)。Intent将包含匹配类型的数据的URIPICK或者GET_CONTENT返回的URI。
像以前一样,上面的过滤器列出了DEFAULTcategory,使activity能够被没有具体指明NoteEditor类的intent所调用
1 2 3 4 5 ; |
<intent-filter> <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的第二个目的是让用户创建一个新的记事本。Intent将包含匹配类型的数据的URIvnd.android.cursor.dir/vnd.google.note--他是指向记事本存在的路径的URI。
鉴于这些能力,下列intents将解决NoteEditor activity:
action:android.intent.action.VIEW
data:content://com.google.provider.NotePad/notes/ID
action:android.intent.action.INSERT
data: content://com.google.provider.NotePad/notes
- * 要求activity通过content://com.google.provider.NotePad/notes创建一个新的,空白的记事本并且允许用户去编辑,如果用户保存,那么它的URI将返回给调用者。
最后的activity,TitleEditor,让用户可以编辑记事本的题目。这个可以通过直接引入activity实现,而不需要通过intent过滤器。但是我们在这里展示如何how to publish alternative operations on existing data:
1 2 3 4 5 6 7 ; |
<intent-filter android:label="@string/resolve_title"> <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>
; |
这个intent过滤器使用了传统的action,vnd.android.cursor.item/vnd.google.note),就像是之前的EDIT。然而,这里的activity显示的是记事本的题目而不是内容。
除了支持常用的LAUNCHER category 指定了应用启动器一样)。注意过滤器还要提供明确的标签(通过"@string/resolve_title")以便能更好的控制用户所能看到的一切。(关于这些categories和创建菜单项,请查看PackageManager.queryIntentActivityOptions()和Menu.addIntentOptions())
鉴于这些能力,下列intent将解决TitleEditor activity:
action: com.android.notepad.action.EDIT_TITLE
data: content://com.google.provider.NotePad/notes/ID
::要求activity按照ID显示记事本的标题,并允许用户编辑标题。