资源描述
Android应用程序有三个核心组件---Activity、Service、Broadcast Receiver,Broadcast receiver是通过叫做Intent的消息来激活的。Intent消息是同一个应用程序或不同应用程序运行后,组件间进行绑定的一种能力。Intent对象本身是一个持有要执行操作的抽象描述的无源数据结构,或者在广播的情况下,经常用它来描述一些已经发生或正在发生的事情。给每种类型组件发送Intent对象都有其独立的机制:
1. 为了做某些新的操作,把一个Intent对象传递给Context.startActivity()或Activity.startActivityForResult()方法来启动一个新的Activity或者获取一个既存的Activity。调用startActivityForResult()方法启动的那个Activity也能够把Intent对象传递给Activity.setResult()方法来返回响应的信息。
2. 把一个Intent对象传递给Context.startService()方法来初始化一个服务或给一个正在运行的服务发送新的指令。类似地,也能把Intent对象传递给Context.bindService()方法,在调用组件和目标服务之间建立一个连接,如果服务还没有运行可以选择启动。
3. 传递给广播方法(如,Context.sendBroadcast()、Context.sendOrderedBroadcast()、或Context.sendStickyBroadcast()方法)的任何Intent对象都被发送给所有的感兴趣的广播接收器。广播的许多类型源于系统代码。
在每种情况下,Android系统都会查找对应的Activity、Service或Broadcast Receiver的设置来响应这个Intent对象,如果需要也会实例化这些组件。在这些消息系统内部没有消息重叠:广播的Intent对象仅发送给Broadcast Receiver组件,不会发送给Activity或Service组件。传递给startActivity()方法的Intent对象仅发送给一个Activity,不会发送给一个Service或Broadcast Receiver组件,等等。
本文档开始介绍Intent对象,然后介绍Android使用的把Intent对象映射给组件的规则---Android系统是如何解析那个组件应该接收对应的Intent消息。对于没有明确命名目标组件的Intent对象,这个过程涉及用与潜在的目标关联Intent过滤器来检测Intent对象。
Intent 对象
一个Intent对象就是一个信息包。它包含了接收这个Intent对象的组件感兴趣的信息(如要执行的动作和动作相关的数据)和Android系统感兴趣的信息(如处理这个Intent对象的组件的分类和有关如何启动目标Activity的指令),主要包含以下信息:
组件名
指的是能够处理Intent对象的组件的名字。这个字段是一个ComponentName对象---它是目标组件的完全类名(如:com.example.project.app.FreneticActivity)和组件所在的应用程序的清单文件中设置的包名(如:com.example.project)的组合。组件名的包部分和清单文件中设置的包名不一定一致。
组件名是可选的,如果设置了组件名,Intent对象就会被发送给这个指定类的实例。如果没有设置,Android系统使用Intent对象中的其他信息来定位合适的目标。
组件名是通过setComponent()、setClass()或setClassName()方法来设置,并且通过getComponent()方法来读取。
动作(Action)
指的是一个要执行的动作的命名字符串,在广播Intent对象的情况下,指的是已经发生和正在报告的动作。Intent类定义很多动作常量,详见下表:
常量
目标组件
动作
ACTION_CALL
Activity
发起电话呼叫
ACTION_EDIT
Activity
为用户显示要编辑的数据
ACTION_MAIN
Activity
作为一个任务的初始Activity启动,没有数据输入和返回输出。
ACTION_SYNC
Activity
用移动设备上的数据同步服务上的数据。
ACTION_BATTERY_LOW
Broadcast Receiver
低电量的一个警告
ACTION_HEADSET_PLUG
Broadcast Receiver
耳麦已经被插入设备,或者从设备上拔出。
ACTION_SCREEN_ON
Broadcast Receiver
显示屏已经被打开
ACTION_TIMEZONE_CHANED
Broadcast Receiver
时区相关的设置已经被改变
对于一般性动作的预定义常量列表,请看Intent类说明。在Android API的其他地方还定义了一些其他的动作。你也可以在自己的应用程序中给Activity组件定义自己的动作字符串。你创建这些动作应该包含应用程序的包名作为动作前缀---例如:com.example.project.SHOW_COLOR.
动作在很大程度上决定了Intent类的结构---特别是data和extras字段---如一个方法名决定了一组参数和一个返回值。由于这个原因,尽可能的指定使用动作的名字是个好主意,并且要把它们与Intent的其他字段紧密的捆绑在一起。换句话说,就是要给你的组件能够处理的Intent对象定义一个完整的协议,而不是定义一个独立的动作。
Intent对象中的动作是由setAction()方法设定的,并且有getAction()方法读取的。
数据(Data)
指的是动作相关的数据的资源标识和数据的MIME类型。不同的动作要跟不同的数据规范类型配合使用。如果动作字段是ACTION_EDIT,那么数据字段应该包含为编辑而显示的数据的资源标识(URI)。如果动作时ACTION_CALL,那么数据字段就应该是tel:带有呼叫号码的URI。类似地,如果动作时ACTION_VIEW,并且数据字段是http:URI。那么接收Activity就应该下载并显示URI所指向的数据。
在把一个Intent对象分配给一个有处理数据能力的组件时,了解附件在URI中的数据类型是至关重要。例如,能够显示图片的组件不应该被调用来播放音频文件。
很多情况下,从URI中能够推断出数据类型---特别是content:URIs,它指明了设备上数据的位置和控制数据的内容提供器。但是数据类型也能够在Intent对象中明确的设定。setData()方法只能给URI指定数据,setType()方法只能给数据指定MIME类型,setDataAndType()方法同时指定URI的数据和数据的MIME类型。通过getData()方法读取数据,getType()方法获取数据类型。
分类(Category)
指的是包含能够处理这个Intent对象的组件类型的相关信息的字符串。任何类别的分类描述都能够被放在Intent对象中。跟action的动作一样,Intent类也定义几个分类常量,如下表:
常量
含义
CATEGORY_BROWSABLE
目标Activity能够安全的调用浏览器来显示链接所指向的数据---如,一张图片或一封电子邮件消息。
CATEGORY_GADGET
Activity能够被嵌入到持有小部件的另一个Activity中
CATEGORY_HOME
Activity显示在主屏幕上,在设备打开时用户看到的第一个屏幕或Home按钮被按下时,用户看到的屏幕。
CATEGORY_LAUNCHER
Activity能够作为任务的初始Activity,并且被列在应用程序启动器的顶层。
CATEGORY_PREFERENCER
目标Activity是一个首选面板。
完整的分类列表请看Intent类的说明。
addCategory()方法把一个分类放到一个Intent对象中,removeCategory()方法删除先前添加的分类,getCategories()方法获取当前Intent对象中的所有分类设置。
附加信息(Extras)
它是以Key-value对的形式发送给处理这个Intent对象的组件的附加信息。就像某些数据URIs要跟动作配对一样,某些特殊的附加信息也需要配对。如,一个ACTION_TIMEZONE_CHANGED类型的Intent对象有一个指定新时区的time-zone附加信息,ACTION_HEADSET_PLUG类型的Intent对象有一个指示耳麦当前是插入还拔出状态的附加信息,对于耳麦类型还有一个name的附加信息。如果你创建了一个SHOW_COLOR动作,颜色值应该被设置在一个key-value对的附加信息中。
Intent对象为插入各种类型的附加数据会有一系列的put…()方法,并为读取数据也会有一组类似的get…()方法。这些方法并行于Bundle对象一些方法。实际上,附加信息能够作为一个Bundle对象使用putExtras()和getExtras()方法来安装和读取。
标记(Flags)
Intent对象有各种标记,很多都是用于指示Android系统如何启动Activity(如,Activity应该属于哪个任务),以及启动后如何处理(如,它是否属于最近的Activity列表)。所有这些标记都在Intent类中定义。
Android系统和平台相关的应用都采用Intent对象来发出面向系统的广播和激活系统定义的组件。
Intent对象解析
Intent能够被分成两组:
1. 用组件的名称把Intent对象明确的指向目标组件(在Intent对象的组件名字段指定目标组件名)。因为一般情况下其他应用的开发者不会了解目标组件的名字,所以通常针对应用程序的内部消息使用明确命名的Intent对象,如一个Activity启动一个下属服务或启动一个姊妹Activity。
2. 没有命名目标(Intent对象的组件名字段是空的)的隐式的Intent对象。隐式的Intent对象经常被用于激活其他应用程序中的组件。
Android系统把一个明确命名的Intent对象发送给目标类的一个实例。除了组件名以外,不再用Intent对象内任何其他信息来判断哪个组件应该获得这个Intent对象。
对于隐式的Intent对象,需要不同的分类。在缺少目标组件的情况下,Android系统必须查找最适合的组件(一个能够执行请求动作的Activity或Service,或者是一组能够响应广播通知的Broadcast Receiver)来处理这个Intent对象。系统通过把Intent对象的内容跟Intent过滤器比较,跟组件的结构关联就能够潜在接收Intent对象。过滤器会公开组件的能力和它能够处理的Intent对象限制。它们打开组件接收可能的公开类型的隐式Intent对象。如果组件没有任何过滤器,那么它仅能接收明确命名的Intent对象。带有过滤器的组件能够接收命名和匿名的Intent对象。
Intent过滤器会参考Intent对象以下三个方面来检测是否接收这个Intent对象:
1. 动作(action)
2. 数据(URI和数据类型)
3. 分类(category)
附加信息(extras)和标记(flags)不作为判断哪个组件接收这个Intent对象标准。
Intent过滤器
Intent过滤器是用来通知系统它们能够处理那种类型隐式的Intent对象,Activity、Service、Broadcast Receiver能够有一个或多个Intent过滤器。每个过滤器都描述了组件的一种能力,说明了组件将会接受的Intent对象集。它滤如有效的期望类型的Intent对象,滤出不想要的Intent对象---但是仅是不想要的隐式Intent对象(那些没有命名目标类的Intent对象)。一个有明确命名的Intent对象总是包被发送给它的目标类的实例,而不管它包含了什么;过滤器不起作用。但是隐式的Intent对象仅能发送给能够通过组件的一个过滤器来传递它的一个组件。
对于组件能够做的每项工作,它都会有一个独立的过滤器,并且每一工作都能够展现给用户。例如,实例应用Note Pad应用程序的NoteEditor Activity就有两个过滤器---一个是用于启动带有用户能够查看或编辑的特定注释信息的过滤器;另一个是用于启动一个新的,用户能够填充和保存的空白注释过滤器。(Note Pad的所有过滤器在“Note Pad示例”一节中介绍)
一个Intent过滤器是IntentFilter类的实例。但是因为Android系统在它启动组件之前必须了解有关组件的能力,所以Intent过滤器通常都不是用Java代码来建立的,而是在应用程序的清单文件(AndroidManifest.xml)中用<intent-filter>元素来声明。(通过调用Context.registerReceiver()方法来动态注册的Broadcast Receiver是一个例外,它们直接创建IntentFilter对象做为过滤器。)
过滤器有类似于Intent对象的动作、数据、和分类的字段,过滤器会用这三个域来检测一个隐式的Intent对象。对于要传递给拥有过滤器的组件的Intent对象,必须传递所有的这三个要检测的字段。如果其中之一失败了,Android系统也不会把它发送给对应的组件---至少在基于那个过滤器的基础上不会发送。但是,因为一个组件能够有多个Intent过滤器,即使不能通过组件的一个过滤器来传递Intent对象,也可以使用其他的过滤器。
以下详细说明对三个域的检测
1. 动作域检测
在清单文件中的<intent-filter>元素内列出对应动作的<action>子元素。如:
<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>元素,否则它会阻塞所有的Intent对象。
要通过这个检测,在Intent对象中指定的动作必须跟这个过滤器的动作列表中动作一致匹配。如果Intent对象或过滤器没有指定的动作,会产生以下结果:
A. 如果对列表中所有动作都过滤失败,那么对于要匹配的Intent对象不做任何事情,而且所有的其他Intent检测都失败。没有Intent对象能够通过这个过滤器;
B. 另一方面,没有指定动作的Intent对象会自动的通过检测---只要这个过滤器包含至少一个动作。
2. 分类域检测
<intent-filter>元素也要列出分类作为子元素。例如:
<intent-filter . . . >
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
. . .
</intent-filter>
注意,对于清单文件中的动作和分类没有使用早先介绍的常量,而是使用了完整字符串值来替代。例如,上例中“android.intent.category.BROWSABLE”字符串对应本文档前面提到的CATEGOR_BROWSABLE常量。类似的“android.intent.action.EDIT”字符串对应ACTION_EDIT常量。
对于一个要通过分类检测的Intent对象,在Intent对象中每个分类都必须跟过滤器中的一个分类匹配。过滤器能够列出额外的分类,但是它不能忽略Intent对象中的任何分类。
因此,原则上一个没有分类的Intent对象应该始终通过这个检测,而不管过滤器中声明的分类。大多数情况都是这样的,但是,有一个例外,Android处理所有传递给startActivity()方法的隐式Intent对象,就像它们至少包含了一个“android.intent.category.DEFAULT(对应CATEGORY_DEFAULT常量)”分类一样。因此接收隐式Intent对象的Activity必须在它们的Intent过滤器中包含“android.intent.category.DEFAULT”分类。(带有“android.intent,action.MAIN”和“android.intent.category.LAUNCHER”设置的过滤器是个例外。因为它们把Activity标记为新任务的开始,并且代表了启动屏。它们能够在分类列表中包含“android.intent.category.DEFAULT”,但是不需要。)
3. 数据域检测
像动作分类检测一样,针对Intent过滤器的数据规则也要包含在一个子元素中,并且,跟动作和分类的情况一样,这个子元素也能够出现多次,或者不出现。例如:
<intent-filter . . . >
<data android:mimeType="video/mpeg" android:scheme="http" . . . />
<data android:mimeType="audio/mpeg" android:scheme="http" . . . />
. . .
</intent-filter>
每个<data>元素能够指定一个URI和一个数据类型(MIME媒体类型)对于每个URI部分都会有独立的属性---scheme、host、port、path:scheme://host:port/path
例如,以下URI:
content://com.example.project:200/folder/subfolder/etc
scheme是content,host是“com.example.project”,port是“200”,path是“folder/subfolder/etc”。host和port一起构成了URI授权,如果没有指定host,那么port也会被忽略。
这些属性是可选的,但是,它们不是彼此独立的,如一个授权意味着必须指定一个scheme,一个path意味着必须指定scheme和授权。
当Intent对象中的URI跟过滤器的一个URI规则比较时,它仅比较在过滤器总实际提到的URI部分。如,如果一个过滤器仅指定了一个scheme,那么带有这个scheme的所有的URIs都会跟这个过滤器匹配。如果一个过滤器指定了一个scheme和授权,但是没有路径,那么带有相同scheme和授权的所有URIs的Intent对象都会匹配,而不管它们的路径。如果一个过滤器指定了一个scheme、授权、和路径,那么就只有相同的scheme、授权和路径Intent对象才会匹配。但是,在过滤器中的路径规则能够包含只要求路径部分匹配的通配符。
<data>元素的type属性指定了数据的MIME类型。它在过滤器中比URI更共同。对于子类型域,Intent对象和过滤器都能够使用“*”通配符---例如,“text/*”或“audio/*”指明可以跟任意子类型匹配。
数据检测会比较Intent对象和过滤器中的URI和数据类型。规则如下:
A. 只有过滤器没有指定任何URI或数据类型的情况下,既没有URI也没有数据类型的Intent对象才能通过检测;
B. 一个包含URI但没有数据类型的Intent对象(并且不能从URI中推断出数据类型)只有跟过滤器中的一个URI匹配,并且同样这个过滤器没有指定数据类型时,才能通过检测。这种情况仅针对不指向实际数据的URIs,如mailto:和tel:。
C. 一个包含了数据类型但不没有URI的Intent对象,只有过滤器也列出相同的数据类型,并也没有指定URI的情况下,才能通过检测。
D. 包含了URI和数据类型的Intent对象(或者是数据类型能够从URI中推断出来)只有它的类型跟过滤器中列出的一个类型匹配,才能通过数据类型部分的检测,如果它的URI部分跟过滤器中的一个URI匹配或者Intent对象有一个content:或file:URI并且过滤器没有指定URI,那么才能能够URI部分的检测。换句话说,如果过滤器仅列出了数据类型,那么一个组件被假设为支持content:和file:数据。
如果一个Intent对象能够通过多个过滤器传递给一个Activity或Service,那么可以询问用户要激活哪个组件。如果没有找到目标,就会产生一个异常。
常见情况
以上数据检测规则中的最后一条(规则d),反映了组件能够获得从文件或内容提供器中获取本地数据的期望。因此,它们的过滤器能够只列出数据类型,并且不需要明确的命名content:和file:方案。这是一种典型的情况。例如,像下面的<data>元素那样,告诉Android,组件能够从一个内容提供器中获取图片并显示它:<data android:mimeType="image/*" />
因为大多数有效的数据是通过内容提供器来配发的,所以,指定一个数据类型但没有URI的过滤器或许是最常见的。
另一个常见的配置是带有方案和数据类型的过滤器。例如,像下面这样的<data>元素会告诉Android组件能够从网络上获取视频并显示它。
<data android:scheme="http" android:type="video/*" />
例如,我们来研究一下当用户点击一个网页上的一个链接时,浏览器应用程序要做的事情。首先,它会试着来显示数据(如果这个链接是一个HTML页,那么就显示)。如果不能显示这个数据,那么就会把方案和数据类型一起放到一个隐式Intent对象中,并试着启动一个能够做这项工作的Activity,如果没有接受者,它就会要求下载管理器来下载数据。然后把数据放在一个内容提供器的控制之下,以便一个潜在的更大的Activity池(那些只带有命名数据类型的过滤器)能够响应。
大多数应用程序都有不引用任何特殊数据就启动刷新的方法。能够初始启动应用程序的Activity都有带有“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>
使用Intent对象进行匹配
Intent对象跟过滤器匹配不仅是要发现要激活的目标组件,而且也发现设备上有关组件集的一些事情。例如,Android系统通过查找所有的拥有指定的“android.intent.action.MAIN”动作和“android.intent.category.LAUNCHER”分类的过滤器的Activity,把它们填充到应用程序的启动器,把用户启动的有效的应用程序显示在屏幕的顶层,然后显示启动器中的那些Activity的图标和标签。类似地,系统通过查找它的过滤中带有“android.intent.category.HOME”的Activity来发现主屏界面。
应用程序能够用于Intent对象匹配的是组类似的方法。PackageManager类中有一组query…()方法,它们返回能够接受一个特殊Intent对象的所有组件,并且还有一组类似的resolve…()方法,用来判断响应一个Intent对象的最好组件。例如,queryIntentActivities()方法返回一个能够执行这个Intent对象要求动作的所有Activity;queryIntentServices()方法类似地返回Service列表。这些方法都不激活组件,它们只是列出能够响应这个Intent对象的所有组件。对于Broadcast Receiver也有类似的方法:queryBroadcastReceivers()。
Note Pad 示例
展开阅读全文