十六、Android 意图:应用间编程

在这一章中,我们将深入探究 Android 的意图 。意图被开发者用来处理组成 Android 应用开发的四个主要功能区域内的模块间通信或指令:活动服务广播接收器内容提供者

我们已经了解了所有的活动,因为这些活动包含了你的 Android 应用的前端,包括你的设计、内容、新媒体、用户界面等等。在接下来的三章中,我们将讨论 Android 的其他三个主要功能领域:服务(处理)、广播接收器(消息传递)和内容提供者(数据存储)。

为了能够涵盖 Android 应用开发中的这三个更高级的领域,我们首先需要涵盖意图意图过滤器这个庞大的主题。这是因为 Intents 在实现这些更复杂的“幕后”Android 应用组件时被更多地使用。

意图也可以用于活动,因为我们非常熟悉活动,所以我们将学习如何在活动中使用意图,在接下来的三章中,我们还将学习如何使用服务、广播接收器以及内容供应器来利用意图。

在这一章中,我们将仔细研究 Android Intent 和 Intent Filter 类,以及 Android Intent 的各种特性,以及如何在 Android 应用中声明这些特性、功能、设置、常量和类似的特性。

我们将进一步了解意图过滤器,它允许您在应用被其他应用使用时,自动处理如何在应用中利用您的意图。正如您可能已经猜到的,声明意图过滤器是通过您的 AndroidManifest.xml 文件完成的,使用 <意图过滤器> 标签。

这是 Android 中比较复杂的主题之一,因为它涉及到模块间通信、消息传递、AndroidManifest、过滤器以及 Java 和 XML 格式的类似高级编程主题。

Android 意向信息:首先,全球概述

意图消息是一个 Android OS 工具,在相同或不同应用内的应用组件之间提供后期运行时绑定

使用 android.content.Intent 类实例化 Android Intent 对象,该类是 java.lang.Object 的子类。这意味着 Intent 仅仅是为了它自己独特的目的而开发的,而不是从任何其他类型的 Android 类派生出来的子类。它被打包在 Android 内容包中,你可以从之前概述的包名称中清楚地看到。

之所以将它与 android.content 包打包在一起,是因为 Intents 可以用来快速访问和操作内容提供者(数据库),这一点我们将在下一章中了解。Intent 对象的用途不仅仅是数据库访问,它还可以用于 Android 服务、活动和广播接收器。

Android Developer Reference 将意图定义为要执行的操作的抽象描述。这意味着 Android 设计意图是创建一个 Java 对象类型,可以用来轻松完成通常需要复杂编程代码的任务。

因此,意图本质上是一种编程捷径,它已经内置于 Android 操作系统和编程环境中,以使事情从长远来看更容易。我说“从长远来看”,因为首先我们需要学习如何使用意图和意图过滤器,然后一旦我们理解了它们,它们就会变得强大,使我们成为更高级的 Android 程序员。

众所周知,Intent 对象结构是一个被动的数据结构对象,因为它只是一个被动的数据和指令的集合,它们被捆绑在一个综合的 Java 对象中,可以很容易地在应用的不同功能模块之间传递。

意图对象数据结构应该包含一个描述,该描述包含需要执行的标准操作系统或开发者创建的动作,并且另外传递那些动作需要处理的数据。这通过其组件名传递给 Java 代码模块,指定哪个类是(接收)意图的目标

我们将在本章的后面学习所有这些意向对象数据结构格式。除了这些特定的动作和数据,Android Intent 对象还可以指定数据类型 (MIME)规范,以及类别常量标志,甚至额外的数据对,这些数据对与 Intent 动作需要处理的主数据包相关。在本章的剩余部分,我们将详细了解意图对象的各种功能领域。

Android 意图实现:三种不同类型的意图用法

Intent 对象有三种不同的用途,每一种都可以在 Android 操作系统中调用活动、服务和广播接收器之间的模块间通信。

然而,一种类型的意图,即意图对象,可以用于操作系统的三个特定区域中的每一个。意图对象使用的不同分类类型都不允许互换使用,以避免处理错误。

当然,您可以将您的意图对象命名为您喜欢的任何名称,但是您用来在您的应用模块中传递您的意图对象的意图处理方法调用的类型将最终决定该意图对象将涉及的使用类型,而不管您在构造意图对象时如何命名它。意图的使用类型决定了它是什么类型的意图(或者更准确地说,意图用于什么类型的目标)。

使用不同的意图对象方法调用来保证意图对象的这些不同使用不会彼此混淆,也就是说,它们不会与意图对象的任何其他实现相交、干扰、冲突或错误地被其使用。

例如, startActivity( ) 使用意图对象来启动活动,而 startService( ) 使用意图对象来启动服务。要将意图对象发送到广播接收机,可以使用 broadcastIntent( ) 方法,因此用于分发意图对象的方法决定了它的实现。

出于这个原因,我们将分别讨论意图对象的每个使用场景,以便我们可以看到基于意图的通信与活动、服务和广播接收方消息之间的区别。

在这本书的最后一部分,我们还将练习在 Android 操作系统的四个主要领域中使用意图对象。因为你已经是 Activity 的专家了,所以在这一章中,我们将看看使用 Intent 对象和 Activity 子类的代码例子。在接下来关于 Android 服务、广播接收器和内容提供者的三章中,我们还将看到 Intent 对象是如何被用来启动和控制 Android 中的其他功能区域的。

活动

使用 Context.startActivity( )Activity . startactivityforresult()将与活动一起使用的意图对象传递给每个活动,以启动活动,或要求现有活动执行一些特定于应用的编程任务。

可以使用 Activity.setResult( ) 方法将意图传递回去,以便首先使用 Activity . startactivityforresult()方法将信息返回给发起意图通信的调用活动。

安卓服务

使用 Context.startService( ) 方法调用将与 Android 服务一起使用的意图对象传递给服务子类,以启动 Android 服务,或者向由启动的服务传递新指令。我们将在本书的下一章学习所有关于服务的知识。

还可以使用 Context.bindService( ) 方法传递一个 Intent 对象,以在调用应用组件和绑定的服务之间建立连接(绑定)。如果服务还没有启动并运行,意向者也可以发起绑定服务。

我们将在本书的下一章讨论绑定的服务,以及启动的服务和混合服务。我们将讨论意图在这个上下文中是如何使用的,因此,我在这里讨论它们是为了上下文和概述不同类型的意图。

广播接收机

传递给任何 Android 广播接收器方法的 Intent 对象被传递给所有感兴趣的 Android 广播接收器。大量的 Android 操作系统广播将源自 Android 系统代码,这在复杂的完整版本的 Linux 操作系统中是可以预料的。

Intent 方法调用的广播接收器特定版本包括 Context.sendBroadcast( ) 方法、context . sendorderedbroadcast()方法和context . sendstickybroadcast()方法,我们将在第 18 章中介绍。

因为每种类型的意图都有一个独特的调用方法,Android 系统可以很容易地定位需要响应每个特定意图对象的适当的应用活动、服务或广播接收器。

由于 Android 意向系统的设置方式,这些意向信息系统之间没有重叠。广播接收机的意图只发送给广播接收机,不会发送给 Android 活动或服务。使用传递的意图对象。startActivity( ) 方法只传递给一个 Activity 子类,而从不传递给 Service 子类或 Broadcast Receiver 子类,等等等等。

安卓意图结构:安卓意图剖析

意图对象包含一个的信息。从第一次让 Eclipse ADT 为您生成 Android 应用开始,您就已经熟悉了 Android Bundle 对象,因为 Bundle 对象在每个 Android 应用中都有使用,用于保存 Activity 子类的实例状态信息。也许你记得看到下面这行代码:

public void onCreate(Bundle savedInstanceState) {onCreate Method Logic is in here}

意图包含接收该意图的应用组件感兴趣的信息。这包括诸如接收应用组件要采取的动作的信息,以及需要采取行动的数据。意图还可以包括 Android 操作系统本身感兴趣的信息,例如接收组件的哪个类别应该处理意图,或者甚至是关于如何启动目标活动的指令。

一个 Android Intent 可以包含以下七个功能部分:

  1. 组件名:目标组件的全限定类名
  2. 动作:命名动作或预定义动作常数的字符串
  3. 数据:要操作的数据对象(文件)的 URI
  4. 类别:字符串命名类别,或安卓类别常量
  5. 数据(MIME)类型:要操作的数据的 MIME 类型
  6. 附加:用于意图的附加信息的键值对
  7. 标志:在意图类中定义的各种标志

让我们深入这七个在意图对象中非常重要的领域,看看为什么每个领域都很重要,以及意图对象的这些领域如何在不同类型的 Android 意图对象中实现。

意图对象组件:指定组件名称参数

您可以为您的意图对象指定的最重要的事情是您显式指定给该意图对象的句柄的应用组件的组件名称

当您在意图对象中指定该组件名称时,您也创建了一个显式意图对象。我们将在本章接下来的几节中更详细地讨论这个明确的意图。

Intent 对象中的这个字段是一个 ComponentName 对象,它包含目标组件的全限定类名的组合。在我们的 Hello World 应用中,这可能是“chapter.two.Hello_World”。“攻击行星”为攻击行星活动的子类。

注意,包名是在 AndroidManifest XML 文件中为组件所在的每个应用设置的。如果您调用的组件位于不同的应用中,组件名的包部分和 AndroidManifest.xml 中设置的包名不一定要匹配。Intents 足够灵活,可以跨不同的应用进行通信,也可以在单个应用内部进行通信。

这里需要注意的是,组件名是可选的。如果设置了它,您的意图对象将被交付给指定类的实例。如果没有指定组件名称,Android 将查看您的意图对象中的所有其他信息来定位合适的目标。

在这种情况下,意图变成了隐含意图,因为 Android 必须暗示如何应用意图,通过推断将意图应用到哪个组件。这是通过查看意图中的所有其他信息并推断在哪里处理意图来实现的。夏洛克·福尔摩斯肯定会为自己是安卓开发者而自豪。

在你的意图对象中,如果你想指定一个组件名,它将通过使用来设置。setComponent( ) 方法,或者,通过使用。setClass( ) 方法,或者使用。setClassName( ) 方法。

另一方面,组件名可以通过使用 Intent 类的从 Intent 对象中读取。getComponent( ) 方法。这用于从 Intent 对象中提取组件名称信息,以便它可以与给定的应用组件匹配,如果匹配就进行处理。

意图对象动作:指定动作参数

使用命名要执行的动作的字符串或用于指定 Android 操作系统内部已经定义的那些动作的 ACTION_ constant 来指定意图动作参数。为所有 Android 活动子类定义的动作常量包括以下动作:

ACTION_MAIN
ACTION_VIEW
ACTION_ATTACH_DATA
ACTION_EDIT
ACTION_PICK
ACTION_CHOOSER
ACTION_GET_CONTENT
ACTION_DIAL
ACTION_CALL
ACTION_SEND
ACTION_SENDTO
ACTION_ANSWER
ACTION_INSERT
ACTION_DELETE
ACTION_RUN
ACTION_SYNC
ACTION_PICK_ACTIVITY
ACTION_SEARCH
ACTION_WEB_SEARCH
ACTION_FACTORY_TEST

在与广播接收器一起使用的意图的情况下,动作实际上指定了在过去发生的动作(已经发生了),并且因此被报告而不是被请求。Intent 类定义了许多广播接收器动作常量,包括:

ACTION_TIME_TICK
ACTION_TIME_CHANGED
ACTION_TIMEZONE_CHANGED
ACTION_BOOT_COMPLETED
ACTION_PACKAGE_ADDED
ACTION_PACKAGE_CHANGED
ACTION_PACKAGE_REMOVED
ACTION_PACKAGE_RESTARTED
ACTION_PACKAGE_DATA_CLEARED
ACTION_UID_REMOVED
ACTION_BATTERY_CHANGED
ACTION_POWER_CONNECTED
ACTION_POWER_DISCONNECTED
ACTION_SHUTDOWN

值得注意的是,您还可以定义自己的自定义操作字符串,以便在应用中激活自己的自定义组件。这允许你在你的应用中开发你自己的定制意图系统。

您自己设计和命名的操作应该包括一个应用包作为前缀,然后是您自己创建的操作常量。以我们自己的 Hello World 应用为例,您可以对操作使用以下完整路径名:

chapter.two.hello_world.ACTION_SHOW_PLANET_STATUS

意图动作参数在确定如何构建意图的其余信息参数时极其重要。

因此,最好使用尽可能具体的动作常数。您还应该尽可能将动作常数与意图对象的其他信息字段紧密关联,如数据、类别、数据类型和标志。

您想要做的是,为您的定制应用组件将要处理的意图对象定义一个完整的协议和一组常量,而不是孤立地定义一个动作。

您的意图对象中的动作常量或字符串值应通过使用来设置。setAction( ) 方法。相反,应该使用从意图对象中读取动作常量或字符串值。getAction( ) 方法。

意图对象数据:发送数据以供操作

意图对象数据参数包含一个 URI 对象,用于使用指定的动作处理数据。正如您可能已经猜到的那样,各种类型的动作与逻辑上对应的数据规范类型一起使用,这些数据规范类型与传递的动作参数非常匹配。

例如,如果传递给意图对象的动作参数的动作是一个 ACTION_DIAL 常量,那么数据参数将包含显示在智能手机拨号区域的电话号码。

如果 Intent action 常量参数是一个 ACTION_CALL ,那么 data 参数将是一个 URI 对象,该对象包含带有电话号码的 tel: 前缀引用,您的应用希望将该电话号码作为一个电话呼叫。

如果意图动作常量是 ACTION_VIEW 并且使用的数据参数是 http: URI,那么接收活动将被调用来下载和显示 URI 引用要查看的任何数据。

意图对象类别:使用类别常量参数

意图对象的类别参数包含一个字符串对象,该对象指定关于处理意图的组件种类的附加信息。任何数量的类别描述都可以放在 Intent 对象中,以帮助接收组件。

Android Intent 类还定义了许多类别常量,就像它为活动和广播接收器定义 ACTION_ constants 一样。这些类别常量都以单词 CATEGORY_ 开头,包括:

CATEGORY_DEFAULT
CATEGORY_BROWSABLE
CATEGORY_TAB
CATEGORY_ALTERNATIVE
CATEGORY_SELECTED_ALTERNATIVE
CATEGORY_LAUNCHER
CATEGORY_INFO
CATEGORY_HOME
CATEGORY_PREFERENCE
CATEGORY_TEST
CATEGORY_CAR_DOCK
CATEGORY_DESK_DOCK
CATEGORY_LE_DESK_DOCK
CATEGORY_HE_DESK_DOCK
CATEGORY_CAR_MODE
CATEGORY_APP_MARKET

Intent 类有几个方法允许您使用类别参数,包括。addCategory( ) 方法,它在一个意图对象中添加一个类别,。removeCategory()、,前者在添加类别后将其删除。getCategories(),,它检索当前包含在 Intent 对象中的所有类别的集合。

意图对象数据类型:设置 MIME 数据类型参数

当将您的意图与能够处理给定的数据类型的组件相匹配时,了解您正在处理的数据值的分类或类型(数据的 MIME 类型)通常是非常重要的(当然,除了数据的 URI 位置之外)。

MIME 代表多用途互联网邮件扩展 ,它最初是为电子邮件服务器开发的,用于定义它们对不同类型数据的支持。

MIME 现在已经扩展到支持的数据和内容类型的其他平台定义,以及通信协议(如 HTTP)数据类型定义,还扩展到 Android OS,在这里也定义内容数据类型。可以说 MIME 已经成为无数计算环境中定义内容数据类型的主要标准。MIME 数据类型定义的示例包括以下常用的内容类型:

 Content-Type: text/html (HTML Data)
 Content-Type: video/mp4 (MPEG Data)
 Content-Type: image/png (PNG8 Data)
 Content-Type: audio/mp3 (MPEG Data)
 Content-Type: application/pdf (.PDF Data)
 Content-Type: multipart/x-zip (.ZIP Data)

为了举例说明为什么要在 Intent 对象中声明 MIME 数据类型,您可能希望确保显示视频数据的应用组件不会被错误地调用来播放音频文件,就像您不希望播放音频的应用组件(如我们的 playSample()方法)被错误地调用并传递视频或图像数据文件来播放一样。

在大多数情况下,您的数据类型可以从传递的 URI 中推断出来。对于 Android content:// URIs 来说尤其如此,它表明数据位于你的设备上的什么位置,并且由内容供应器控制。我们将在本书最后一节的后续章节中介绍 Android 内容供应器。

数据类型也可以是在你的意图对象中显式设置的数据类型。利用意向对象。setData( ) 方法只指定数据,作为 URI,而使用。setType( ) 方法调用仅使用 MIME 类型指定数据。

第三种方法叫做。setDataAndType( ) 结合了这两种方法,并将数据指定为 URI 和 MIME 类型。可以使用意图对象读取 URI。getData( ) 方法和数据类型可以通过使用意图对象读取。getType( ) 方法。

意图对象附加:在意图对象中使用附加

意向对象还可以包括键值对形式的额外内容(数据)。这些用于传递附加信息,这些信息应该被包括在内,以便于对意图对象进行正确的组件处理。

Android Intent 类还定义了许多额外的常量,就像它为活动和广播接收器定义 ACTION_ constants,以及 CATEGORY_ constants 一样,正如我们在上一节中看到的。这些额外常量始终以单词 extra 开头,包括以下内容:

EXTRA_ALARM_COUNT
EXTRA_BCC
EXTRA_CC
EXTRA_CHANGED_COMPONENT_NAME
EXTRA_DATA_REMOVED
EXTRA_DOCK_STATE
EXTRA_DOCK_STATE_HE_DESK
EXTRA_DOCK_STATE_LE_DESK
EXTRA_DOCK_STATE_CAR
EXTRA_DOCK_STATE_DESK
EXTRA_DOCK_STATE_UNDOCKED
EXTRA_DONT_KILL_APP
EXTRA_EMAIL
EXTRA_INITIAL_INTENTS
EXTRA_INTENT
EXTRA_KEY_EVENT
EXTRA_ORIGINATING_URI
EXTRA_PHONE_NUMBER
EXTRA_REFERRER
EXTRA_REMOTE_INTENT_TOKEN
EXTRA_REPLACING
EXTRA_SHORTCUT_ICON
EXTRA_SHORTCUT_ICON_RESOURCE
EXTRA_SHORTCUT_INTENT
EXTRA_STREAM
EXTRA_SHORTCUT_NAME
EXTRA_SUBJECT
EXTRA_TEMPLATE
EXTRA_TEXT
EXTRA_TITLE
EXTRA_UID

我们已经看到,一些动作与某些类型的数据 URIs 成对出现;类似地,一些动作通常与特定类型的附加动作成对出现。

例如,ACTION_TIMEZONE_CHANGED Intent 对象操作参数具有标识新时区的额外“时区”, ACTION_HEADSET_PLUG 具有指示耳机现在是插入还是拔出的额外“状态”,以及耳机类型的额外“名称”。

如果你要发明一个 ACTION_SHOW_PLANET_STATUS 动作,那么行星状态值将使用 EXTRA_STATUS_PLANET 键值对来设置。

意向对象有一系列的。putExtra( ) 插入各种类型的额外数据参数的方法和类似的一组。getExtra( ) 读取额外数据参数的方法。有趣的是,这些 Java 方法也与捆绑对象中使用的方法类似。

值得注意的是,这些额外的键值对可以通过使用作为 Android Bundle 对象来安装和读取。方法和。getExtras( ) 方法。如果你使用了大量的额外功能,这可能是最有效的设置方式,毕竟这是 Bundle 对象最初的用途。

意图对象标志:对意图对象使用标志

可以包含在 Android Intent 对象中的最后一种参数称为标志参数。标志是布尔值,作为一名程序员,你可能非常熟悉。标志对于以高度数据紧凑的方式设置状态和开关非常有用。

就意图对象而言,大多数标志参数会指示 Android 系统如何以某种方式启动或处理该活动,或者可能在启动后如何处理该意图。然而,Intent object flag 参数是足够开放和灵活的,您可以以任何您认为适合您的应用的创造性方式来使用它们,所以发挥您的创造性吧!

显性与隐性意图:使用哪种意图类型

安卓意图对象有两种分类:显性意图隐性意图。显式意图是两者中较容易在你的应用中使用的,而隐式意图要复杂得多,通过意图过滤器,你可以允许其他开发者通过他们的意图对象隐式地使用你的应用组件。

明确的意图

正如我前面提到的,显式 Intents 对象使用组件名参数指定目标应用组件。这之所以称为显式,是因为您的组件(类)命名模式一般不会提供给其他应用的开发人员,所以如果允许他们使用 Intent 对象访问您的代码,他们必须通过组件名参数获得要调用的类名。

因此,显式意图通常主要用于应用内部消息传递(或内部应用消息传递)。一个很好的例子是启动一个从属服务类的活动或者启动一个相关的活动,我们将在本章的后面看到。

Android 总是向组件名中指定的目标类的实例传递一个明确的意图。当您使用显式意图时,除了组件名称之外,意图对象中的任何内容对于确定哪个应用组件获得意图都无关紧要。

另一方面,隐式意图不命名目标(意图对象中的组件名称参数为空)。

隐含的意图

隐式意图通常用于激活其他应用中的组件或 Android 操作系统中更一般化的特性或功能,在这些应用中很容易推断出需要什么,例如通过应用用户界面而不是通过操作系统电话拨号实用程序为用户拨打或呼叫电话号码。

当接收到隐式意图对象时,完全需要遵循不同的方法。在没有明确指定目标的情况下,Android 操作系统必须确定处理该意图对象的最佳应用组件。

这被称为意图解析,因为 Android 操作系统正在为你解析你的意图对象。意向对象解析可能导致启动一个活动类来显示一个新的用户界面,或者启动一个服务类来执行所请求的动作,或者甚至激活一个 Android 广播接收器来响应您的广播通知。

意图解析可以通过几种不同的方式来执行。如果 Intent 的内容清楚地表明了需要做什么,比如说在 Intent 对象中有一个 action_MAIN 的 ACTION 参数和一个 category_HOME 的 CATEGORY 参数常量,那么主屏幕将在 Android 设备的屏幕上启动。另一种解决包含更多定制动作的意图的方法是使用一个意图过滤器,开发者可以定义它来帮助其他应用将意图发送到它们适当的组件进行处理。

隐式意图解析:引入意图过滤器

在缺少填充有 Android 指定意图常数的意图对象的情况下(我们在本章前面已经详细讨论过了),通常通过将任何提交的意图对象的全部内容与该应用的 AndroidManifest.xml 文件中的现有意图过滤器定义进行比较来执行隐式意图解析

意图过滤器是使用 XML 标记标签/参数逻辑在 AndroidManifest 内部创建的复杂逻辑结构。它们与您的应用中可能接收意图的组件相关联。

意图过滤器在涉及意图对象时执行一些重要的功能。首先,它们概述了您的应用组件所体现的功能,其次,它们用于指定它可以处理的意图的特征和限制。

意图过滤器 XML 定义使您的应用组件更有可能成功接收指定类型的隐式意图。在下一节中,我们将讨论可用于为您的应用定义意图过滤器解析结构的标签。

请注意,如果您的应用组件没有在您的 AndroidManifest.xml 文件中定义任何意图过滤器,它将只能接收显式意图。您的应用当然可以继续使用我们之前提到的 Android 操作系统意图常量发送隐含意图,因为这些是使用操作系统的意图过滤器结构定义的。

定义了意图过滤器的组件可以接收显式和隐式意图。

针对意图过滤器测试意图对象时,会分析该意图对象的三个主要参数。它们是动作(或动作常量)、数据(URI 和 MIME)和类别。

如果你想一想,这三个主流意图参数中有相当多的信息,正如我们之前所了解的,所以有了这些和意图过滤器结构(定义)来告诉我们如何应用这些信息参数,操作系统应该能够成功地解析任何和所有意图对象。

有趣的是,Intent 对象中的 extras 和 flags 参数在确定哪个应用组件接收给定意图的意图解析过程中完全不起作用。

接下来,让我们仔细看看 Android标签及其参数和子(嵌套)标签,以便我们可以看到如何在 AndroidManifest.xml 文件中指定意图过滤器。关于<意图过滤器>标签结构、参数和子标签的更多详细信息可以在 Android 开发者网站上找到,网址如下:

http://developer.android.com/guide/topics/manifest/intent-filter-element.html

创建意图过滤器:使用 XML 标签

虽然自定义意图过滤器结构的编码超出了这本介绍 Android 应用开发的入门书籍的范围,但我将在本节中概述这些<意图过滤器>标签和结构,以便您理解这些结构是如何实现的,在您的 AndroidManifest.xml 应用引导文件中使用 XML 标记。

利用一个标签结构或层次来指定活动、服务或广播接收器子类可以在应用中响应的意图对象的类型。

一个标签在其父组件标签容器的上下文中声明其功能,父组件标签容器将是一个 <活动> 标签、一个 <服务> 标签或一个 <接收者> 标签,因为这些是主要的应用组件子类,在这些子类下您可以设计您的定制意图过滤器层次结构。在接下来关于服务、广播接收机和内容供应器的三章中,我们将学习这些标签。

位于其父标签内部的标签定义了一个意图对象可以为那个或做什么,以及一个组件类可以处理哪些类型的广播。

标签结构定义了它所包含的组件标签,因此它知道它可以接收指定类型的意图,并允许操作系统过滤掉那些对特定组件 XML 定义没有意义的意图。

正如你在本书前几章的 XML 声明中已经看到的,你很快就会看到,使用和广播组件,所有核心 Android 组件的类别总是在 AndroidManifest.xml 文件中定义。

因此,正如您所看到的,这种方法与首先为您的 Android 应用设置和配置组件的方式是高效和无缝的。因此,如果您需要添加一个层次结构,您只需对其进行模块化编码,将其嵌套在您的一个组件标签内,就在现有组件标签的子标签和参数之下。

标签的大部分内容由其子标签描述,包括 <动作> 标签、 <类别> 标签、 <数据> 标签元素。正如你可能想象的那样,< intent-filter >标签的这些子标签包含了在任何给定的 intent 对象的每个参数槽中寻找什么的定义。

这三个子标签中唯一一个在 <动作> 标签中绝对需要的子标签,它将定义采取什么动作来解析(完成)意图对象的消息或任务。< intent-filter >标签本身也可以有三个基本参数;一个用于图标图形,一个用于文本标签,一个给它一个优先级值

接下来,让我们在我们的 Hello_World Android 应用中实际实现一个 Intent 对象,看看所有这些如何在我们现有的应用中协同工作。

在 Hello World 中使用意图对象启动活动

让我们创建一个名为 TimePlanet 的新活动子类,我们可以使用 ConfigPlanet 活动子类中的 Intent 对象调用它。我们将创建一个新的 Activity 子类,为它创建一个新的 XML 用户界面屏幕布局,将其添加到我们的 AndroidManifest.xml 文件中,以便 Android OS 知道它的存在,将用户界面元素添加到我们的 Configure a Planet Activity 中,以调用新的 TimePlanet Activity,然后编写实现 Intent 对象的 Java 代码,并使用它来调用(启动)TimePlanet 活动及其原子钟用户界面。

打开 Eclipse,然后打开你的 Hello World 项目文件夹,右键点击 chapter.two.hello_world 包名文件夹,最后选择新建image菜单序列如图图 16-1 所示。

9781430257462_Fig16-01.jpg

图 16-1 。在 Eclipse 中创建新的image类来保存我们的 TimePlanet.java 活动子类 Java 代码

这将打开新的 Java 类对话框,如图 16-2 所示,我们可以填写所有必要的参数来精确地创建我们希望 Eclipse 为我们生成的 Java 类的类型,在这种情况下,它是一个活动子类,将保存我们的行星时间用户界面元素。

9781430257462_Fig16-02.jpg

图 16-2 。命名我们的 TimePlanet.java 类,选择一个 android.app.Activity 超类和公共修饰符

源文件夹:字段中应该已经有了您的 Hello_World/src 项目源代码文件夹。因为您右键单击包文件夹来调用这个新的 Java 类对话框,所以 Eclipse 会为您推断出文件夹信息。同样的,包:字段也应该自动填入你的包名。

接下来将您的 TimePlanet 类名放入 Name: 字段,然后单击浏览。。。按钮位于超类:字段的右侧,这样我们可以选择我们想要从中派生出新类的类的类型,在本例中,我们想要使用 Activity 类。

图 16-2 右侧所示的超类选择对话框打开后,在选择类型:字段中输入一个一个字符,选择 Activity - android.app 选择选项后,点击 OK 按钮。一旦你回到新 Java 类对话框,点击完成按钮。

一旦你完成了这个新的 Java 类工作过程,Eclipse ADT 应该已经在 IDE 编辑窗格的中心部分为你打开了一个新的编辑标签,名为 TimePlanet.java(参见图 16-3 )。

9781430257462_Fig16-03.jpg

图 16-3 。在 Eclipse 中打开一个新创建的 TimePlanet.java 活动子类,显示导入语句

应该已经为您编写了四行 Java 代码,如下所示:

package chapter.two.hello_world;
import android.app.Activity;
public class TimePlanet extends Activity {
}

这些声明了 chapter.two.hello_world 包名和一个 import 语句,导入一个 android.app.Activity 包及其相关类,供您在即将编写的 TimePlanet.java Activity 子类中使用。

您的名为 TimePlanet 的公共类也应该为您声明,并且应该使用 extends 关键字对 Activity 类进行子类化,并为您准备好开始编写 Java 代码的基础结构。

让我们编写标准代码来创建活动屏幕,并将其内容视图设置为 XML 用户界面设计文档,这是我们接下来要编写的。

首先,我们使用名为 savedInstanceState 的 Android Bundle 对象编写受保护的类 onCreate(),在该类中,我们使用 super 关键字从父 Activity 超类调用 onCreate()方法,并在 onCreate()方法调用中传递 savedInstanceState 变量,使用以下 Java 代码行:

super.onCreate(savedInstanceState);

接下来,我们将使用setContentView()Activity 类方法将我们的内容视图设置为一个 XML 文件,我们接下来将创建该文件,它将保存使用 XML 标记的用户界面定义。

Java 代码的 setContentView()方法调用行如下所示:

setContentView(R.layout.activity_time);

注意在图 16-4 中,Eclipse IDE 错误标记了 activity_time.xml 文件引用;这是因为我们还没有创建这个文件,而且因为我们接下来要这样做,所以我们现在可以忽略这个突出显示的错误。

9781430257462_Fig16-04.jpg

图 16-4 。编写 onCreate()方法和 setContentView()方法来访问 activity_time.xml UI 布局

接下来,我们将创建我们的 XML 用户界面,这样我们就可以解决这个错误,然后一旦完成,我们将返回到这个编辑选项卡,并编写实现我们的意图对象的代码。

为我们的 TimePlanet.java 活动创建 linear layout XML

接下来我们需要做的是右击项目资源文件夹中现在熟悉的 /res/layout 文件夹,然后选择新建image Android XML 文件菜单命令序列来启动如图图 16-5 所示的新建 Android XML 文件对话框。

9781430257462_Fig16-05.jpg

图 16-5 。创建一个新的image Android XML 文件和 LinearLayout 根元素

选择一个资源类型:布局项目:名称 Hello_World 并将文件命名为 activity_time 以匹配我们的活动 XML 文件命名约定。

接下来,选择根元素: LinearLayout类型,然后单击 Finish 按钮,这将创建我们的 activity_time.xml 文件,以及父 LinearLayout 标签。一旦你点击 Finish,Eclipse 会自动在 IDE 的中间部分打开一个编辑窗格,显示它为你编写的 XML 标记,如图 16-6 所示。

9781430257462_Fig16-06.jpg

图 16-6 。在 Eclipse 中打开我们的 activity_time.xml 布局 xml 文件,并编辑< LinearLayout >标签

现在,我们可以添加我们的 LinearLayout 参数,以及文本标题元素、模拟时钟元素和按钮用户界面元素的几个子标签,当我们使用完 Planet Time 活动时,它会将我们返回到 Configure a Planet 活动。接下来,让我们单击 IDE 底部的图形布局编辑器选项卡,以便添加一个 AnalogClock UI 小部件。

打开图形布局编辑器后,点击屏幕左侧控件面板中的时间&日期抽屉,打开那个装满控件图标的抽屉,就可以看到模拟时钟控件,如图图 16-7 所示。我还在截图中展示了当您将模拟时钟小部件拖放到 Eclipse 中央工作区显示的空白应用屏幕模拟上时,您的屏幕看起来是什么样子。

9781430257462_Fig16-07.jpg

图 16-7 。使用 Eclipse 中的图形布局编辑器选项卡在 UI 屏幕上拖放一个 AnalogClock 小部件

接下来,我们需要为行星时间屏幕创建两个字符串常量数据值,一个标题(标题文本)以及模拟时钟小部件下方按钮上的文本标签,如图图 16-8 所示。这两个<字符串>常量的 XML 标记应该如下所示:

<string name="time_caption_value">Home Planet Earth Time</string>
<string name="time_button_value">Return to Planet Configuration</string>

9781430257462_Fig16-08.jpg

图 16-8 。将 activity_time.xml 文件所需的两个字符串常量添加到我们的 strings.xml 文件中

在我们使用完行星的原子钟功能后,我们将使用按钮返回到“配置行星”活动。

现在,我们准备在 activity_time.xml 用户界面 xml 规范中配置 LinearLayout 父标记和子标记。首先,我们需要向 LinearLayout 标签本身添加两个参数,这将允许我们从 Java 代码(android:id)控制它,并为它提供我们在本书前面创建的壮观的背景图像(android:background)。

给 LinearLayout 添加以下两个参数,如图图 16-9 所示:

android:id="@+id/timePlanetScreen"
android:background="@drawable/trans_stars_galaxy"

9781430257462_Fig16-09.jpg

图 16-9 。为 TextView、AnalogClock、Button 和 LinearLayout 标签添加布局和文本参数

接下来我们需要添加格式化参数到 < AnalogClock > 标签,我们使用图形布局编辑器创建了这个标签,如图 16-7 中所示。让我们给它一个地球的背景图像,顶部 20 度倾斜,在屏幕上居中,确保我们的布局宽度和高度设置为 wrap_content 。我们可以使用以下 XML 参数完成所有这些事情:

android:id="@+id/analogClock1"
android:background="@drawable/earth"
android:layout_marginTop="20dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

接下来,让我们添加我们的 < TextView > 用户界面元素,在 GLE 或 XML 编辑器中,并将其配置为匹配我们的等离子背景的颜色,具有 25sp 的大文本大小和 40°的顶部倾斜边距,还将其居中,并确保它也使用 wrap_content 值,使用以下标记:

android:text="@string/time_caption_value"
android:textColor="#FFCCAA"
android:textSize="25sp"
android:layout_gravity="center"
android:layout_marginTop="40dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

最后,我们需要添加一个 <按钮> 用户界面元素,要么在 GLE 中,要么在 XML 编辑器中,并配置它以匹配我们的等离子体背景的颜色,上边距倾斜 20 °,并将其居中,确保它也使用标准的 wrap_content 布局值,使用这个标记:

android:id="@+id/timeButton"
android:textColor="#FFCCAA"
android:text="@string/time_button_value"
android:layout_marginTop="20dp"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

我们将颜色、间距和大小值与其他活动用户界面屏幕设计相匹配,以保持 Hello World 应用中所有用户界面屏幕的一致外观。整个用户界面设计 XML 标记可以在图 16-9 中看到。

接下来,我们将看看需要在我们的 AndroidManifest.xml 文件中做些什么来准备我们的应用以支持这个新的活动,并使用 label 参数在活动屏幕的顶部放置一个定制的屏幕标题。

为我们的 TimePlanet 活动配置 AndroidManifest.xml

在我们开始向 AndroidManifest.xml 文件添加标记之前,我们需要创建一个名为activity _ title _ time _ planet的字符串常量值,如图图 16-10 所示。

9781430257462_Fig16-10.jpg

图 16-10 。为 TimePlanet.java 活动的屏幕标签添加一个<字符串>标签和参数

在 Eclipse 的编辑选项卡中打开 strings.xml 文件,添加一个新的 < string > 标签,其名称值和文本数据值如下:

<string name="activity_title_time_planet">Hello World - Planet Earth Time</string>

接下来,我们将把 TimePlanet 活动子类的标签添加到应用的 AndroidManifest.xml 文件中。右键点击图 16-11 中所示的 Android Manifest 文件,并从菜单中选择打开,这样我们就可以在编辑窗格中编辑文件的内容,也显示在图 16-11 的中间部分。

9781430257462_Fig16-11.jpg

图 16-11 。为新的 TimePlanet.java 活动子类添加一个<活动>标签和参数

添加一个带有指定活动类名的 android:name 和引用我们刚刚在 strings.xml 文件中创建的字符串常量的 android:label 参数的标签。标签和参数 XML 标记应该如下所示:

<activity       android:name="chapter.two.hello_world.TimePlanet"
                android:label="@string/activity_title_time_planet" />

既然我们已经在应用清单中告诉了 Android OS 我们的新活动,我们需要在我们的 Configure a Planet Activity 屏幕中添加一个按钮用户界面元素。一旦我们将这个 XML 添加到 activity_config.xml 用户界面屏幕定义中,我们就可以添加声明和实例化这个按钮元素的 Java 代码。完成后,我们可以添加事件处理方法,该方法包含我们需要发送到 TimePlanet.java 活动的意图对象,以便在应用用户忙于配置他们的行星特征时,在他们希望查看他们的行星地球原子钟的任何时候启动该活动。

向 activity_config.xml 文件添加一个原子钟按钮标签

我们需要做的第一件事是为添加到 activity_config.xml UI XML 定义中的原子钟按钮用户界面元素创建我们的常量值。我们将字符串常量命名为 button_name_time ,然后将其值设置为原子钟,如图图 16-12 所示。现在我们可以在我们的 activity_config.xml 文件中添加引用这个字符串常量的 XML 按钮 UI 元素标记。

9781430257462_Fig16-12.jpg

图 16-12 。将名为 button_name_time 的原子钟按钮标签字符串常量添加到 strings.xml 文件中

因为我们希望原子钟 UI 按钮元素位于文本数据输入字段之下(这样就不会与配置用户界面按钮混淆),我们将把它放在第二个 LinearLayout 容器标签中,如图图 16-13 所示。这样我们就有更少的参数需要把它放到合适的位置,只有一个 MarginTop 和一个 MarginLeft 和一个 textColor 来确保它匹配我们的空间等离子体和屏幕文本标题颜色。我们还引用我们的字符串常量,并将 ID 设置为时间按钮

9781430257462_Fig16-13.jpg

图 16-13 。在 activity_config.xml 的第二个 LinearLayout 容器的底部添加一个按钮标签

timeButton 按钮元素的 XML 标记应该如下所示:

<Button android:id="@+id/timeButton
        android:text="@string/button_name_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#FFFFFF"
        android:layout_marginTop="11dp"
        android:layout_marginLeft="11dp" />

既然我们已经在 Configure a Planet 活动中添加了一个原子钟 UI 按钮对象,现在是时候进入 Java 代码,实例化该按钮对象,这样我们就可以添加事件处理代码,允许我们将一个 Intent 对象发送到我们的 TimePlanet.java 活动类并启动它!

用 Java 为我们的 ConfigPlanet.java 活动编码一个意图对象

让我们折叠其他按钮对象代码块,如图 16-14 中的加号所示,并实例化一个新的 timeButton 对象,并使用下面一行 Java 代码将其引用到我们的 XML:

Button timeButton = (Button) findViewById(R.id.timeButton);

9781430257462_Fig16-14.jpg

图 16-14 。在 ConfigPlanet 活动的 timeButton 事件处理程序中编写 callTimeIntent Intent 对象

接下来,我们需要使用将事件监听器附加到按钮对象。setOnClickListener( ) 方法,并创建一个新视图。OnClickListener( ) 事件处理方法使用的四行代码如图图 16-14 所示:

timeButton.setOnClickListener(new View.OnClickListener() {
    public void onClick(View view) {
        Intent callTimeIntent = new Intent(view.getContext(), TimePlanet.class);
        startActivityForResult(callTimeIntent, 0);     } });

正如您所看到的,我们在 onClick()事件处理方法中构造了 Intent 对象,使用了下面一行 Java 代码:

Intent callTimeIntent = new Intent(view.getContext(), TimePlanet.class);

这声明了 Intent ,将其命名为 callTimeIntent ,并使用 new 关键字来构造 Intent,将它传递给两个必需的参数:当前上下文,由 view.getContext( ) 方法表示,以及 Intent 的目标TimePlanet.class 活动专有名称。

下一行代码使用我们刚刚创建的 callTimeIntent 对象启动 TimePlanet 活动,代码如下:

startActivityForResult(callTimeIntent, 0);

第一个参数是 Intent 对象,第二个是代码,其中零表示没有代码,任何非零正数都是在中返回的代码。活动退出时调用的 onActivityResult( ) 方法。

用 Java 为我们的 TimePlanet.java 活动编写一个意图对象

接下来,我们需要将我们的意图处理代码添加到我们的 TimePlanet.java 活动子类中,以便我们可以在使用返回星球配置按钮退出活动时返回一个意图结果。我们需要添加的 Java 代码是

Button returnFromTimeButton = (Button) findViewById(R.id.timeButton);
returnFromTimeButton.setOnClickListener(new view.OnClickListener() {
    public void onClick(View view) {
          Intent returnIntent = new Intent();
          setResult(RESULT_OK, returnIntent);
          finish();
    }
});

首先,我们需要实例化 returnFromTimeButton 按钮对象,然后使用。setOnClickListener()方法。这将调用新视图。OnClickListener()方法,然后包含我们的 onClick()事件处理程序,最后包含我们的 Intent 对象声明和 setResult()方法调用,最后是我们的 finish()方法调用,将我们返回到 ConfigPlanet,如图图 16-15 所示。

9781430257462_Fig16-15.jpg

图 16-15 。在我们的 TimePlanet 活动的 returnFromTimeButton 处理程序中编写 returnIntent 意图对象

现在,我们唯一需要做的就是为我们在 activity_time.xml 文件中指定的 tran_stars_galaxy drawable XML 文件添加 wow factor 背景图像 fade Java 代码,如图 16-16 所示。

9781430257462_Fig16-16.jpg

图 16-16 。为我们的 TransitionDrawable 用户界面屏幕背景淡出效果添加 Java 代码

我们这样做是因为我们已经在前一章中完成了设置特殊效果的所有工作,所以为什么不在原子钟屏幕上利用它来增加特殊效果的剂量呢!

让我们从我们的 NewPlanet.java 活动中复制 TransitionDrawable trans 对象和相关代码,并将其粘贴到我们的 TimePlanet.java 类中的 setContentView()方法下,如图图 16-16 所示。

不要忘记 trans.startTransition()方法代码,当我们进入用户界面屏幕时,它将启动转换动画。最终的 Java 代码块应该如下所示:

final TransitionDrawable trans = (TransitionDrawable)getResources().getDrawable(R.drawable.tran_stars_galaxy);
LinearLayout timePlanetScreen = (LinearLayout)findViewById(R.id.timePlanetScreen);
timePlanetScreen.setBackground(trans);
trans.startTransition(5000);

现在,我们终于准备好利用作为 Android 应用运行的工作流程,并测试我们新的 TimePlanet 活动和意图处理代码。

一旦 Nexus S 模拟器启动,点击菜单按钮,从菜单中选择配置行星选项,并启动 ConfigPlanet.java 活动。然后点击 UI 屏幕右下角的原子钟按钮,如图图 16-17 所示。

9781430257462_Fig16-17.jpg

图 16-17 。在 Eclipse 的 Nexus 模拟器中测试我们的 TimePlanet.java 活动和意图对象调用

正如你所看到的,我们新的行星地球时间屏幕平稳启动,从恒星背景到等离子体空间背景的动画过渡平稳地发生在我们用行星地球制作的令人印象深刻的模拟时钟后面,如图 16-17 右侧所示。

在行星地球时间活动屏幕的底部是你的返回到行星配置按钮,点击这个,它将返回到配置一个行星活动。

为了测试我们的意图对象和它们的有效性,多点击几次原子钟和返回行星配置按钮,以绝对确保我们可以在这两个活动屏幕之间来回切换,次数不限。恭喜,你现在已经使用了一个 Intent 对象在两个应用活动屏幕之间切换了!

摘要

在这一章中,我们仔细研究了 Android 意图对象以及 Android 意图过滤器。我们研究了它们是如何工作的,以及不同类型的意图,这样我们就可以在接下来的几章中使用它们来启动服务和调用广播接收器。

我们了解了 Intent 对象中包含的信息,Android 操作系统中指定的动作、数据、类别、附加内容及其常量,以及通常在 Intent 中指定的标志、数据类型(MIME)和组件名称属性。

我们了解了指定组件名称的显式意图之间的区别,这样,意图对象的目标是已知的,不需要进行推断,我们还了解了组件名称未知而目标通过动作、数据和类别进行推断的隐式意图。

我们了解了如何在您的应用和 AndroidManifest.xml 文件中创建您自己的意图过滤器,从而为解析隐含的意图对象(那些没有指定组件名称的对象)提供推理引擎,以及在什么条件下需要利用这些意图过滤器为通过意图使用您的应用组件的其他开发人员提供其他应用兼容性。

最后,我们在 Hello World 应用中添加了一个原子钟 TimePlanet.java 活动,这样我们就可以展示如何使用 Intent 对象在应用中的活动之间进行切换。我们将在本书的下两章中学习如何在服务和广播接收器中使用 Intent 对象,所以我们不在本章中讨论这些例子。

在下一章中,我们将仔细研究 Android 服务,了解已启动和已绑定的服务,并了解它们如何帮助我们的应用更加流畅和高效地工作。