二十一、创建意图过滤器

到目前为止,这本书的重点一直是用户从设备的启动器直接打开的活动。这是让您的活动开始运行并让用户可见的最明显的例子。而且,在许多情况下,这是用户开始使用您的应用的主要方式。

但是,请记住,Android 系统是基于许多松散耦合的组件的。你可能在桌面 GUI 中通过对话框、子窗口等完成的事情通常被认为是独立的活动。虽然一个活动是“特殊的”,因为它显示在启动器中,但是其他活动都需要被访问...不知何故。

“不知何故”是通过意图。

意图基本上是你传递给 Android 的一个信息,说“哟!我想做什么...呃...有事!耶!”“某事”的具体程度取决于具体情况——有时你确切地知道你想做什么(例如,打开你的一个其他活动),有时你不知道。

抽象地说,Android 是关于意图和那些意图的接收者的。所以,现在你已经精通了创建活动,让我们深入了解意图,这样我们就可以创建更复杂的应用,同时成为“好的 Android 公民”

你的意图是什么?

当蒂姆·伯纳斯·李爵士发明超文本传输协议(HTTP)时,他建立了一个动词加地址的 URL 形式的系统。该地址表示一种资源,如网页、图形或服务器端程序。动词指示应该做什么:GET 检索它,POST 将表单数据发送给它进行处理,等等。

意图是相似的,因为它们代表一个动作加上上下文。与 HTTP 动词和资源相比,Android 意图的上下文有更多的动作和组件,但概念仍然是相同的。正如 web 浏览器知道如何处理动词+URL 对一样,Android 知道如何找到处理给定意图的活动或其他应用逻辑。

件意图

意图的两个最重要的部分是动作和 Android 称为的数据。这些几乎完全类似于 HTTP 动词和 URL:动作是动词,数据是一个Uri,比如content://contacts/people/1,表示联系人数据库中的一个联系人。动作是常量,比如ACTION_VIEW(打开资源的查看器)、ACTION_EDIT(编辑资源),或者ACTION_PICK(选择一个可用的项目,给定一个代表集合的Uri,比如content://contacts/people

如果你要创建一个意图,将ACTION_VIEWcontent://contacts/people/1的内容Uri结合起来,并将这个意图传递给 Android,Android 会知道找到并打开一个能够查看该资源的活动。

除了动作和数据Uri之外,您还可以在意图(表示为Intent对象)中放置其他标准,例如:

  • 类别:你的“主要”活动将在LAUNCHER类别中,表明它应该出现在启动菜单上。其他活动可能属于DEFAULT类别或ALTERNATIVE类别。
  • MIME 类型:如果你不知道一个集合Uri,这表示你想要操作的资源的类型。
  • 组件:这是应该接收这个意图的活动的类。以这种方式使用组件消除了对意图的其他属性的需要。然而,它确实使意图更加脆弱,因为它假设了特定的实现。
  • 额外信息:这指的是你想要传递给接收者的其他信息的Bundle,通常是接收者可能想要利用的信息。给定的接收者可以使用哪些信息取决于接收者,并且(希望)被很好地记录下来。

您将在 Android SDK 文档中找到Intent类的标准动作和类别的列表。

意图路由

如前一节所述,如果您在意图中指定目标组件,Android 毫无疑问会将意图路由到哪里,并且它会启动指定的活动。如果目标意图在您的应用中,这可能是好的。绝对不建议将意图发送到其他应用。总的来说,组件名称被认为是应用的私有名称,可能会更改。模板和 MIME 类型是识别您希望第三方代码提供的服务的首选方式。

如果您没有指定目标组件,那么 Android 必须找出哪些活动(或其他接收者)有资格接收该意图。请注意复数 activities 的使用,因为广义的书面意图可能会分解为几个活动。那是...嗯...意图(原谅这个双关语),你会在本章后面看到。这种路由方法被称为隐式路由

基本上,有三个规则,对于给定的活动,所有这些规则都必须符合给定的意图:

  • 活动必须支持指定的操作。
  • 活动必须支持规定的 MIME 类型(如果提供)。
  • 活动必须支持意向中指定的所有类别。

结果是,你要让你的意图足够具体,以找到正确的接收者,没有比这更具体。当我们在本章后面学习一些例子时,这一点会变得更加清楚。

陈述你的意图

所有希望通过意图得到通知的 Android 组件都必须声明意图过滤器,因此 Android 知道哪些意图应该发送给该组件。为此,您需要将intent-filter元素添加到您的AndroidManifest.xml文件中。

所有的示例项目都定义了意图过滤器,这要归功于 Android 应用构建脚本(android create project或 IDE 等价物)。它们看起来像这样:

<?xml version="1.0"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.commonsware.android.skeleton">     <application>         <activity android:name=".Now" android:label="Now">             <intent-filter>                 <action android:name="android.intent.action.MAIN"/>                 <category android:name="android.intent.category.LAUNCHER"/>             </intent-filter>         </activity>     </application> </manifest>

注意activity元素下的intent-filter元素。在此,我们声明这一活动:

  • 是该应用的主要活动
  • LAUNCHER类别中,这意味着它在 Android 主菜单中有一个图标

因为这个活动是应用的主要活动,Android 知道当有人从主菜单中选择应用时,它应该启动这个组件。

欢迎您在意向过滤器中加入多个行动或多个类别。这表明相关联的组件(例如,活动)处理多个不同种类的意图。

很有可能,您还希望让您的次要(非MAIN)活动指定它们所处理的数据的 MIME 类型。然后,如果一个意图是针对那个 MIME 类型的——不管是直接的,还是通过引用那个类型的东西的Uri间接的——Android 将知道组件处理这样的数据。

例如,您可以像这样声明一个活动:

<activity android:name=".TourViewActivity">   <intent-filter>     <action android:name="android.intent.action.VIEW" />     <category android:name="android.intent.category.DEFAULT" />     <data android:mimeType="vnd.android.cursor.item/vnd.commonsware.tour" />   </intent-filter> </activity>

该活动将由请求查看代表一条vnd.android.cursor.item/vnd.commonsware.tour内容的Uri的意图发起。该Intent可能来自同一应用中的另一个活动(例如,该应用的MAIN活动),或者来自另一个 Android 应用中的另一个活动,该活动碰巧知道该活动处理的Uri

窄接收器

在前面的例子中,意图过滤器是在活动上设置的。有时,将意图与活动捆绑在一起并不完全是您想要的,如以下情况:

  • 一些系统事件可能会导致您想要触发服务中的某个事件,而不是某个活动。
  • 一些事件可能需要在不同的情况下启动不同的活动,其中标准不仅仅基于意图本身,而是基于一些其他状态(例如,如果我们获得意图 X,并且数据库具有 Y,则启动活动 M;如果数据库没有 Y,则启动活动 N)。

对于这些情况,Android 提供了接收器,定义为实现BroadcastReceiver接口的类。广播接收器是设计用来接收意图(具体地说,广播意图)并采取行动的一次性对象。

BroadcastReceiver接口只有一个方法:onReceive()。接收者实现那个方法,在那里他们做任何他们想要做的事情。要声明一个接收者,在您的AndroidManifest.xml文件中添加一个receiver元素:

<receiver android:name=".MyIntentReceiverClassName" />

接收器只在处理onReceive()的时间内有效——一旦该方法返回,接收器实例就会被垃圾收集,不会被重用。这意味着接收者在他们能做的事情上有些限制,主要是为了避免任何涉及回调的事情。例如,它们不能绑定到服务,也不能打开对话框。

例外情况是,如果BroadcastReceiver是在一些长期存在的组件上实现的,比如一个活动或服务。在这种情况下,接收方的寿命与其“宿主”的寿命一样长(例如,直到活动被冻结)。但是,在这种情况下,您不能通过AndroidManifest.xml声明接收者。相反,你需要在你的ActivityonResume()回调中调用registerReceiver()来声明对一个意向感兴趣,然后当你不再需要那些意向时,从你的ActivityonPause()中调用unregisterReceiver()

各种场合的意图

随着 Android 每个新版本的推出,Intent类包含的动作数量稳步增长。随着冰淇淋三明治(ICS)4.0 版本的发布,Google 又增加了六个动作,并取消了三个不再需要的动作。Android SDK 文档涵盖了 ICS 中所有可用的 97 个意图动作,您可以在闲暇时阅读。以下是让你思考可能性的一些亮点:

  • ACTION_AIRPLANE_MODE_CHANGED:设备已进入或退出飞行模式。
  • ACTION_CAMERA_BUTTON:相机按钮被按下。
  • 日期已经改变。这对于提醒列表、日历等应用来说很重要。
  • ACTION_HEADSET_PLUG:耳机被连接或移除。这对于音乐播放类应用和类似的应用来说是相当重要的。

你可以开始看到可能性和复杂性。

暂停警告

使用Intent对象传递任意消息有一个问题:它只有在接收者活动时才起作用。引用BroadcastReceiver的文档:

如果在您的Activity.onResume()实现中注册了一个接收者,您应该在Activity.onPause()中注销它。(暂停时你不会收到意图,这将减少不必要的系统开销)。不要在Activity.onSaveInstanceState()中取消注册,因为如果用户在历史堆栈中返回,将不会调用这个函数。

因此,您只能在以下情况下使用Intent框架作为任意的消息总线:

  • 你的接收器并不在乎是否因为没有激活而错过了信息。
  • 您提供了一些方法来让接收者“抓住”它在不活动时错过的消息。
  • 你的收货人在货单上登记了。