任务是用户尝试在您的应用中执行操作时与之互动的一系列 activity。这些 activity 按照每个 activity 的打开顺序排列在一个名为“返回堆栈”的堆栈中。
例如,电子邮件应用可能有一个 activity 用于显示新邮件列表。用户选择一条消息后,系统会打开一个新的 activity 来查看该消息。系统会将这个新 activity 添加到返回堆栈。然后,当用户点按或手势“返回”时,该新的 activity 会完成并从堆栈中弹出。
任务及其返回堆栈的生命周期
大多数任务都从设备主屏幕上启动。当用户轻触应用启动器或主屏幕上的应用图标或快捷方式时,该应用的任务会进入前台。如果应用不存在任务,系统会创建一个新任务,并将该应用的主 activity 作为堆栈中的根 activity 打开。
当前 activity 启动另一个 activity 时,这个新 activity 会被推送到堆栈顶部并获得焦点。前一个 activity 仍保留在堆栈中,但会停止。当某个 activity 停止时,系统会保留其界面的当前状态。当用户执行返回操作时,当前 activity 会从堆栈顶部弹出并销毁。上一个 activity 将恢复,并恢复其界面的上一个状态。
堆栈中的 activity 绝不会重新排列,只会被推送到堆栈中的 activity 和从堆栈中弹出,因为相应 activity 由当前 activity 启动并由用户通过返回按钮或手势关闭。因此,返回堆栈采用“后进先出”对象结构。图 1 显示了将 activity 推送到返回堆栈并从返回堆栈弹出的时间轴。
![](https://developer.android.google.cn/static/images/fundamentals/diagram_backstack.png?authuser=6&hl=da)
当用户继续点按或手势“返回”时,堆栈中的每个 activity 都会弹出以显示前一个 activity,直到用户返回主屏幕或任务开始时正在运行的任何 activity。从堆栈中移除所有 activity 后,任务将不再存在。
根启动器 activity 的返回点按行为
根启动器 activity 是使用 ACTION_MAIN
和 CATEGORY_LAUNCHER
声明 intent 过滤器的 activity。这些 activity 是独一无二的,因为它们充当从应用启动器进入应用的入口点,并用于启动任务。
当用户从根启动器 activity 点按或手势“返回”时,系统会根据设备搭载的 Android 版本以不同的方式处理该事件。
- Android 11 及更低版本上的系统行为
- 系统结束 activity。
- Android 12 及更高版本中的系统行为
系统会将 activity 及其任务移至后台,而不是完成 activity。使用主屏幕按钮或手势从应用中导航出应用时,此行为与默认系统行为相符。
在大多数情况下,此行为意味着用户可以更快地从温状态恢复应用,而不必从冷状态完全重启应用。
如果您需要提供自定义返回导航,我们建议您使用 AndroidX Activity API,而不是替换
onBackPressed()
。如果未发生拦截系统返回点按操作的组件,AndroidX Activity API 会自动遵循适当的系统行为。不过,如果您的应用替换
onBackPressed()
来处理返回导航并完成 activity,请更新您的实现来调用super.onBackPressed()
,而不是完成 activity。调用super.onBackPressed()
会在适当时将 activity 及其任务移至后台,并为各应用中的用户提供更一致的导航体验。
后台和前台任务
![](https://developer.android.google.cn/static/images/fundamentals/diagram_multitasking.png?authuser=6&hl=da)
任务是一个统一的单元,可在用户开始新任务或转到主屏幕时移至后台。在后台时,任务中的所有 activity 都会停止,但任务的返回堆栈保持不变,即当其他任务发生时,任务会失去焦点,如图 2 所示。然后,任务可以返回到前台,以便用户从上次停下的地方继续。
假设当前任务 A 的堆栈中有三个 activity,包括当前 activity 下的两个 activity,请考虑以下任务流:
用户使用主屏幕按钮或手势,然后从应用启动器启动新应用。
出现主屏幕时,任务 A 会进入后台。新应用启动时,系统会使用自己的 activity 堆栈为该应用启动一项任务(任务 B)。
与该应用互动后,用户再次返回主屏幕并选择最初启动任务 A 的应用。
现在,任务 A 进入前台,其堆栈中的所有三个 activity 均保持不变,且堆栈顶部的 activity 恢复。此时,用户还可以切换回任务 B,方法是转到主屏幕并选择启动该任务的应用图标,或者从“最近使用的应用”屏幕中选择该应用的任务。
多个 activity 实例
![](https://developer.android.google.cn/static/images/fundamentals/diagram_multiple_instances.png?authuser=6&hl=da)
由于返回堆栈中的 activity 绝不会重新排列,因此,如果您的应用允许用户从多个 activity 启动特定 activity,则系统会创建该 activity 的新实例并将其推送到堆栈,而不是将任何先前的 activity 实例置于顶部。因此,应用中的一个 activity 可能会多次实例化,即使来自不同的任务也是如此,如图 3 所示。
如果用户使用“返回”按钮或手势向后导航,系统会按 activity 实例的打开顺序显示它们,并且每个实例都有自己的界面状态。不过,如果您不想多次实例化 activity,可以修改此行为。如需了解详情,请参阅管理任务部分。
多窗口环境
当应用在 Android 7.0(API 级别 24)及更高版本支持的多窗口模式环境中同时运行时,系统会单独管理每个窗口的任务。每个窗口可以包含多个任务。在 Chromebook 上运行的 Android 应用也是如此:系统会按窗口管理任务或任务组。
生命周期回顾
Activity 和任务的默认行为总结如下:
当 activity A 启动 activity B 时,activity A 会停止,但系统会保留其状态,例如其滚动位置以及输入到表单中的任何文本。如果用户在 activity B 中点按或使用“返回”手势,activity A 会恢复并恢复其状态。
当用户使用主屏幕按钮或手势离开任务时,当前 activity 会停止,其任务会进入后台。系统会保留任务中每个 activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,任务会进入前台并恢复堆栈顶部的 activity。
如果用户点按或做出“返回”手势,当前 activity 会从堆栈中弹出并销毁。堆栈中的上一个 activity 将恢复。销毁 activity 后,系统不会保留该 activity 的状态。
当应用在搭载 Android 12 或更高版本的设备上运行时,根启动器 activity 的此行为有所不同。
Activity 可以多次实例化,甚至是从其他任务对其进行实例化。
管理日常事务
Android 通过将连续启动的所有 activity 置于同一任务中的后进先出堆栈中,来管理任务和返回堆栈。这对于大多数应用来说非常棒,并且您通常不必担心 activity 与任务的关联方式或任务如何存在于返回堆栈中。
但是,您可能会决定要中断正常行为。例如,您可能希望应用中的 activity 在其启动时启动新任务,而不是将其放置在当前任务中。或者,当您启动某个 activity 时,您可能希望前置该 activity 的现有实例,而不是在返回堆栈之上创建新实例。或者,您可能希望在用户离开任务时清除返回堆栈中的所有 activity(根 activity 除外)。
您可以使用 <activity>
清单元素中的属性和传递给 startActivity()
的 intent 中的标志来执行这些操作。
以下是可用于管理任务的主要 <activity>
属性:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
以下是您可以使用的主要 intent 标志:
以下部分讨论如何使用这些清单属性和 intent 标志来定义 activity 如何与任务关联,以及它们在返回堆栈中的行为方式。
此外,还讨论了有关如何在“最近使用的应用”屏幕中表示和管理任务与 activity 的注意事项。通常,您可以让系统定义任务和 activity 在“最近使用的应用”屏幕中的表示方式,并且无需修改此行为。如需了解详情,请参阅“最近使用的应用”屏幕。
定义启动模式
通过启动模式,您可以定义 activity 的新实例如何与当前任务关联。您可以通过两种方式定义启动模式,如以下部分所述:
-
在清单文件中声明 activity 时,您可以指定 activity 在启动时如何与任务关联。
-
调用
startActivity()
时,您可以在Intent
中添加一个标志,用于声明新 activity 如何(或是否)与当前任务关联。
因此,如果 activity A 启动 activity B,activity B 可以在其清单中定义如何与当前任务关联,并且 activity A 可以使用 intent 标志请求 activity B 如何与当前任务相关联。
如果两个 activity 都定义了 activity B 如何与任务关联,则 intent 中定义的 activity A 的请求将优先于 activity B 的请求(如其清单中所定义)。
使用清单文件定义启动模式
在清单文件中声明 activity 时,您可以使用 <activity>
元素的 launchMode
属性指定 activity 如何与任务关联。
您可以为 launchMode
属性指定五种启动模式:
"standard"
- 默认模式。系统会在启动 activity 的任务中创建 activity 的新实例,并向其传送 intent。该 activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例。
"singleTop"
- 如果当前任务的顶部已存在 activity 实例,则系统会通过调用该实例的
onNewIntent()
方法向其传送 intent,而不是创建新的 activity 实例。activity 被多次实例化,每个实例可以属于不同的任务,并且一个任务可以有多个实例(但前提是返回堆栈顶部的 activity 不是 activity 的现有实例)。
例如,假设任务的返回堆栈由根 activity A 组成,activity B、C 和 D 在顶部(因此堆栈为 A-B-C-D,D 在顶部)。系统将为 D 类型的 activity 发出一个 intent。如果 D 具有默认的
"standard"
启动模式,则会启动该类的新实例,并且堆栈会变为 A-B-C-D-D。不过,如果 D 的启动模式为"singleTop"
,则 D 的现有实例会通过onNewIntent()
接收 intent,因为它位于堆栈顶部,并且堆栈仍然为 A-B-C-D。另一方面,如果 intent 到达 B 类型的 activity,则系统会向堆栈中添加 B 的新实例,即使其启动模式为"singleTop"
也是如此。"singleTask"
- 系统会在新任务的根位置创建 activity,或将该 activity 放置在具有相同相似性的现有任务上。如果已存在 activity 实例,则系统会通过调用现有实例的
onNewIntent()
方法向其传送 intent,而不是创建新实例。同时,它上面的所有其他 activity 都会被销毁。
"singleInstance"
。- 其行为与
"singleTask"
相同,只是系统不会将任何其他 activity 启动到包含该实例的任务中。该 activity 始终是其任务中的唯一 activity。由此启动的所有 activity 都会在单独的任务中打开。
"singleInstancePerTask"
。- activity 只能作为任务的根 activity(即创建该任务的第一个 activity)运行,因此在任务中只能有此 activity 的一个实例。与
singleTask
启动模式相反,如果设置了FLAG_ACTIVITY_MULTIPLE_TASK
或FLAG_ACTIVITY_NEW_DOCUMENT
标志,则可以在不同任务的多个实例中启动此 activity。
再举一个例子,Android 浏览器应用通过在 <activity>
元素中指定 singleTask
启动模式,声明网络浏览器 activity 始终在其自己的任务中打开。这意味着,如果您的应用发出打开 Android 浏览器的 intent,则其 activity 不会与应用置于同一任务中。相反,系统会为浏览器启动新任务;或者,如果浏览器已有一个任务在后台运行,系统会将该任务上移一层以处理新的 intent。
无论 activity 是在新任务中启动的,还是与启动它的 activity 在同一任务中启动,返回按钮和手势始终会将用户转到上一个 activity。不过,如果您启动指定了 singleTask
启动模式的 activity,且后台任务中存在该 activity 的实例,那么整个任务都会转到前台运行。此时,返回堆栈包含移至堆栈顶部的任务中的所有 activity。图 4 显示了此类情况。
![](https://developer.android.google.cn/static/images/fundamentals/diagram_backstack_singletask_multiactivity.png?authuser=6&hl=da)
"singleTask"
的 activity 添加到返回堆栈。如果 activity 已经是某个具有自己的返回堆栈的后台任务的一部分,那么整个返回堆栈也会移到当前任务之上。如需详细了解如何在清单文件中使用启动模式,请参阅 <activity>
元素文档。
使用 intent 标志定义启动模式
启动 activity 时,您可以通过在传递给 startActivity()
的 intent 中添加标志来修改 activity 与其任务的默认关联。可用于修改默认行为的标志如下:
FLAG_ACTIVITY_NEW_TASK
系统会在新任务中启动 activity。如果正在为正在启动的 activity 运行任务,系统会将该任务置于前台并恢复其上次的状态,并且该 activity 会在
onNewIntent()
中接收新的 intent。这会产生与上一部分中讨论的
"singleTask"
launchMode
值相同的行为。FLAG_ACTIVITY_SINGLE_TOP
如果要启动的 activity 是当前 activity(位于返回堆栈的顶部),则现有实例会收到对
onNewIntent()
的调用,而不是创建 activity 的新实例。这会产生与上一部分中讨论的
"singleTop"
launchMode
值相同的行为。FLAG_ACTIVITY_CLEAR_TOP
如果要启动的 activity 已在当前任务中运行,系统会销毁该 activity 上的所有其他 activity,而不是启动该 activity 的新实例。intent 会通过
onNewIntent()
传递给 activity 的已恢复实例(现在位于顶部)。launchMode
属性没有会产生此行为的值。FLAG_ACTIVITY_CLEAR_TOP
最常与FLAG_ACTIVITY_NEW_TASK
结合使用。结合使用这些标志可找到其他任务中的现有 activity,并将其置于可以响应 intent 的位置。
处理亲和性
“亲和性”指示 activity“首选”属于哪个任务。默认情况下,同一应用中的所有 activity 彼此具有亲和性:它们“更喜欢”位于同一个任务中。
不过,您可以修改 activity 的默认相似性。不同应用中定义的 activity 可以具有共同的相似性,并且同一应用中定义的 activity 可以被分配不同的任务相似性。
您可以使用 <activity>
元素的 taskAffinity
属性修改 activity 的亲和性。
taskAffinity
属性采用的字符串值必须与 <manifest>
元素中声明的默认软件包名称不同,因为系统会使用该名称来标识应用的默认任务亲和性。
亲和性可在两种情况下发挥作用:
启动 activity 的 intent 包含
FLAG_ACTIVITY_NEW_TASK
标志。默认情况下,新的 activity 会启动到名为
startActivity()
的 activity 的任务中。它会被推送到与调用方相同的返回堆栈中。不过,如果传递给
startActivity()
的 intent 包含FLAG_ACTIVITY_NEW_TASK
标志,系统会查找其他任务来存放新 activity。这通常是一项新任务。不过,情况并非必然如此。如果存在与新 activity 具有相同相似性的现有任务,则该 activity 会启动到该任务中。如果不存在,则会启动一个新任务。如果此标志导致 activity 启动新任务,并且用户使用主屏幕按钮或手势离开该任务,则必须为用户提供某种方式来返回到该任务。某些实体(例如通知管理器)始终在外部任务中启动 activity,绝不会作为自己的 activity 的一部分,因此它们始终将
FLAG_ACTIVITY_NEW_TASK
放入传递给startActivity()
的 intent 中。如果可能使用此标志的外部实体可以调用您的 activity,请确保用户可以独立地返回到启动的任务(例如使用启动器图标),其中任务的根 activity 具有
CATEGORY_LAUNCHER
intent 过滤器。如需了解详情,请参阅启动任务部分。当 activity 的
allowTaskReparenting
属性设置为"true"
时。在这种情况下,activity 可以从其启动的任务移至与其有亲和性的任务(当该任务出现在前台时)。
例如,假设一个用于报告所选城市天气状况的 activity 被定义为某个旅行应用的一部分。它与同一应用中的其他 activity 具有相同的相似性、默认应用相似性,并且可以使用该属性更改父项。
当您的某个 activity 启动天气报告程序 activity 时,它最初和该 activity 属于同一任务。不过,当旅行应用的任务进入前台时,系统会将天气预报 activity 重新分配给该任务并显示在其中。
清除返回堆栈
如果用户长时间离开任务,系统会清除除根 activity 之外的所有 activity 的任务。当用户返回任务时,系统仅恢复根 activity。系统基于这样一种假设:在一段时间后,用户会放弃之前正在进行的操作,并返回任务以开始新的任务。
您可以使用下列几个 Activity 属性修改此行为:
alwaysRetainTaskState
- 在任务的根 activity 中将该属性设置为
"true"
时,不会发生刚才所述的默认行为。即使应用很长时间后,任务仍会保留其堆栈中的所有 activity。 clearTaskOnLaunch
如果在任务的根 activity 中将此属性设置为
"true"
,那么每当用户离开任务并返回任务时,任务都会被清除到根 activity。也就是说,它与alwaysRetainTaskState
相反。用户始终会返回到任务的初始状态,即使只离开任务片刻后也是如此。finishOnTaskLaunch
此属性与
clearTaskOnLaunch
类似,但它适用于单个 activity,而非整个任务。它还可以导致除根 activity 之外的任何 activity 完成。如果设置为"true"
,则 activity 仅在当前会话中属于任务。如果用户在离开任务后再返回,任务将不复存在。
启动任务
您可以将 activity 设置为任务的入口点,方法是为其提供一个 intent 过滤器,其中 "android.intent.action.MAIN"
作为指定操作,"android.intent.category.LAUNCHER"
作为指定类别:
<activity ... >
<intent-filter ... >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
...
</activity>
此类 intent 过滤器会使 activity 的图标和标签显示在应用启动器中,以便用户启动 activity 并在启动后随时返回到其创建的任务。
第二个能力非常重要。用户必须能够离开任务,之后使用此 activity 启动器返回该任务。因此,当 activity 具有 ACTION_MAIN
和 CATEGORY_LAUNCHER
过滤器时,请仅使用 "singleTask"
和 "singleInstance"
这两种启动模式,这两种启动模式会将 activity 标记为始终启动任务。
例如,想象一下,如果缺少过滤器,会发生什么情况:intent 会启动一个 "singleTask"
activity,再启动一个新任务,而用户花了一些时间来处理该任务。然后,用户使用主屏幕按钮或手势。此时,该任务会转到后台,不再可见。现在,用户无法返回到任务,因为它未显示在应用启动器中。
如果您不希望用户能够返回 activity,请将 <activity>
元素的 finishOnTaskLaunch
设置为 "true"
。如需了解详情,请参阅清除返回堆栈部分。
如需详细了解如何在“最近使用的应用”屏幕中表示和管理任务及 activity,请参阅“最近使用的应用”屏幕。