Android 中基于 PendingIntent 与 AlarmManager 的定时广播

代码与范例:

说明:上述代码将创建并注册一个定时发出的广播,广播接收器 AlarmReceiver 将接收该广播。广播接收器需要事先在 Manifest 文件中注册,如下所示。

此外,如果要为 AlarmManager 设置间隔,需要注意的是,参数(单位为毫秒)大小有一定的限制,且 AlarmManager 无法在系统关机后保留。

Android 中分享内容至其他应用

代码与范例:

说明:该意图中包含了所要分享的内容的信息。通过该意图可以或许能够处理该意图的应用列表。

Android中接受来自其他应用的内容

Mainfest 文件代码:

<activity android:name=”.IntentReceiver” >
<intent-filter>
<action android:name=”android.intent.action.SEND” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”image/*” />
</intent-filter>
<intent-filter>
<action android:name=”android.intent.action.SEND” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”text/plain” />
</intent-filter>
<intent-filter>
<action android:name=”android.intent.action.SEND_MULTIPLE” />
<category android:name=”android.intent.category.DEFAULT” />
<data android:mimeType=”image/*” />
</intent-filter>
</activity>

范例:

说明:通过在 Manifest 文件中声明相应的意图过滤器,就可以接收来自其他应用的数据以供在自己的应用中进行处理。

Android中在系统启动完成时自动运行程序

代码与范例:

启动活动的情况

启动服务的情况

Manifest文件

……
<uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED” >
</uses-permission>
……
<application >
……
<receiver android:name=”.StartupReceiver” >
<intent-filter>
<action android:name=”android.intent.action.BOOT_COMPLETED” />
</intent-filter>
</receiver>
</application>
……

说明:通过 Intent 和 BroadcastReceiver 来实现系统启动后自动执行程序。需要在Manifest中注册该 BroadcastReceiver 并声明相应的权限。StartupReceiver 应当作为单独的类存在,而不能被包含在,例如,一个 Activity 类内部。这种情况下将无法接受到广播。更多的意图请参见官方文档

Android中通过Intent调用其他应用的方法

启动浏览器

代码与范例:

说明:启动浏览器并前往http://www.google.com 。

启动拨号程序

代码与范例:

说明:启动拨号程序并输入号码138。

通话

代码与范例:

说明:拨打号码138。需要为程序申请权限<uses-permission android:name=”android.permission.CALL_PHONE”/>。

发送短信

代码与范例:

说明:启动短信程序。

启动通讯录

代码与范例:

说明:启动同学录的某一条目。

启动地图程序(Google Maps等)

代码与范例:

说明:启动地图程序。

搜索路线

代码与范例:

说明:启动Google Maps并搜索路线,具体的方法另外补充。

启动邮件程序

代码与范例:

说明:启动邮件程序并将收件人设为xxx@example.com,邮件主题设为Hello world,内容设为Ganbarimasu。

启动邮件程序并添加多个收件人

代码与范例:

说明:启动邮件程序并设置为发送给多个收件人。

启动邮件程序并添加附件

代码与范例:

说明:启动邮件程序并添加附件。

播放MP3文件

代码与范例:

说明:启动音乐程序并播放MP3歌曲。

卸载程序

代码与范例:

说明:卸载包名为strPackageName的程序。

安装程序

代码与范例:

说明:安装包名为strPackageName的程序。

启动设置

代码与范例:

说明:进入设定程序。

启动电子市场

代码与范例:

说明:以包名org.breezesoft.techolite为条件启动Android Market。

启动电子市场并进入程序信息界面

代码与范例:

说明:启动Android Market并进入包名为org.breezesoft.techolite的程序的详细信息界面。

从图库(Gallery)中选择并获取一张图片

代码与范例:

说明:启动图库并中选择并获取一张图片,返回原程序。

意图和意图过滤器

一个程序的三个核心组件——活动、服务和广播接收者——通过被称为意图(intent)的消息来被激活。意图消息是一种在同一个或不同程序的组件间的运行后绑定机制。意图自身,即一个Intent对象,是包含了对于要执行的操作的抽象描述的被动数据结构——或者,对于广播来说,是对于已发生正在被播报的事件的描述。对于向不同类型的组建传递意图,有不同的机制:

  • 一个Intent对象被传递给Context.startActivity()或Activity.startActivityForResult()以启动一个活动,或是让一个已存在的活动进行新的工作。(它也可以被传递给Activity.setResult()使其向调用了startActivityForResult()的活动返回信息。)
  • 一个Intent对象被传递给Context.startService()以初始化一个服务或向正在运行的服务发出新的指令。同样的,一个意图可以被传递给Context.bindService()以在进行调用的组件和目标服务间建立联系。如果服务不在运行,还可以选择初始化该服务。
  • 被传递给任何广播方法(例如Context.sendBroadcast()、Context.sendOrderedBroadcast()或Context.sendStickyBroadcast())的Intent对象将被发送给所有相关的广播接收者。很多种类的广播在系统代码中被创建。

在任何一种情况下,Android系统将寻找合适的活动、服务或广播接收者来响应意图,在必要时对它们进行初始化。这些消息系统之间没有交集:广播意图仅被发送给广播接收者,而不会给活动或是服务。一个传递给startActivity()的意图将仅被发送给一个活动,而不会是服务或广播接收者,依此类推。

本文档将首先对Intent对象进行描述。之后介绍Android用以映射意图至组建的规则——它如何了解哪一个组建应当接收意图消息。对于没有显式地命名目标组件的意图,这个过程将包括使用与可能对象关联的意图过滤器(intent filter)来测试Intent对象。

Intent对象

Intent对象是一捆数据。它包含了接受该意图的组件所感兴趣的信息(例如要采取的操作及操作的对象数据)及Android系统所需的信息(例如要处理该意图的组件类别及如何启动目标活动的指令)。原则上,它包含以下内容:

组件名称

应当处理该意图的组件名称。这部分是一个ComponentName对象——一种目标组件的完整类名(例如“com.example.project.app.FreneticActivity”)和组件所在程序的manifest文件中的包名称组(例如“com.example.project”)的组合。组件名称的包部分和manifest中的包名称组不一定要相同。

组件名称是可选的。如果被设定,Intent对象将被发送给指定类的实例。如果没有设定,Android使用Intent对象中的其他信息来选择合适的目标——参见本文档中之后的“意图解决方案”。

组件名称通过setComponent()、setClass()或setClassName()来设定,通过getComponent()来读取。

行为

命名了要被执行的行为的字符串——或者,对于广播的意图来说将要发生并被报告的行为。Intent类定义了很多行为常量,包括:

常量 目标组件 行为
ACTION_CALL 活 动 初始化一次电话呼叫。
ACTION_EDIT 活 动 向用户显示数据以供编辑。
ACTION_MAIN 活 动 作为一个任务(task)的首活动启动,不包含数据输入且没有返回的输出。
ACTION_SYNC 活 动 以移动设备上的数据同步服务器上的数据。
ACTION_BATTERY_LOW 广播 接收者 电量低的警告。
ACTION_HEADSET_PLUG 广播接收者 耳机插入或是拔出设备。
ACTION_SCREEN_ON 广播接收者 屏幕被打开。
ACTION_TIMEZONE_CHANGED 广播接收者 时区设定被改变。

参见Intent类的描述以获知通用行为的预定义常量列表。其他的行为在Android API的其他部分被定义。也可以为激活自己的程序中的组件而定义自有的行为字符串。它们应当包含作为前缀的程序包——例如:“com.example.project.SHOW_COLOR”

行为很大程度上决定了意图剩余部分的结构——特别是数据和额外信息——就好像是一个方法的名称决定了一组参数和返回值。因此,最好尽可能具体地使用行为名称,将它们紧密地和意图的其他部分配对。换言之,不要单独定义一个行为,而是要为组件可以处理的Intent对象定义一整套的协议。

一个Intent对象的行为通过setAction()方法设置,通过getAction()来读取。

数据

将被执行操作的数据的URI及数据的MIME类型。不同的行为和不同的数据规格相对应。例如,如果行为域是ACTION_EDIT,那数据域就需要包含要被显示以编辑的文档的URI。如果行为是ACTION_CALL,那数据域需要是一个要拨打的号码的tel: URI。同样地,如果行为是ACTION_VIEW且数据域是http: URI,那接收方活动将会下载或显示该URI指向的任何数据。

在配对一个意图和一个有能力处理其数据的组件时,知道数据的类型(即它的MIME类型)以及URI是很重要的。例如,一个可以显示图像数据的组建不应被调用来播放音频文件。

在许多情况下,数据类型可以由URI获知——特别是content: URI,它表明数据位于设备中并由一个内容提供者所控制(参见另外的关于内容提供者的解说)。不过也可以在Intent对象内显式地设定类型。setData()方法以仅URI来指定数据,setType()仅以MIME类型来指定数据,setDataAndType()同时以URI和MIME类型来指定数据。URI通过getData()读取,类型由getType()读取。

类别

一个包含了应该由哪一种类型的组件来处理意图的额外信息的字符串。Intent对象中可以存放任何数量的类别描述。和行为一样,Intent类定义了一些类别常量,其中包括:

常量 意义
CATEGORY_BROWSABLE 目标活动可以安全地被浏览器用于显示某一链接指向的数据——例如,一幅图像或是一条电子邮件消息。
CATEGORY_GADGET 该活动可以被另一个持有小部件(gadget)的活动嵌于内部。
CATEGORY_HOME 该活动显示主界面(home screen)这一用户启动设备或按下HOME键后首先看到的画面。
CATEGORY_LAUNCHER 该活动是一个任务中的首活动,被列于应用程序启动器的首级。
CATEGORY_PREFERENCE 目标活动是一个偏好设置面板。

 参见Intent类的描述以获知完整的类别列表。

addCategory()方法将一个类别存入一个Intent对象,removeCategory()则删除之前添加的一个类别,getCategories()用于获取当前在意图对象内的整个类别集。

额外信息

关于需要被传递给要处理意图的组件的其他信息的键值对(key-value pair)。如同一些行为与特定类型的数据URI相配对,另一些会与特定的额外信息相配对。例如,一个ACTION_TIMEZONE_CHANGED意图有一个额外信息“time-zone”来识别新的时区,ACTION_HEADSET_PLUG则有一个额外信息“state”来识别耳机是被插入还是拔出,另外还有额外信息“name”来识别耳机的类型。如果想要设计一个SHOW_COLOR行为,颜色的值将被设置于一个额外的键值对之中。

Intent对象有一系列的put…()方法来插入不同类型的额外数据,类似地有一组get…()方法来读取这些值。这些方法和Bundle对象中的相类似。事实上,额外信息可以如同一个Bundle那样通过putExtras()和getExtras()方法来被设置和读取。

旗标

各种类型的旗标。其中很多指示了Android系统如何去启动一个活动(例如,该活动应该属于哪一个任务)以及在启动后应该如何处理它(例如,它是否应该属于最近活动列表)。所有的这些旗标在Intent类中被定义。

Android系统和该平台的应用程序使用Intent对象来发出系统创建的广播及激活系统定义的组件。要了解如果构建一个意图以激活一个系统组件,参见附录中的意图列表。

意图的解决方案

意图可以被分为两类:

  • 显式意图通过其名称指定目标组件(前面提到过,该组件的名称域有一个值集)。因为组件的名称通常不会被其他程序的开发者知道,显式的意图往往被用于程序内部消息——例如一个活动启动一个附属的服务或是一个平级的活动。
  • 隐式意图不会命名目标(组件的名称域是空白的)。隐式意图常用于激活其他活动中的组件。

Android将一个显式意图传递给所指定的目标类的一个实例。只有Intent对象中的组件名称会影响到意图最终将被传送给哪一个组件。

对于隐式意图需要一种不同的策略。由于没有指定的目标,Android系统必须找到最为合适的那一组件(或那一些组件)——一个单独的执行所请求的行动的活动或服务,或是一组广播接收者来响应广播通告。系统通过比较Intent对象的内容和意图过滤器(和组件相关联的用以接收潜在意图的结构)来实现这一目的。过滤器告知了一个组件的功能并界定了它可以处理的意图。它们使得组件可能接收所告知的类型的隐式意图。如果一个组件没有任何的意图过滤器,它将只能接收显示意图。一个拥有意图过滤器的组件可以同时接收显式和隐式意图。

当一个Intent对象被意图过滤器检查时只有三个方面会被考虑:

行为

数据(同时包括URI和数据类型)

类别

额外信息和旗标不会影响到决定由哪个组件接收意图。

意图过滤器(Intent filter)

为了告知系统它可以处理哪些隐式意图,活动、服务和广播接收者都可以拥有一个或多个意图过滤器。每一个过滤器将描述组件的某一种功能以及该组件要接收的意图集。它有效地过滤下所需的类型,排除不需要的意图——不过仅能排除不需要的(未命名目标类的)隐式意图。显式意图将始终被传递给其目标而无论它包含什么内容;过滤器不会起作用。然而一个隐式意图就只有在通过了组件的过滤器之后才能够被传递给组件。

一个组件对每一类它可以处理的工作及用户所能看见的每一种形式分别有着不同的过滤器。例如,范例中Note Pad程序的NoteEditor活动有两个过滤器——一个用于创建用户可以查看并编辑的特定便筏,另一个用于启动一个空的用户可以输入并保存的新的便筏。(Note Pad的所有的过滤器将会在之后的Note Pad范例一节中详细说明。)

一个意图过滤器是IntentFilter类的一个实例。不过因为Android系统必须在启动一个组件之前知道它所具有的功能,所以意图过滤器一般不在Java代码中使用,而是作为程序的manifest文件中(AndroidManifest.xml)的<intent-filter>元素。(不过有一个例外是广播接收者的过滤器是通过调用Context.registerReceiver()动态地在代码中注册的;它们被作为IntentFilter对象直接创建。)

一个过滤器有着和Intent对象中的行为、数据和类别域相对应的域。一个隐式意图将检测过滤器的所有这三个内容。要被传递给某个组件,一个意图必须通过该组件的过滤器的所有三项检测。即使只有一项不满足,Android系统也不会将该意图传递给组件——至少不会是基于过滤器传递给组件。不过,由于一个组件可以有多个意图过滤器,一个意图如果无法通过其中一个过滤器还是有可能能通过其他的的。

过滤器和安全

意图过滤器不能确保安全性。尽管它只让一个组件接受特定类型的隐式意图,它无法阻止显式意图指向目标组件。即使过滤器约束了意图使其只能使用将会被请求处理特定的行为和数据源的组件,一些人仍然可以将一个显式意图与不同的行为和数据源结合在一起,并将目标以组件的名称来命名。

下面将详细描述三种检测是如何进行的:

行为检测

manifest文件中的一个<intent-filter>元素以<action>子元素的形式列出了行为。例如:

如同示例所展示的,一个Intent对象只命名一个单独的行为,而一个过滤器可以列出多个行为。列表不能为空;一个过滤器至少要包含一个<action>元素,否则它将屏蔽所有的意图。

要通过这个检测,在Intent对象中指定的行为必须与过滤器所列出的行为之一相匹配。如果该对象或是过滤器没有指定一个行为,就会有下面的结果:

  • 如果过滤器没有列出任何行为,就没有行为可以与意图相匹配,所有的意图都无法通过检测。没有意图可以通过过滤器。
  • 另一方面,如果Intent对象没有指定任何的行为,它将自动通过检测——只要过滤器含有一个或以上的行为。

类别检测

<intent-filter>也会将类别作为子元素列出。例如:

注意之前描述过的表示行为和类型的常量不在mainfest文件中被使用。而是使用完整的字符串值。例如,在范例中的“android.intent.category.BROWSABLE”字符串和文档之前提到的CATEGORY_BROWSABLE常量相对应。类似地,字符串“android.intent.action.EDIT”ACTION_EDIT常量相对应。

一个意图要通过类别检测,Intent对象中的每一个类别都必须与过滤器中的某个类别相匹配。过滤器可以列出其他更多的类别,但不能省略任何一个意图中含有的类别。

原则上,所以说,一个不含有类别的Intent对象,无论过滤器中有哪些类别,总是能够通过这项检测。这通常是正确的。不过,有一个例外,Android把所有传递给startActivity()的隐式意图视为它们包含了至少这样一个类别:“android.intent.category.DEFAULT”CATEGORY_DEFAULT常量)。因此,要接受隐式意图的活动必须在其意图过滤器中包含有“android.intent.category.DEFAULT”。(设有“android.intent.action.MAIN”“android.intent.category.LAUNCHER”的过滤器不在此范围之中。它们将活动标记为了新任务的开始,并显示在应用启动器(launcher)屏幕上。它们可以在类别列表中包含“android.intent.category.DEFAULT”,不过这并不是必须的。)参见之后的“使用意图匹配”来进一步了解这些过滤器。

数据检测

如同行为和类别,意图过滤器的的数据类型也是作为子元素保存的。而且和它们一样,这些子元素可以出现多次或完全不出现。例如:

每一个<data>元素可以指定一个URI和数据类型(MINE媒体类型)。URI的每一个部分由不同的属性——模式(scheme)、主机(host)、接口(port)和路径(path)。

scheme://host:port/path

例如,下面的URI,

content://com.example.project:200/folder/subfolder/etc

其模式是“content”,主机是“com.example.project”,接口是“200”,路径是“folder/subfolder/etc”。主机和接口共同构成了URI的授权;如果没有指定主机,那么接口将被忽略。

每一个属性都是可选的,但并非与其他属性相互独立:要让一个授权变得有意义,必须指定一个模式。要让路径具有意义,必须指定一个模式和一个授权。

当一个Intent对象中的URI和过滤器中的URI相比较时,仅比较过滤器中所包含的URI部分。例如,如果一个过滤器仅指定了一个模式,那所有具有这个模式的URI就与过滤器相匹配。如果过滤器指定了一个模式及一个授权但是没有指定路径,那么无论是什么路径,具有相同模式和授权的URI将得到匹配。如果过滤器指定了模式、授权和路径,那就只有具有相同模式、授权和路径的URI得到匹配。不过,过滤器中指定的路径可以包含通配符以仅仅限定部分路径。

一个<data>元素的类型(type)属性指定了数据的MINE类型。在过滤器中这比URI更为常见。Intent对象和过滤器都可以用”*”通配符作为子类别域来标识子类别匹配,例如“text/*”“audio/*”

数据检测同时将Intent对象中的URI和数据类型与过滤器中的URI和数据类型相比较。该过程遵循以下规则:

  • 不包含URI和数据类型的Intent对象只有在过滤器也不指定任何URI及数据类型时才能通过检测。
  • 包含URI但不包含数据类型(且数据类型不能通过URI推断出来)的Intent对象只有在其URI与过滤器中的URI相匹配且过滤器同样没有指定类型时才能通过检测。这是仅仅在类似mailto:和tel:这样没有指向实际数据的URI时才会出现的情况。
  • 包含数据类型但不包含URI的Intent对象只有在过滤器列出了相同的数据类型而没有指定URI时才能通过检测。
  • 同时包含有URI和数据类型(或URI可以推导出数据类型)的Intent对象在其数据类型与过滤器中列出的类型相匹配时通过检测的数据类型部分。当其URI与过滤器中的URI相匹配,或,其具有一个content:file:URI并且过滤器没有指定URI时,通过测试的URI部分。换言之,如果过滤器仅列出了数据类型,一个组件将被假定支持content:file:数据。

如果一个意图可以通过不止一个活动或服务的过滤器,用户将被询问要激活哪一个组件。如果没有一个目标能被找到,则会抛出一个例外。

常见情况

上面数据检测中的最后一条规则,说明了组件可以从一个文件或是内容提供者处获取本地数据。因此,它们的过滤器可以仅仅列出数据类型而不需要显式地命名content:file:模式。这是一种典型的情况。比如说,一个如下的<data>元素,告诉Android组件可以从内容提供者处获取图像数据并显示:

因为大部分可用的数据由数据提供者分发,所以指定了数据类型而没有指定URI的过滤器也许是最为常见的了。

另一种常见的情况是过滤器指定了模式和数据类型。例如,如下的一个<data>元素告诉Android组件可以从网络获取视频数据并播放:

试想,例如,浏览器类应用在用户打开网页上的一个链接后将做什么。它首先尝试显示数据(如果该链接是一个HTML页面的话)。如果它无法显示数据,它将生成一个包含模式和数据类型的隐式意图并试图启动一个可以处理该工作的活动。如果没有合适的活动,它将请求下载管理器下载该数据。这处于内容提供者的控制之下,所以有大量的可能合适的活动(它们的过滤器仅指定了数据类型)将能够做出响应。

大部分程序还有一种不需要任何指定数据引用的开始刷新的方法。能够初始化一个程序的活动有着将行为指定为”android.intent.action.MAIN”的过滤器。如果它们需要被显示在应用程序启动器中的话,它们还需要被指定为”android.intent.category.LAUNCHER”类别:

使用意图匹配

将意图与意图过滤器相匹配不仅仅是为了寻找要激活的某一目标组件,同时也是要获取设备上的某一组组件的某些属性。例如,Android系统通过查找所有具有指定了“android.intent.action.MAIN”行为和“android.intent.category.LAUNCHER”类别的过滤器的活动(如前一节所示)来弹出应用程序启动器这一列出了所有可供用户启动的程序的顶层屏幕内容。之后它在启动器中显示那些活动的图标(icon)和标签(label)。同样地,它通过查找具有“android.intent.category.HOME”类别的过滤器的活动来显示主界面。

程序可以以一种类似的方式来使用意图匹配。PackageManager拥有一组query…()方法来返回所有可以接受某一特定意图的组件,以及一系列的resolve…()方法来决定响应意图最合适的组件。例如,queryIntentActivities()将返回一个列出了所有可以将意图作为参数传递的活动的列表,queryIntentServices()将返回一个类似作用的所有服务的列表。这两种方法都不会激活组件;它们只是列出了那些可以做出响应的。对于广播接收者,还有一个类似的queryBroadcastReceivers()方法。

记事本(Note Pad)范例

记事本范例允许用户浏览笔记列表、查看列表中项目的详细内容、编辑项目、向列表增加新项目。本节将分析在其manifest文件中声明的意图过滤器(如果正在离线使用SDK,可以在<sdk>/samples/NotePad/index.html下找到包括manifest文件在内的该范例程序的所有源文件。如果是在在线查看文档,源文件则是在这里的“教程和范例代码”一节。

在manifest文件中,记事本程序声明了三个活动,每一个都有至少一个意图过滤器。它还声明了一个内容提供者用以管理笔记数据。以下是整个manifest文件的内容:

第一个活动,NoteList,和其他的活动不同,它要对一系列的笔记(笔记列表)进行操作而不是某条单独的笔记。它通常将作为用户进入程序的初始界面。如其三个意图过滤器所描述的,它能执行三项工作:

该过滤器声明了Note Pad程序的主入口。标准MAIN行为是一种不需要Intent中的其他信息(比如不需要数据类型)的入口(entry point),LAUNCHER类别则说明该入口应该被列于应用程序启动器中。

该过滤器声明了这个活动可以对一列笔记进行何种操作。它可以允许用户查看或编辑笔记(通过VIEWEDIT行为),或是从列表中选取一条特定笔记(通过PICK行为)。

<data>元素的mimeType属性指定了这些行为要执行操作的数据类型。它表明了这个活动可以从一个持有Note Pad数据的内容提供器(vnd.google.note)中获取一个指向零个或更多个项目(vnd.android.cursor.dir)的Cursor。启动该活动的Intent对象包含一个content: URI用以指定活动应该打开的确切数据类型。

也请注意该过滤器中提供的DEFAULT类别,因为Context.startActivity()和Activity.startActivityForResult()方法在意图包含DEFAULT类别时将会对其进行处理——只有两种例外:

  • 显式地命名了目标活动的Intent
  • 包含了MAIN行为和LAUNCHER类别的Intent

因此,所有的过滤器都需要DEFAULT类别——除非有MAIN行为和LAUNCHER类别。(意图过滤器不过滤显式意图)

该过滤器说明了这个活动可以返回一条用户选中的笔记而不需要这条笔记所在的列表的相关信息。GET_CONTENT行为类似于PICK行为。两种情况下活动都将返回用户所选择的笔记的URI。(将返回给调用了startActivityForResult()来启动NoteList活动的那个活动。)不过现在,调用者指定了所需的数据类型而不是用户要进行选择的数据列表的类型。

数据类型,vnd.android.cursor.item/vnd.google.note,标明了该活动可以返回的数据的类型——一条单笔记的URI。通过所返回的URI,调用者可以从持有Note Pad数据(vnd.google.note)的内容提供其中获取某一特定条目(vnd.android.cursor.item)的Cursor。

也就是说,前一个过滤器中的PICK行为,数据类型标识出了活动可以显示给用户的数据的类型。而GET_CONTENT过滤器则标识出了活动可以返回给它的调用者的数据的类型。

有了这些能力,下面的意图可以实现活动NotesList的功能:

行为:android.intent.action.MAIN

不需要特定的数据就能启动活动

行为:android.intent.action.MAIN 

类别:android.intent.category.LAUNCHER

不需要特定的被选择的数据就能启动活动。这是实际上应用程序启动器用来填充其最高层列表(top-level list)的意图。所有具有匹配该行为和类别的过滤器的活动将被添加至列表。

行为:android.intent.action.VIEW 

数据:content://com.google.provider.NotePad/notes

请求活动显示在content://com.google.provider.NotePad/notes下的所有笔记的列表。之后用户可以浏览该列表并获取列表中项目的信息。

行为:android.intent.action.PICK 

数据: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

请求活动提供一条单独的Note Pad数据。

第二个活动,NoteEditor,向用户显示一条单独的笔记条目并允许对其进行编辑。根据其两个意图过滤器的描述,它可以进行两项任务:

这个活动的第一个,也是主要的目的是使用户与一条笔记交互,VIEW这条笔记或是EDIT它。(类别EDIT_NOTEEDIT的意义相同。)该意图将包含匹配MIME类型vnd.android.cursor.item/vnd.google.note的数据的URI——也就是说,这条指定的笔记的URI。它通常是被NoteList活动的PICKGET_CONTENT行为所返回的URI。

和之前一样,该过滤器列出了DEFAULT类别因此这个活动可以被其他没有显式地指定NoteEditor类的意图启动。

这个活动的第二个目的是使用户创建一条新的笔记并INSERT至现有的笔记列表中。该意图包含了匹配MIME类型vnd.android.cursor.dir/vnd.google.note的数据的URI——即笔记将被插入的笔记列表的URI。

有了这些能力,下面的意图可以实现NoteEditor活动的功能:

行为:android.intent.action.VIEW 

数据:content://com.google.provider.NotePad/notes/ID

请求活动显示由ID确定的笔记的内容。(关于content: URIs是如何指定一个群组内的个别成员的,请参见“内容提供器”。)

行为:android.intent.action.EDIT 

数据:content://com.google.provider.NotePad/notes/ID

请求活动显示由ID确定的笔记的内容,并允许用户对其进行编辑。如果用户保存了修改,该活动将更新内容提供器中的笔记数据。

行为:android.intent.action.INSERT 

数据:content://com.google.provider.NotePad/notes

请求活动在content://com.google.provider.NotePad/note的笔记列表中创建一个新的空笔记并允许用户对其进行编辑。如果用户保存了笔记,其URI将被返回给调用者。

最后一个活动,TitleEditor,允许用户编辑笔记的标题。这可以通过直接调用该活动来实现(通过显式地在Intent中设定该组件名称),而不需要通过一个意图过滤器。不过这里主要关注如何对现存的数据进行其他操作:

这个活动的唯一的意图过滤器使用了一个名为”com.android.notepad.action.EDIT_TITLE“的自定义行为。它必须像之前的VIEWEDIT行为一样通过一条指定的笔记调用(数据类型vnd.android.cursor.item/vnd.google.note)。不过,这个活动将显示包含于笔记数据中的标题,而不是笔记内容自身。

另外为了支持常见的DEFAULT类别,这个标题编辑器也支持其他两种常见类别:ALTERNATIVE和SELECTED_ALTERNATIVE。这两个类别表明了活动可以在一个选项菜单中显示给用户(就好象LAUNCHER类别表明了活动应当在应用程序启动器中显示给用户一样)。注意过滤器也支持一个显式标签(通过android:label=”@string/resolve_title”)以更好地控制用户能够看到当前被这个活动以其他行为方式查看的数据的哪些内容。(关于这些类别和创建选项菜单的更多信息,请参见PackageManager.queryIntentActivityOptions()和Menu.addIntentOptions()方法。)

有了这些能力,下面的意图就可以实现TitleEditor活动的功能:

行为:com.android.notepad.action.EDIT_TITLE 

数据:content://com.google.provider.NotePad/notes/ID

请求活动显示和笔记ID相关联的标题,并允许用户对其进行编辑。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

应用程序基础

Android应用程序以Java编程语言写成。Android SDK工具将代码连同数据和资源文件一起编译进一个以.apk为后缀的档案文件——Android包(Android package)中。一个.apk文件中的所有代码被认为是一个程序,它也是使用Android的设备的程序安装文件。

一旦被安装,每一个Android程序就活在其自己的安全沙盒中:

  • 每一个程序默认都运行于其独立的Linux进程中。当任何一段代码需要被执行时Android会开始一个进程,而当它不再被需要或当其它程序请求系统资源时进程将被关闭。
  • 每一个进程有其自己的虚拟机,因此程序代码的运行和所有其他程序是孤立开的。
  • 每一个程序默认被分配一个专属的Linux用户ID。由于设立的权限,因此程序的文件仅对该用户和程序可见。同时也有很多方法将程序文件输出给外部程序。

两个不同的程序可以共享同一个用户ID,这样它们的文件将会相互可见。为了节省系统资源,两个程序还可以运行于同一个进程中,并使用同一个虚拟机。

应用程序组件

Android的一大核心特性是一个程序可以使用其他程序的部件(如果那些程序允许的话)。例如,当你的程序需要显示一个滚动式列表时,如果有另外的某个程序拥有一个合适的滚动式列表程序并且向其他程序开放使用权限,那你就可以让这个已经存在的滚动列表来完成你的工作,而不必自己写一个新的滚动列表。并且,它只会在你需要的时候启动你所需要的那一部分。 为了实现这个工作,这个系统必须在当任何一个部分被需要时都能创建一个新的程序进程,并为这个部分实例化一个Java对象。因此,和其他系统上的程序不同,Android程序没有一个单独的入口指针以进入整个程序(例如,没有main()函数),并且这些程序有一些必需的组件使得系统可以在需要时初始化并运行它们。组件有以下四种:

活动(Activity)

为了让用户可以集中处理一个操作,一个活动呈现为一个视觉用户界面。例如,一个活动可以呈现为一个项目列表以供用户进行选择,或者显示照片及其注释。一个短消息程序可能会有一个活动来列出可用的收件人,一个活动可以给选定的收件人撰写消息,以及另外一个活动用来查看旧消息或改变设定。尽管它们一起工作以形成一个完整的用户界面,但每一个活动都是独立于其他活动的。每一个活动都是Activity类的一个子类。

一个程序可以仅由一个活动组成,或者像之前提到的短信息程序那样由多个活动组成。用怎样的活动和多少个活动取决于程序和它的设计。通常其中的一个活动会被标识为当程序启动时第一个被呈现在用户面前的那个。让当前活动启动另一个活动就可以从一个活动切换到下一个。每一个活动被分配了一个默认的窗口来绘图。一般来说这个窗口将填满屏幕,但它也可以小于屏幕而浮在其他窗口的上方。一个活动还可以使用更多的窗口——比如,一个在活动中出现的弹出式的对话框,提示用户进行操作,或者当用户选择了屏幕上一个特定项目后出现一个窗口向用户显示重要的信息。

窗口的视觉内容由视图(View)的一个继承提供,这些视图是从基本的View类衍生出的对象。每一个视图控制窗口中的一块特定的矩形区域。父视图包含并组织它们的子视图的布局(Layout)。叶视图(那些位于继承底部的视图)在它们控制的矩形区中绘图并对指向该区域的用户操作进行回应。因此,视图是一个活动与用户的交互之处。比如,一个视图可以显示一个小的图片并在用户轻触这张图片时启动一个动作。Android中有很多你能马上使用的视图——包括按钮、输入框、滚动条、菜单项目、可选框等等。

视图的继承由Activity.setContentView()方法来设置到一个活动的窗口中。一个内容视图是这个继承的根视图。(参阅另外的用户界面文档来获得关于视图和继承的更多信息)

服务(Service)

服务没有视觉用户界面,它在后台运行不确定的一段时间。比如,一个服务可以在用户处理别的事的同时在后台播放音乐,或从网络接收数据,或计算并给活动提供所需的结果。每一个服务都继承于Service类。

一个基本的例子就是音乐播放器播放一个播放列表中的音乐。这个播放程序可能有一个或多个活动来允许用户选择歌曲并开始播放。但是,播放本身不应该由一个活动来操作,因为用户希望他们离开播放器进行别的工作时音乐仍然继续播放。为了让音乐继续,播放器活动应该在后台开启一个服务。系统会保持音乐播放服务一直运行即使启动它的活动已经不在屏幕上。

关联一个正在运行的服务(并且要是它没在运行的话启动这个服务)是可能的。当关联后,你可以通过这个服务的一个接口和它通信。对于音乐服务来说,这个接口将允许用户暂停,停止和重新播放。

如同活动和其他组件一样,服务运行于程序进程的主线程中。因此它们不会阻碍其他组件或是用户界面。它们常产生一个另外的线程来执行消耗时间的任务(比如音乐播放)。之后请看进程和线程。

如同活动和其他组件一样,服务运行于程序进程的主线程中。因此它们不会阻碍其他组件或是用户界面。它们常产生一个另外的线程来执行消耗时间的任务(比如音乐播放)。之后请看进程和线程。

广播接收者(Broadcast Receivers)

作为一种组建,广播接收者仅仅接收并回应广播通知。很多广播是在系统代码中创建的——比如,通知时区已被改变,通知电量低下,通知拍摄了一张照片,或通知语言偏好被用户改变。程序也可以初始化各种广播——比如,让别的程序知道设备完成了某些数据的下载并已经能够被使用。

一个程序能够有任意多的广播接收者来对任何它认为重要的通知进行回应。所有的广播接收者都继承与BroadcastReceiver基类。

广播接收者不会显示出一个用户界面。然而,它们可能会启动一个活动来响应接收到的信息,或者是使用通知管理器(NotificationManager)来提示用户。通知可以通过各种途径来引起用户的注意——背光的闪烁,设备的震动,声音的播放等。它们通常会在状态栏显示一个图标,用户可以打开它来获得更多的信息。

内容提供者(Content Providers)

内容提供者使程序中特定的一组数据可被其他程序使用。这些数据能够被储存在文件系统中,在SQLite数据库中,或其他任何适合的地方。内容提供者继承于ContentProvider基类,它运用一系列标准的方法来使得其他程序能够接受并储存它所控制的这部分数据。然而,那些程序不会直接使用这些方法。通常他们会使用一个ContentResolver对象并使用它的方法。ContentResolver能够和任何内容提供者交流信息,它与它们合作管理任何类型的进程间交流。

请参阅另外的内容提供者文档来获取更多关于如何使用内容提供者的信息。

当存在一个请求需要被某一特定的组件处理时,Android会确保这个组件的程序进程处于运行状态,并在必要时启动它。如果该组件的一个正确的实例是可用的,系统会在必要时创建这个实例。

激活组件:意图(Intent)

内容提供者会在有一个来自ContentResolver的请求时被激活。其他三种组件——活动,服务和广播接收者——是由被称为意图的异步信息来激活的。一个意图是一个Intent对象,这个对象包含了激活信息的内容。对于活动和服务来说,它命名了被请求的活动并分配了要执行操作的数据的URI等。比如,一个意图可以传达一个请求,让一个活动向用户显示一张图像或是允许用户编辑文字内容。对于广播接收者来说,Intent对象命名了被通知的操作内容。比如,它能通知相关联的部分照相机的拍摄键被按下的这一消息。 有几种不同的方法来激活每一种组件:

  • 可以通过向Context.startActivity()或是Activity.startActivitiForResult()传递一个Intent对象来启动一个活动(或让这个活动做一些新的工作)。接受响应的活动会调用它的getIntent()方法来查找最初启动它的那个意图。Android调用该活动的onNewIntent()方法来传递之后其他的意图。
  • 一个活动常常会启动另一个活动。如果希望从启动的活动中获得一个返回的结果,就要改用startAcitivityForResult()方法而不是startAcitity()。比如说,如果一个活动启动了另一个活动来让用户选择一张照片,那么应该要能收到返回的那张被选择的照片。其结果会被返回到一个Intent对象,然后被传递到前一个活动的onActivityResult()方法。

  • 通过向Context.startService()传递一个Intent对象可以启动一个服务(或给正在运行的服务一个新的指令)。Andoird会调用这个服务的onStart()方法然后将它传递给Intent对象。
  • 同样的,可以传递一个意图给Context.bindService()来给正在调用的组件和目标服务间建立联系。该目标服务会在onBind()中收到一个Intent对象(如果这个服务没有在运行,bindService()还可以选择启动它)。比如说。一个活动可以和前面提到过的音乐播放服务相连接,这样它能给用户提供一种途径(或者说一个用户界面)来控制播放。这个活动将调用bindService()来建立这个连接,然后调用在服务内定义的一些方法来控制播放。 在之后会更详细说明远程步骤调用(Remote procedure call)如何与服务建立连接。

  • 通过传递一个Intent对象给比如Context.sendBroadcast()、Context.sendOrderedBroadcast()、Context.sendStickyBroadcast()或是其他任何它们的变体,一个程序能够初始化一个广播。Android会调用任何可能接收的广播接收者的onReceive()方法来把这个广播递送给它们。

关于意图的更多信息,请参阅另外的“意图与意图过滤器”条目。

关闭组件

内容提供者只有在它对来自ContentResolver的请求进行响应的时候才处于活动状态。类似地,广播接收者只有在响应一个广播通知的时候才是活动的。因此,没有必要显式地关闭这两种组件。 另一方面来说,活动(Activity)负责提供用户界面。它们要与用户进行长期的交互,因此只要这个交互仍在继续,一个活动即使处于空闲时也要保持活动(Active)状态。同样地,服务也可能要保持长时间的运行状态。因此Android中提供了关闭活动和服务的方法:

  • 可以通过调用finish()方法关闭一个活动。而通过调用finishActivity()则可以关闭另一个(由startActivityForResult()方法启动的)活动。
  • 调用一个服务自身的stopSelf()方法可以关闭这个服务,或者也可以调用Context.stopService()来实现这个操作。

当组件不再被使用时或剩余内存不足以启动其他更多的组件时,系统可能会自动关闭这些组件。在稍后的组件生命周期板块中会更加具体地讨论这一问题及其后果。

manifest文件

Android在开始一个应用程序组件之前必须先确认其存在。因此,程序必须在manifest文件中声明这些组件,并把这个文件与Android包相绑定。此外,.apk文件中则是包含了程序的代码、文件和资源。 manifest文件是一种结构化的XML文件,并且对于任何程序来说都只能被命名为AndroidManifest.xml。除了声明应用程序组件,它还有一系列的功能,比如对程序要关联的(Android默认库以外的)库进行命名,或者对程序希望取得的操作权限进行认证。 不过manifest的主要任务还是告诉Android一个程序有哪些组件。比如,一个活动将会被这样声明:

<activity>元素的name属性命名了这个活动(它是一个Activity类的子类)。而iconlabel属性则指向了包含了图标和标签的资源文件,它们会被显示以表示这个活动。 其他的组件也被以相似的方式来声明——<service>元素声明服务,<receiver>元素声明广播接收者,<provider>元素声明内容提供者。没有在manifest中被声明的活动、服务和内容提供者对于系统来说是不可见的,所以也就无法被运行。然而,广播接收者既可以在manifest中声明,也能够在代码中作为一个BroadcastReceiver对象声明后通过调用Context.registerReceiver()来向系统注册这个组件。 关于如何构建一个manifest文件的更多信息,请参见”AndroidManifest.xml文件“。

意图过滤器(Intent filters)

一个Intent对象可以命名一个目标组件。当这么做时,Android会通过manifest文件中的声明来找到这个组件并激活它。但如果这个目标组件没有被显式地命名,Android会比较可能的目标组件的意图过滤器和这个Intent对象,之后选择一个最合适的组件来响应这个意图。一个组件的意图过滤器告诉Android这个组件可以处理哪些类型的意图。和别的一个组件所必需的信息一样,意图过滤器需要在manifest文件中被声明。下面是前面的例子的一个扩充,它向这个活动添加了两个意图过滤器:

例子中的第一个过滤器——行动(action)“android.intent.action.MAIN和类别(category)“android.intent.category.LAUNCHER”的组合——一种常见的情况。它标明了这个活动会被显示在应用程序启动器中(也就是设备中那个列出了所用用户可以启动的程序列表的画面)。换句话说,这个活动将作为这个程序的入口,当用户从启动器中选择了这个程序后,该活动会被第一个启动。 第二个过滤器声明了这个活动可以执行某种数据类型的操作。 一个组件可以有任何数量的意图过滤器,每一个声明一种不同的能力。如果一个组件不含有任何过滤器,它将只能被那些明确以其为目标的意图启动。 对于在代码中创建并注册的广播接收者来说,意图过滤器会被直接实例化为一个IntentFilter对象。其他所有的过滤器则是在manifest中被建立的。 关于意图过滤器的更多信息,参见另外的“意图和意图过滤器”篇目。

活动和任务(Task)

正如前面提到过的,一个活动可以启动另一个活动,甚至是在其他的应用程序中的活动。想象一下,比如,你希望向用户显示某地的一张街道地图,而现在已经有了一个活动可以做到这个功能,那么你的活动要做的就只是把需要的信息放入一个Intent对象然后通过startActivity()传递这个意图。那个活动将会显示地图,当用户按下返回键时,你的活动将会重现屏幕。 对于用户来说,地图显示似乎是你的程序的一部分,尽管它其实是在另外一个程序中定义并在另一个程序的进程中运行的。Android通过在同一个任务(task)中保持这两个活动来呈现这样的用户体验。简单说,一个任务是用户所感受到的“一个程序”。它是一组相关的活动,被分配在一个栈中。栈底部活动是开始整个任务的那个活动。通常它也是用户从应用程序启动器中选择的那个活动。栈顶部的活动是正在运行的活动,它是用户操作的接收者。当一个活动启动了另一个活动,新的活动被推入栈中,成为正在运行的那个活动。前一个活动则保持在栈中。当用户按下返回键,当前活动会从栈中弹出,而前一个活动成为当前正在运行的活动。 栈中包含有对象,因此如果一个任务中有同一个活动子类的多个实例处于开启状态,比如说多个地图显示程序,栈对于每个实例会有单独的入口。栈中的活动不会被重新排序,仅仅是被推入和弹出而已。 一个任务是一个包含多个活动的栈,不是manifest文件中的一个类或是元素。因此无法单独为一个任务中的活动设定值。在栈底的活动中,整个任务的值会被设定。比如,下一节中将讨论“任务的关系(affinity)”,通过读取一个任务的底部活动的“关系”可以获得这个任务的值。 一个任务中所有的活动作为一个整体一起移动。整个任务(即整个活动栈)可以被带至前台或是送往后台。设想一下,比如说当前任务有四个活动在栈中,其中三个活动在当前活动之下。用户按下了HOME键,前往了程序启动器并选择了一个新的程序(事实上是一个新的任务)。当前的任务被送往了后台,同时新的任务的底部活动被显示。一段时间以后,用户回到主界面并再次选择了之前的那个程序(或者说任务)。那个有四个活动在栈的任务将返回前台。当用户按下返回键,用户刚刚离开的那个活动不会被显示(即前一个任务的底部活动),而是显示同一个栈中的上一个活动(同时该栈的顶部活动被移除)。 上一段所描述的是活动和任务的默认行为。但是可以通过一些方法来完全地改变它们的行为。活动和任务间的关联,以及一个任务中某一个活动的行为,是由交互旗标(interaction flag)和属性(attribute)所控制的,前者存在于启动了活动的Intent对象中,后者存在于manifest中活动的<activity>元素中。两者都能改变活动和任务间的行为。

主要的意图旗标有:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
FLAG_ACTIVITY_SINGLE_TOP

主要的<activity>属性有:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch

下面将描述其中一些旗标和属性的作用,以及它们如何进行交互工作和使用上的注意事项。

关系(affinity)和新任务

一个程序中的所有活动默认和其他活动有一种“关系”——即,它们默认属于同一个任务。然而,可以用<activity>元素中的taskAffinity属性来为每一个活动设置单独的关系。不同程序中的活动可以被定义为共享同一个关系,同一个程序中的活动也可以被分配不同的关系。在两种情况下关系将发挥作用:当Intent对象启动了一个包含有FLAG_ACTIVITY_NEW_TASK旗标的活动时,或者这个活动的allowTaskReparenting属性值为true

FLAG_ACTIVITY_NEW_TASK旗标

正如前面所说的,一个新的活动默认会加入调用了startActivity()的那个活动的任务中。它会被推入相同的一个栈中。然而,如果被传递给startActivity()的Intent对象里包含有FLAG_ACTIVITY_NEW_TASK旗标的话,系统会给新的活动分配一个不同的任务。如旗标的名称,通常那会是一个新的任务。不过也可以不是新任务。如果存在一个任务和这个新的活动有同样的关系,这个活动就会加入这个活动。不然则是创建一个新的任务。

allowTaskReparenting属性

如果一个活动的allowTaskReparenting属性值为“true”,这个活动能在另一个和它有关系的任务来到前台时从启动它的任务转移到入这个任务中。例如,在一个旅行用程序中有一个活动能报告所选地点的天气状况。这个活动和该程序中的其他活动一样有着相同的关系(也就是默认关系),并且允许重新指定父级。当你自己的一个活动启动了天气报告活动,这两个活动是属于同一个任务的。然而,当那个旅行用程序再次来到前台,天气报告活动将被重新分配给该任务并被显示。

如果,从用户的角度来看,一个.apk文件包含了不止一个的程序,将有必要对与这些程序相关的活动分配不同的关系。

启动模式

有四种不同启动模式可供指定给一个<activity>元素的launchMode属性:

standard” (默认模式)
singleTop
singleTask
singleInstance

这些模式在以下四个方面有所不同:

  • 对意图进行响应的活动的所属任务的不同。对于“standard”和“singleTop”模式,活动属于创建了意图(并调用了startActivity())的任务——除非Intent对象包含了FLAG_ACTIVITY_NEW_TASK旗标。因为这种情况下会像之前在“关系和新任务”一节中说的那样选择一个不同的任务。 与此相对应的,“singleTask”和“singleInstance”模式将活动标记为始终处于任务底部。它们这些活动定义了一个任务并将不会在其他任务中运行。
  • 活动是否存在多个不同的实例。一个“standard”或是“singleTop”的活动可以被多次实例化。它们可以属于多个任务,并且一个任务里可以有该活动的多个实例。 相对应的,“singleTask”和“singleInstance”的活动被限定于只能有一个实例。这些活动处于任务的底部,因此这个限制意味着在设备上该任务不可能同时存在一个以上的实例。
  • 活动的实例是否在它所属的任务中有其他活动。一个“singleInstance”活动作为它所属的任务中唯一的一个活动而存在。如果它启动了另一个活动,无论那个活动的启动模式是什么都必定会在另一个不同的任务中被创建,就好象意图中含有FLAG_ACTIVITY_NEW_TASK。在其他的方面,“singleInstance”模式等同于”singleTask“。另外的三种模式允许多个活动属于一个任务。“singleTask”活动将始终是任务的根活动,这个活动可以启动其他将被分配到该任务的活动。“standard”和“singleTop”活动的实例可以存在于栈的任何位置。
  • 一个类的新的实例是否会被启动以处理一个新意图。对于默认的“standard”模式,在响应每一个新意图时都会创建一个新的实例。每一个实例只能处理一个意图。对“singleTop”模式来说,一个已经存在的类实例在当它处于目标任务的活动栈的顶部时,它可以被重新使用以处理一个新的意图。如果这个活动不在顶部,则无法被重用。这时,会有一个新的实例被创建并被推入栈中来处理这个新意图。
    例如,假设一个任务的活动有一个根活动A,在其上按顺序有活动B,C,D。所以这个栈就是A-B-C-D。这时传来了一个意图启动类型D的活动,如果D是“standard”的启动模式,这个类就启动一个新的实例,同时栈变为A-B-C-D-D。然而要是D的启动模式是“singleTop”,那么现有的这个实例会去处理那个新的意图(因为它已经在栈顶了),这个栈会保持为A-B-C-D。
    另一方面来说,如果这个意图是启动B类型的,无论B的模式是“standard”还是“singleTop”,都会启动一个新的实例(因为B不再栈顶),使这个栈变为A-B-C-D-B。
    如前面所说到的,“singleTask”或“singleInstance”类型的活动只能有一个实例,所以该实例会处理所有的新意图。“singleInstance”活动永远保持在栈的顶部(因为它是这个栈里唯一的活动),所以它会始终处理新的意图。然而,“singleTask”活动可能会存在一些在栈中位于其上方的活动,那样它就不处于处理意图的位置了,这个意图回被舍弃。(即使意图被舍弃了,它也会使任务按照本应该的那样来到前台。)

当一个存在的活动被要求处理一个新的意图时,Intent对象通过onNewIntent()被传递至这个活动。(最初启动这个活动的意图对象可以通过调用getIntent()来检索)
需要注意,当一个活动的新实例被创建以处理新的意图时,用户可以按下返回键来回到前一个状态(或者说前一个活动)。但是当一个已经存在的活动处理一个新的意图时,用户无法通过返回键来回到这个实例在接受意图之前的状态。

关于启动模式的更多内容,参见<activity>元素的描述。

栈的清除

如果用户长时间不运行一个任务,系统会清除除了根活动外其他所有的活动。当用户再次返回到这个任务时,这个任务会和离开之前一样,不过只有最初的活动会被呈现。这一思想的实质是认为用户在一段时间后返回之前的任务时会希望放弃之前行进的工作而开始新的。

这是默认的情况。可以通过一些活动属性来控制并修改这一行为:

alwaysRetainTaskState属性

如果这个属性在一个任务的根活动里被设置为“true”,那么之前描述的默认行为就不会发生。任务会长时间保持所有的活动于栈中。

clearTaskOnLaunch属性

如果这个属性在一个任务的根活动里被设置为“true”,那无论用户是离开任务还是回到任务,该栈都会被清除到只剩根活动。换言之,这是alwaysRetainTaskState的相反极。即使只经过了短暂的离开,用户也会回到任务的最初状态。

finishOnTaskLaunch属性

这个属性类似于clearTaskOnLaunch属性,不过它只对单个活动有效而不是整个任务。而且它能清除包括根活动在内的任何活动。当它的值为“true”时,该活动仅仅在当前状态下才是任务的一部分。一旦用户离开后,即使再次返回,这个活动也不会被呈现。

还有一种方法可以强制活动从栈中清除。如果一个Intent对象含有FLAG_ACTIVITY_CLEAR_TOP旗标,同时目标任务已经存在有一个应当处理此意图的活动类型的实例,那么这个实例之上的所有活动将被清除从而该实例能处于栈顶来响应这个意图。如果这个活动的启动模式是“standard”,它同样也会被移除出栈,会有一个新的实例被启动来处理那个意图。之所以会这样的原因是“standard”启动模式下会为每一个新意图创建一个新的实例。

FLAG_ACTIVITY_CLEAR_TOP通常都会和FLAG_ACTIVITY_NEW_TASK一起使用。此时,这两个旗标的作用是将一个已存在的活动置于另一个任务中的某个合适的位置使其可以响应某个意图。

任务的开始

通过给一个活动设置“android.intent.action.MAIN”和“android.intent.category.LAUNCHER”这两个意图过滤器来设定其动作和目录,可以将这个活动设为一个任务的入口。(在之前的意图过滤器部分有这样的一个例子。)这种类型的过滤器会在应用启动器中显示该活动的图标和标签,使用户可以启动这个任务,或在启动后的任何时刻返回到该任务。

这里的第二个能力很重要:用户必须可以在离开一个任务之后再回到这个任务。为此,“singleTask”和“singleInstance”这两个会使活动始终初始化任务的启动模式应当仅被用于有MAINLAUNCHER过滤器的活动。想象一下,假如没有过滤器会发生什么:一个意图启动了“singleTask”的活动,初始化了一个新任务,然后用户在这个任务中进行了操作。之后用户按下HOME键。任务被置于后台,被主界面遮挡住。但是,由于它没有被显示于应用启动器中,因此用户将无法返回到这个任务。

对于FLAG_ACTIVITY_NEW_TASK旗标来说也有类似的问题。在这个旗标使一个活动开始一个新的任务后,如果用户按了HOME键离开的话,必须提供某种方式使用户可以重返这个任务。有些实体(比如通知管理器)始终会在一个外部而非其自身的任务中启动活动,所以它们始终会在传递至startActivity()的意图中包含FLAG_ACTIVITY_NEW_TASK。如果你的活动涉及某个会使用该旗标的外部实体时,注意要提供用户一个返回到任务的途径。

如果你不希望用户可以返回到原来的活动的话,把<activity>元素的finishOnTaskLaunch属性设置为“true”即可。具体的可以参见之前的栈的清除。

进程和线程

当一个应用程序组件的第一个部分要运行时,Android会启动为它启动一个Linux进程及一个单独的执行线程。默认来说,所有的组件会在这个线程和进程里运行。

不过,你也可以让组件运行于其他进程,或者是在任意其他进程中生成新的线程。

进程

一个组件将运行于哪一个进程是由manifest文件控制的。组件元素——<activity><service><receiver><provider>——每一个都含有一个process属性,指定了该组件应当运行于哪一个进程。可以设置这些属性使每一个组件运行于其自身的进程,或者可以让一部分的组件共用同一个进程。甚至可以设定让不同程序中的组件运行于同一个进程——假设这些程序共享同样的Linux用户ID并被相同的权限标记。<application>元素也有<process>属性,用来给所有的组件设定默认值。

所有组件都是在制定进程的主线程中被实例化的,系统对组件的调用也是从这个线程中分发的。系统不会对每一个实例创建单独的线程。因此,那些响应调用的方法——比如View.onKeyDown()这类的报告用户操作的方法,或者是在之后的组件生命周期一节中会讨论的生命周期通知(lifecycle notification)——总是会运行于进程的主线程中。这意味着组件在被系统调用时不应该长时间或阻断性地运行(比如网络工作或是循环计算),因为这样会阻断在进程中的其他组件。你可以为长期操作创建单独的线程,如同接下来会讨论的那样。

Android在某些时候(比如内存不足而用户又马上需要更多的内存来运行其他的进程)会做出关闭某个进程的决定。运行于该进程的应用程序组件也就相应地会被销毁。当再次需要它们时将会重启一个进程。

在决定终止哪一个进程时,Android会权衡它们对于用户的重要性。比如,系统更有可能关闭一个活动在屏幕上不可见的进程而不是一个活动仍然可见的进程。因此,是否终止一个进程取决于进程中组件的运行状态。而这则是之后的章节,组件生命周期,的内容了。

线程

即使你可能会限制你的程序运行于单独的进程,有时你也可能需要创建一个线程用于后台工作。由于用户界面必须快速响应用户操作,含有活动的线程不应当也含有消耗时间的操作(比如网络下载)。任何可能无法迅速完成的工作都应该被分配给不同的线程。

线程在代码中通过Java的Thread对象创建。Android提供了一系列类来便捷地管理线程——在线程中进行消息循环的Looper,执行消息的Handler,以及设立含有消息循环的线程的HandlerThread。

远程过程调用

Android有一种轻量级的远程过程调用机制(remote procedure call — RPC)——一种方法会被本地调用,之后(在另一个进程中)远程执行,最后结果会被传回调用者。这样势必会在一定程度上分解方法调用以及所有相关数据使系统得以能够理解它们,将其从本地进程传送出去,在远程进程中指定空间,重组和重新调用。返回值必须被反向传输。Android提供了完成这些工作的所有代码,因此你只需要集中注意力于定义和实现RPC接口自身就好。

RPC接口只包含方法。默认地,即使没有返回值,所有方法也都是同步执行的(本地方法在远程方法完成前将被阻塞)。

简单来说,这个机制将这样运作:由通过一个简单的IDL(接口定义语言)声明你想要执行的RPC接口开始。在这个声明中,
aidl工具生成一个必须在本地和远程进程中都可用的Java接口定义。它如下图所示含有两个类:

内部类含有所有需要的用于管理通过IDL声明的接口远程过程调用的代码。两个内部类都有IBinder接口。其中一个在系统内部被本地使用,你写的代码可以忽略它。另一个,Stub,继承于Binder类。另外,为使内部代码有效化IPC调用,它包含了你所声明的RPC接口的方法的声明。如图所示,你需要Stub的子类来使用这些方法。

一般来说,远程进程由一个服务来管理(因为服务可以告知系统这个进程与其他进程的联系)。该进程含有由aidl工具生成的接口文件和执行RPC方法的Stub子类。服务的客户端则只含有aidl工具生成的接口文件。

这里将说明服务和它的客户端之间的连接是如何被建立的:

  • 服务的(本地端的)客户端会执行onServiceConnected()和onServiceDisconnected()方法以获知何时与远程服务的连接被成功建立记忆何时中断。它们之后将调用bindService()来建立连接。
  • 服务的onBind()方法可以被用来接收或是拒绝连接,这取决于它所收到的意图(即传递到bindService()的意图)。如果一个连接被接受,它将返回一个Stub子类的实例。
  • 如果服务接受了连接,Android会调用客户端的onServiceConnected()方法并传给它一个IBinder对象。这是一个由服务管理的Stub子类的代理。通过这个代理,客户端能够在远程服务中进行调用。

以上的描述中省略了RPC机制的细节。更多信息请参见“使用AIDL设计远程接口”和IBinder类的描述。

线程安全(thread-safe)方法

在一些context中,你使用的方法可能会被不止一个线程调用,因此必须使这段代码线程安全。

对于会被远程调用的方法来说,这是非常正确的——就像在前一节的RPC机制中所提到的那样。当IBinder对象内的一个方法的调用在与IBinder相同的进程中生成时,这个方法会在调用者的线程中被执行。然而,当这个调用在另外的进程中生成时,Android会在IBinder所在进程中的线程里选择一个来执行这个方法;它不在进程的主线程中被执行。比如说,一个服务的onBind()方法会被服务所在进程的主线程调用,而onBind()所返回的对象(比如使用RPC方法的一个Stub子类)中使用的方法会被池线程调用。由于服务可以有不止一个客户端,多个池线程可以同时对同一个IBinder方法操作。因此IBinder方法必须线程安全。

同样地,内容提供者可以接收能创建其他进程的数据请求。尽管ContentResolver和ContentProvider类隐藏了多进程通信管理的细节,响应了query(),insert(),update()和getType()方法的ContentProvider方法是被内容提供者所在进程的池线程而非主线程调用的。由于这些方法可能会同时被多个线程调用,因此它们也必须确保线程安全。

组件生命周期

应用程序组件具有生命周期——从Android响应意图而实例化组件开始到该实例被销毁为止。在这之间,它们有时处于活动状态,有时处于非活动状态(对于活动来说,则是可见与不可见)。这一节会讨论活动、服务、广播接收者的生命周期——包括在它们的存在期间内的各种状态,提示状态转换的方法,以及这些状态对组件所属进程是否被终止及实例是否被销毁等的可能性的影响。

活动的生命周期

一个活动有三种必要的状态:

  • 当它在屏幕前台(处于当前任务的活动栈的顶部)时,它是活动的(active)或者说是正在运行的。该活动处于用户操作的焦点之下。
  • 当它失去焦点而仍然可见时,它是暂停的(paused)。也就是说,有另一个活动在其上方,但这个活动是透明的或没有覆盖整个屏幕,因此一些暂停的活动依旧是可见的。一个暂停的活动仍然是存在的(alive),它保持着所有的状态和成员信息并和窗口管理器(window manager)相关联,不过在内存过低的情况下系统会杀死这个活动。
  • 如果它完全被另一个活动遮盖,它是停止的(stoped)。它仍保持着所有的状态和成员信息。不过,它已经不再对用户可见了,因此它的窗口会被隐藏,通常会被系统在需要内存时关闭。

当一个活动是暂停或者停止的时候,系统可以通过要求其终止(调用其finish()方法)或者直接杀死它的进程来将它从内存中清除。当它再次呈现给用户时,它将是完全重启启动的,并被还原为它先前的状态。

随着一个活动在不同状态之间的切换,以下protected方法的调用能指示出这些变化:

void onCreate(Bundle savedInstanceState)
void onStart()
void onRestart()
void onResume()
void onPause()
void onStop()
void onDestroy()

所有这些方法都能被override以使在状态转换时正常工作。所有的活动都必须在对象被初次初始化时使用onCreate()来初始,不少活动还会使用onPause()来确认数据变化或准备停止用户交互操作。

调用父类的方法

使用任何活动生命周期的方法前都应先调用其父类的版本,比如
protected void onPause() {
super.onPause();
. . .
}

总的来说,这七种方法定义了一个活动的整个生命周期。通过使用这些方法,可以监控三种嵌套循环:

  • 一个活动的完整的生命周期处于第一次对onCreate()的调用与对onDestory()的调用之间。一个活动在onCreate()中完成左右的“全局(global)”状态设置,并在onDestroy()中将所有的资源释放。比如,对于一个从网络上下载数据的背景线程,活动将在onCreate()中创建该线程,之后在onDerstroy()中停止它。
  • 一个活动的可见的生命周期始于对onStart()的调用而止于对onStop()的调用。在此期间,用户可以在屏幕上看到活动,尽管它可能没有处于最前端和用户发生交互操作。在这两种方法之间,可以保留需要的资源以向用户显示该活动。比如,可以在onStart()中注册一个广播接收者来监视对UI会有影响的变化,之后当用户不会再看到屏幕显示内容时在onStop()中注销该接收者。onStart()onStop()方法可以多次被调用,活动也会在可见和不可见之间转换。
  • 一个活动的前台生命周期始于对onResume()的调用而止于对onPause()的调用。在此期间,该活动处于所有其他活动的上方并与用户发生交互。一个活动可能会在resumed和paused状态间频繁切换——比如,当设备进入睡眠状态或一个新的活动启动时,onPause()会被调用,而当一个活动得出结果或是有新的意图被传递时,onResume()会被调用。因此,在这两种方法间的代码应当是非常轻量级的。

下面的图描绘出了以上这些循环以及一个活动在状态间可能的转换路径。彩色的椭圆是一个活动的主要状态。线框中代表当活动转换状态时你可以用来执行操作的反馈方法。

下面的表格更加详细地介绍了这些方法,并说明了它们在整个生命周期中的位置:

方法 描述 可杀? 后继
onCreate() 当活动被创建时被调用。所有静态设定应当在此进行,包括创建view、对列表的数据绑定等等。会有一个Bundle对象传递给该方法,它包含了活动前一个状态的信息(如果该状态被记录的话)(参见之后的 保存活动状态)。

始终后续 onStart() 。

不可 onStart()
onRestart() 当活动停止时被调用,仅发生于被再次启动之前

始终后续 onStart() 。

不可 onStart()
onStart() 仅在活动可见之前被调用。

当活动来到前台时,后续 onResume() ,当活动退出前台隐藏时后续 onStop()

不可 onResume()

onStop()
onResume() 仅在活动开始用户交互前被调用。这时该活动处于活动栈的顶部,可以接受用户输入。

始终后续 onPause().

不可 onPause()
onPause() 当系统将要恢复另一个活动时被调用。该方法主要用于保存尚未保存的修改、停止动画,或者其他需要消耗处理器资源的工作。该方法应当迅速完成,不然下一个活动将一直等待至其完成才恢复。

当活动回到前台时后续 onResume() ,当活动变为不可见时后续 onStop()

可以 onResume()

onStop()
onStop() 当活动不再对用户可见时被调用。当该活动被销毁或有其他活动(已存在的或是新的活动)被恢复来覆盖此活动时会使用该方法。

当活动回到前台与用户发生交互时后续 onRestart() ,当活动被舍弃时后续onDestroy()

可以 onRestart()

onDestroy()
onDestroy() 当活动被销毁时被调用。这时一个活动能收到的最后一次调用。当活动完成(调用了finish() )或是系统为节省空间而销毁了该活动的实例时会调用该方法。可以通过 isFinishing() 来区别这两种情况。 可以

注意上表中的可杀一栏。它说明了系统是否可以在方法返回后的任一时刻杀死运行该活动的进程而不必执行更多的活动代码。三种方法(onPause()onStop()onDestroy())被标记为“可以”。由于onPause()是三者中的第一个,因此它是唯一可以保证在进程被杀之前被调用的——onStop()onDestroy()就不一定了。因此,应该使用onPause()来执行保存长期数据(比如用户编辑的内容)的工作。

可杀一栏中标记为“不可”的方法在它们被调用时会保护运行活动的进程不被杀除。因此一个活动在,比如说,onPause()返回直到onResume()被调用之间,是可杀的。在onPause()再次返回之前,它不会再次变为可杀的。

之后的“进程和生命周期”一节中将会提到,一个在定义上“技术性不可杀”的活动仍有可能被系统杀除——不过这只会发生在资源缺乏极端严重的情况下。

保存活动状态

当系统而不是用户关闭一个活动来获得内存时,用户会希望能再次回到活动并处于之前的状态。

为获取活动被杀之前的状态,可以在活动中使用onSaveInstanceState()状态。Android会在杀死活动之前调用这个方法——也就是在onPause()被调用前。会有一个Bundle对象传递给该方法,其中以名称-值对(name-value pair)的形式记录了活动的动态状态。当活动重新启动时,Bundle会被传递给onCreate()以及在onStart()之后调用的一个方法,即onRestoreInstanceState(),这样这两个方法都可以重创建被记录的状态。

onPause()及其他之前提到的方法不同,onSaveInstance()onRestoreInstanceState()不是生命周期中的方法。它们并非总是会被调用。比如,Android会在活动可能会被系统销毁前调用onSaveInstanceState(),但不会在用户销毁该实例时(比如按下返回键)调用它。这种情况下,用户不希望返回该活动,因此不必保存其状态。

由于onSaveInstanceState()并不是总是会被调用,因此应该只用它记录活动的瞬间状态,而非长期数据。应当使用onPause()来保存长期数据。

协调活动

当一个活动启动了另一个时,它们都会经历生命周期的转换。当其中一个暂停或是停止时,另一个会启动。有时,必须协调这些活动之间的关系。

当同一个进程中仅两个活动时,其生命周期的回馈顺序被清楚地定义如下:

  1. 当前活动的onPause()方法被调用。
  2. 接下来,被启动的活动的onCreate()onStart()onResume()方法依次被调用。
  3. 之后,如果启动的活动不再屏幕可见,它的onStop()方法会被调用。

服务生命周期

一个服务可以通过两种方法使用:

  • 一个服务可以被启动并允许运行直至有人停止它或是它自动停止。在这种模式下,它通过调用Context.startService()启动,调用Context.stopService()停止。通过调用Service.stopSelf()或Service.stopSelfResult()它可以自动停止。无论调用了多少次startService(),只需一次stopService()调用就可以停止服务。
  • 一个服务可以通过它定义输出的一个接口来被预编程式地操作。客户端建立一个与Service对象的连接,然后以此来调用服务。通过调用Context.bindService()来建立这个连接,调用Context.unbindService()来断开。多个客户端可以与同一个服务绑定。如果服务还没有被启动,bindService()可以选择启动它。

这两种模式不完全是不相关的。你可以绑定一个通过startService()启动的服务。比如,一个背景音乐服务以一个识别音乐播放操作的Intent对象调用startService()来启动。不久,比如当用户想对播放器进行某些操作来获取当前音乐的信息时,一个活动会通过调用bindService()与服务绑定连接。在这种情况下,stopService()在最后一次的绑定被断开前无法真正停止服务。

像活动一样,对于服务也有生命周期方法,可以用它们来监视状态变化。不过方法的数量较活动要少——只有三种——并且它们是public而非protected的:

void onCreate()
void onStart(Intent intent)
void onDestroy()

使用这些方法,可以监视服务的生命周期的两种嵌套循环:

  • 一个服务整个的生命期在onCreate()的调用和onDestroy()的调用之间。和活动一样,一个服务在onCreate()中进行初始化,在onDestroy()中释放所有的资源。比如,一个音乐播放服务在onCreate()中创建播放音乐的线程,之后在onDestroy()中终止这一线程。
  • 一个服务的有效的生命周期始于onStart()的调用。这个方法包含将被传递给startService()的Intent对象。该音乐服务会打开Intent来寻找播放哪一首音乐并播放。没有相对应于服务停止的回馈——即没有onStop()方法。

onCreate()onDestroy()方法被所有类型的服务调用,不管它们是由Context.startService()启动还是由Context.bindService()启动。不过,onStart()只被由startService()启动的服务所调用。

如果一个服务允许被绑定,那么它会有更多可用的回馈方法:

IBinder onBind(Intent intent)
boolean onUnbind(Intent intent)
void onRebind(Intent intent)

onBind()回馈会被传递一个传递给bindService的Intent对象,而对于onUnbind()则是一个被传递给unbindService()的意图。如果一个服务允许绑定,onBind()会返回客户端与其交互的通讯频道。当新的客户端与服务连接时onUnbind()方法可以要求调用onRebind()。

下图说明了一个服务的回馈方法。尽管它区分了由startService()创建的和由bindService()创建的服务,不过注意任何服务不论它是如何被创建的,都可以被客户端绑定,也就是说任何服务都可以接收onBind()onUnbind()调用。

广播接收者生命周期

广播接收者只有一种回馈方法:

void onReceive(Context curContext, Intent broadcastMsg)

当一条广播消息传递至接收者时,Android调用接收者的onReceive()方法并将包含该消息的Intent对象传给它。广播接收者仅有在执行该方法时才被认为是活动的(active)。当onReceive()返回时,它就变为非活动状态。

包含活动的广播接收者的进程是不会被杀的。只包含非活动组件的进程则可能在任何其他进程需要内存的时候被系统杀除。

当对一条广播消息的响应需要消耗时间时这就会有问题,因此,必须在另外的线程中完成这些工作,而不是在还有其他UI相关组件运行着的主线程。如果onReceive()生成线程后返回,这个进程包括新的线程会被认为是非活动的(除非进程中有其他活动的应用程序组件),而处于被杀除的危险之中。该问题的解决方案是为onReceive()启动一个服务,让服务来执行工作,这样系统就能知道进程中还有活动着的工作正在被处理。

下一节会更深入地讨论将被杀除的进程的脆弱性(vulnerability)。

进程和生命周期

Android系统试图尽可能长地维持一个应用程序进程,但在内存不足时它最终不得不移除旧的进程。Android将每一个进程放入一个由组件状态所决定的“重要性层级(importance hierarchy)”中以决定哪些进程要保持而哪些要杀除。最低重要性的进程会被首先杀除,之后是次重要的。共有五种层级。下面依重要性列出了他们:

1. 前台进程是用户当前需要操作的进程。当下列条件之一满足时一个进程被认为是前台的:

  • 它正在运行一个与用户发生交互的活动(Acitvity对象的onResume()方法被调用)。
  • 它有一个与用户正在操作的活动相绑定的服务。
  • 它有一个正在执行其生命周期回馈方法之一(onCreate(),onStart()或onDestroy())的Service对象。
  • 它有一个正在执行其onReceive()方法的广播接收者。

在任何时候,只会存在少数的前台进程。它们将被最后杀除——在内存极端不足的情况下。通常此时设备处于内存分页状态,需要杀除前台进程来保持用户界面响应。

2. 可见进程没有任何前台组件,但是可以影响用户在屏幕上看到的内容。当以下任一条件满足是一个进程为被认为是可见进程:

  • 它有一个不在前台但是被用户可见的活动(其onPause()方法被调用)。比如,当前台活动是一个对话框,而前一个活动仍可以被看到在其后方的情况。
  • 它有个一个与可见活动相绑定的服务。

可见进程被认为是很重要的,除非急需内存确保所有的前台进程,不然不会被杀除。

3. 服务进程是一个运行着由startService()方法启动的服务的进程,它不属于前两种进程。尽管服务进程不与任何用户直接看到的内容绑定,它们确实在执行用户希望的工作(比如背景播放mp3或是从网络下载数据),因此系统只有在亟需内存维持所有的前台和可见进程时才会杀除他们。

4. 后台进程有一个当前不对用户可见的活动(Activity对象的onStop()方法被调用)。这些进程对用户体验没有直接影响,当前台、可见或服务进程需要内存时它们会被随时杀除。通常有着大量的后台进程在运行,它们被保存在一个LRU表(Least recently used)中以确保最后看到的活动所在的进程会被最后杀除。如果一个活动正确地执行其生命周期方法并保存其状态,杀死它的进程不会影响用户体验。

5. 空进程是没有任何活动的(active)应用程序组件的进程。保持这样一个进程的唯一理由是作为缓存来提高下一次有组件要在其内运行时的启动速度。系统为了在进程缓存和底层核心缓存间平衡整体系统资源而常常杀除这些空进程。

Android根据进程中当前活动着的组件的重要性来尽可能高地给一个进程定级别。比如,如果一个进程包含一个服务和一个可见活动,那么进程会被分级为可见进程而不是服务进程。

另外,进程的层级可能会因为其他进程依赖于它而被提升。服务于另一个进程的进程不可能比那个进程的层级低。比如,进程A中的一个内容提供者服务于进程B中的一个客户端,又或是进程A中的一个服务与进程B中的一个组件绑定,那么进程A被认为至少要和进程B同样重要。

由于运行服务的进程比运行后台活动的进程层级高,一个执行长时间操作的活动应该开启一个服务来处理该工作而不是简单地创建一个线程——特别是该操作可能比活动本身持续更久时。这样的例子有个后台播放音乐或是上传一张拍摄的照片到网上。使用服务至少可以确保该操作有服务进程的优先级,而不必在意活动本身怎样。正如之前在广播接收者生命周期一节中提到的,这也是为什么广播接收者应当用服务而不是用线程来执行花费时间的工作的原因。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。