Android开发指南-框架主题-意图和意图过滤器

意图和意图过滤器Intents and Intent Filters

 

一个应用程序的三个核心组件-活动,服务和广播接收器是通过消息即意图(Intents)来激活的。Intent消息传送是相同或不同应用中组件运行时晚绑定的一种机制。意图本身,一个意图对象,是一个包含被执行操作抽象描述的被动的数据结构-或者,对于广播而言,是某件已经发生并被声明的事情的描述。存在不同的机制来传送意图到每种组件中:

在每个例子里,Android系统找到合适的活动,服务,或者一组广播接收者来回应这个意图,必要时实例化它们。这些消息传送系统没有重叠:广播意图仅被传递给广播接收者,永远不会给活动或者服务。一个传送给startActivity()意图是只会被传递给一个活动,永远不会给一个服务或广播接收者,如此类推。

这篇文档以意图对象的描述开始,然后描述Android映射意图到组件的规则-如何解决哪个组件应该接收一个意图消息。对于没有显式命名一个目标组件的意图,这个过程包括对照与潜在目标相关联的意图过滤器来测试这个意图对象。

 

意图对象Intent Objects

一个意图Intent对象是一堆信息。它包含接收这个意图的组件感兴趣的信息(例如将要采取的动作和操作的数据)再加上Android系统感兴趣的信息(例如应该处理这个意图的组件类别和如何启动一个目标活动的指令):

组件名称Component name

应该处理这个意图的组件名字这个字段是一个ComponentName对象一个组合物:目标组件的完全合格的类名 (比如"com.example.project.app.FreneticActivity") 以及应用程序描述文件中设置的组件所在包的名字(比如, "com.example.project"). 这个组件名字的包部分和描述文件中设置的包名字不一定要匹配。

组件名字是可选的。如果被设置了,这个意图对象将被传递到指定的类。如果没有, Android使用另外的意图对象中的信息去定位一个合适的目标请看本文稍后描述的意图解析Intent Resolution

组件名字通过如下方法:setComponent(),setClass()或者setClassName()设置并通过getComponent()读取。

 

动作Action

一个将被执行的动作的字符串命名-或者对于广播意图而言是发生并被报告的动作。这个意图类定义了一些动作常量包含下面这些:

 

常量

目标组件

Action

ACTION_CALL

活动

开始一个电话呼叫

ACTION_EDIT

活动

显示数据以给用户编辑

ACTION_MAIN

活动

开始任务的初始活动,没有输入数据也没有输出返回

ACTION_SYNC

活动

同步服务器与移动设备之间的数据

ACTION_BATTERY_LOW

广播接收器

电池低电量警告

ACTION_HEADSET_PLUG

广播接收器

耳机插拔

ACTION_SCREEN_ON

广播接收器

屏幕开启

ACTION_TIMEZONE_CHANGED

广播接收器

时区变化

通过查看Intent类描述可获得一个通用动作的预定义常量列表。其他动作被定义在Android API的其他地方。你也可以自定义动作字符串来激活应用程序中的组件。那些你所创建的动作字符串应该以应用程序包名作为前缀-例如:

"com.example.project.SHOW_COLOR".

动作很大程度上决定了意图其他部分如何被组织-尤其是数据data和附加字段extras-很像一个方法名决定了一些参数和返回值因此一个好的想法是使用尽可能具体的动作名并和意图的其他字段紧密联系起来。换句话说,为您的组件能处理的意图对象定义一个整体的协议而不是定义一个孤立的动作。

一个意图对象里的动作可以通过setAction()方法设置和通过getAction()方法读取.

 

数据Data

想要操作的数据统一资源标识符(URI)和那种数据的多用途互联网邮件扩展(MIME不同的动作伴随着不同种类的数据规格。例如,如果动作是ACTION_EDIT,数据字段会包含可编辑文档的URI;如果动作是ACTION_CALL,数据字段会是一个电话号码:含呼叫电话号码的URI;类似的,如果动作是ACTION_VIEW而且数据字段是一个http:URI, 那么接收到的活动将会是下载并显示URI所引用数据的请求。当匹配一个意图到一个能处理数据的组件时,除了它的URI外,通常需要知道数据类型(它的MIME类型)。

比如,一个能显示图片的组件不应该被要求去播放一个声音文件。

在很多情况下,这个数据类型可以从URI里推断出来-尤其是content:URIs, 这意味着数据被存放在设备上而且由一个内容提供者控制着。(参阅separate discussion on content providers). 但类型可以在意图对象里显示的设置。setData()方法指定数据只能为一个URIsetType()指定它只能是一个MIME类型setDataAndType()指定它同时为URIMIME类型。URI通过getData()读取,类型则通过getType().

 

目录Category

一个包含关于应该处理这个意图的组件的附加信息的字符串。任意数目的类别描述可以被放到一个意图对象里。和动作一样,意图类定义若干类别常量,包含如下这些:

常量

含义

CATEGORY_BROWSABLE

目标活动可以被浏览器安全的唤起来显示被一个链接所引用的数据-比如,一张图片或一条e-mail消息。

CATEGORY_GADGET

这个活动可以被嵌入到充当配件宿主的另外的活动里面。

CATEGORY_HOME

这个活动将显示桌面,也就是用户开机后看到的第一个屏幕或者按HOME键时看到的屏幕。

CATEGORY_LAUNCHER

这个活动可以是一个任务的初始活动并被列在应用程序启动器的顶层。

CATEGORY_PREFERENCE

目标活动是一个选择面板。

查阅Intent类描述可获取类别的完整列表。

addCategory()方法在一个意图对象中添加了一个目录,removeCategory()删除之前添加的目录,而getCategories()可以获取当前对象的所有类别。

 

附加信息Extras

应该递交给意图处理组件的附加信息键-值对。就像一些动作伴随着特定的数据URIs类型,一些动作则伴随着特定的附加信息。比如,一个ACTION_TIMEZONE_CHANGED意图有一个“时区”附加信息用来区别新的时区,而ACTION_HEADSET_PLUG有一个“状态”附加字段表明耳机有没有插着,以及一个“名字”附加信息来表示耳机的类型。如果你想要创建一个SHOW_COLOR动作,颜色的值将被设置在一个附加的键-值对中。

意图对象有一系列的put...()方法来插入各种不同的附加数据和一个类似的用来读取数据的get...()方法系列。这些方法与Bundle对象的方法相似。事实上,附加信息可以被当作一个Bundle通过使用putExtras()getExtras()方法安装和读取。

标志Flags

各种类型的标志许多标志用来指示Android系统如何去加载一个活动(例如,哪个是这个活动应该归属的任务)和启动后如何对待它(比如,它是否属于当前活动列表),所有这些列表都在意图类中定义了。

Android系统以及这个平台上的应用程序利用意图对象来发送源于系统的广播以及激活系统定义的组件。要查阅如何组织一个意图去激活一个系统组件,请咨询引用中的意图列表list of intents

 

意图解析Intent Resolution

意图可以被分成两组:

  • 显式意图  通过名字指明目标组件(这个组件名字字段component name field前面提到过有一个数值集)。既然组件名称通常不为其他应用程序的开发者所了解,显式意图典型的被用作应用程序的内部消息-例如一个活动启动一个附属服务或姊妹活动。
  • 隐式意图  不命名目标组件(组件名称字段为空)。隐式意图经常用来激活其他应用程序的组件。

Android递交一个显式的意图给一个指定目标类的实例。意图对象中的组件名称唯一的确定哪个组件应该获取这个意图。隐式意图需要一个不同的策略。在没有指定目标的情况下,Android系统必须找到最合适的组件来处理这个意图-单个活动或者服务来执行这个请求动作或者一系列的广播接收器来应对广播通告。

这是通过比较意图对象的内容和意图过滤器,有可能接收意图的组件相关结构。过滤器公布一个组件具备的能力以及限定它能处理的意图。他们使组件接收该公布类型的隐式意图成为可能。如果一个组件没有任何的意图过滤器,那它只能接收显式意图。一个带过滤器的组件可以同时接收显式和隐式意图。

当一个意图对象被一个意图过滤器测试时,只有三个方面会被参考到:

动作
数据(URI以及数据类型)
类别

附加信息和标志并不参与解析哪个组件接收一个意图。

 

意图过滤器Intent filters

为了通知系统它们可以处理哪些意图,活动、服务和广播接收器可以有一个或多个意图过滤器。每个过滤器描述组件的一个能力,一系列组件想要接收的意图。它实际上按照一个期望的类型来进行意图滤入,同时滤出不想要的意图-但是只有不想要的隐式意图会被滤出(那些没有命名目标的对象类)。一个显式意图总能够被递交给它的目标,而无论它包含什么。这种情况下过滤器不起作用。但是一个显式意图仅当它能通过组件的一个过滤器时才可以被递交到这个组件。

组件为它能做的每项工作,每个呈现给用户的不同方面分有不同的过滤器。比如,范例记事本应用程序中的主要活动有三个过滤器-一个是空白板,另一个是用户可以查看、编辑、或选择的一个指定的记事目录,第三是在没有初始目录说明的情况下查找一个特定的记录。一个意图过滤器是IntentFilter类的一个实例。但是,由于Android系统在启动一个组件前必须知道这个组件的能力,意图过滤器通常不会用Java代码来设置,而是在应用程序清单文件(AndroidManifest.xml)中设置<intent-filter>元素。(有一个例外,通过调用Context.registerReceiver() 来注册的广播接收器的过滤器;它们是作为意图过滤器对象而被直接创建的。

 

过滤器与安全Filters and security

不能信赖一个意图过滤器的安全性。当它打开一个组件来接收某些特定类型的隐式意图,它并不能阻止以这个组件为目标的显式意图。即使过滤器对组件要处理的意图限制某些动作和数据源,总有人能把一个显式意图和一个不同的动作及数据源组合在一起,然后命名该组件为目标。

一个过滤器和意图对象有同样的动作、数据以及类别字段。一个隐式意图在过滤器的所有三个方面都被测试。为了递交到拥有这个过滤器的组件,它必须通过所有这三项测试。即便只有一个不通过,Android系统都不会把它递交给这个组件-至少以那个过滤器的标准而言。不过,由于一个组件可以包含多个意图过滤器,一个不能通过其中一个组件过滤器的意图可能在另外的过滤器上获得通过。

三个测试详细描述如下:

动作测试Action test

清单文件中的意图过滤器元素里列举了动作元素,比如

<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>

如同例子所示,一个意图对象只对单个动作命名,而一个过滤器可能列举多个。列表不能为空;一个过滤器必须包含至少一个动作元素,否则它将阻塞所有的意图。

为了通过这个测试,在意图对象中指定的动作必须匹配过滤器中所列举的动作之一。如果意图对象或过滤器不指定一个动作,结果将如下:

·         如果这个过滤器没有列出任何动作,那意图就没有什么可匹配的,因此所有的意图都会测试失败。没有意图能够通过这个过滤器

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

类别测试Category test

一个意图过滤器<intent-filter>元素也列举了类别作为子元素。比如:

<intent-filter . . . >

     <category android:name="android.intent.category.DEFAULT" />

     <category android:name="android.intent.category.BROWSABLE" />

     . . .

</intent-filter>

注意前面描述的动作和类别常量没有在清单文件中使用。相反使用了完整的字符串。比如,对应于前述CATEGORY_BROWSABLE常量,上面的例子里使用了"android.intent.category.BROWSABLE"字符串。类似的,字符串"android.intent.action.EDIT对应于ACTION_EDIT常量。

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

因此,原则上一个没有类别的意图对象应该总能够通过测试,而不管过滤器里有什么。绝大部分情况下这个是对的。但有一个例外,Android把所有传给startActivity()的隐式意图当作他们包含至少一个类别:"android.intent.category.DEFAULTCATEGORY_DEFAULT常量)。 因此,想要接收隐式意图的活动必须在它们的意图过滤器中包含"android.intent.category.DEFAULT"。(带"android.intent.action.MAIN""android.intent.category.LAUNCHER"设置的过滤器是例外)。它们标记那些启动新任务和呈现在启动屏幕的活动。它们可以在类别列表中包含"android.intent.category.DEFAULT",但不是必要的。可查阅后面的使用意图匹配(Using intent matching)以获得更多关于过滤器的信息。

数据测试Data test

就像动作和类别,一个意图过滤器的数据规格被包含在一个子元素中。而且这个子元素可以出现多次或一次都不出现。例如:

<intent-filter . . . >

     <data android:type="video/mpeg" android:scheme="http" . . . />

     <data android:type="audio/mpeg" android:scheme="http" . . . />

     . . .

 </intent-filter>

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

scheme://host:port/path

比如,在下面的URI里面,

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的意图对象仅在这个过滤器列举了同样的数据类型而且也没有指定一个URI的情况下才能通过测试。

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

如果一个意图可以通过不止一个活动或服务的过滤器,用户可能会被询问要激活那个组件。如果没有发现目标对象将会出现异常。

 

通常情况Common cases

上面描述的数据测试的最后一个规则(d),表达了这样一个期望即组件能够从文件或内容提供者中获取本地数据。因此,它们的过滤器可以只列举一个数据类型而不需要显式的命名content:file:模式。这是一个典型情况。比如,一个如下的数据<data>元素,告诉Android这个组件能从内容提供者获取图片数据并显示:

<data android:type="image/*" />

既然大多数可用数据是通过内容提供者来分发,那么过滤器最通常的配置就是指定一个数据类型而不指定URI。另外一个通用的配置是带有一个模式和数据类型的过滤器。比如,一个如下的数据<data>元素告诉Android可以从网络获取视频数据并显示:

<data android:scheme="http" android:type="video/*" />

比如,想一下,当用户点击网页上的一个链接时浏览器做了什么。它首先试图去显示这个数据(如果这个链接指向一个HTML页面)。如果它不能显示这个数据,它会把一个显式意图和一个模式、数据类型组成整体然后尝试启动一个可以处理这个工作的活动。如果没有接受者,它将要求下载管理器来下载数据。这让它处于内容提供者的控制下,以便一个潜在的更大的活动池可以做出反应。

大多数应用程序同样有一个方法去启动刷新,而不包含任何特定数据的引用。能初始化应用程序的活动拥有指定动作为"android.intent.action.MAIN"的过滤器。如果它们表述在应用程序启动器中,那它们同样指定了"android.intent.category.LAUNCHER"类别:

<intent-filter . . . >

     <action android:name="code android.intent.action.MAIN" />

     <category android:name="code android.intent.category.LAUNCHER" />

 </intent-filter>

 

使用意图匹配Using intent matching

通过意图过滤器匹配的意图不仅是为了发现要激活的目标组件,而且为了发现这个设备上的一系列组件的某些东西。比如,Android系统通过查找符合条件的所有活动(需要包含指定了动作"android.intent.action.MAIN""android.intent.category.LAUNCHER"类别的意图过滤器,如前面章节所述)来产生应用程序启动器,也就是用户可用程序的前置屏幕。然后它显示在这个启动器里的这些活动的图标和标签。类似的,它通过查找其过滤器配有"android.intent.category.HOME"元素的活动来发现桌面。

你的应用程序可以用类似的方式使用意图匹配。PackageManager有一系列的查询query…()方法可以接收一个特定的意图,以及相似的一个解析resolve…()方法系列可以确定应答意图的最佳组件。比如,queryIntentActivities()返回一个所有活动的列表,而queryIntentServices()返回一个类似的服务列表。两个方法都不会激活组件;它们仅仅列举能应答的。对于广播接收者,有一个类似的方法queryBroadcastReceivers()

posted @ 2011-01-10 08:40  张兴业  阅读(195)  评论(0编辑  收藏  举报