任务是用户在尝试通过应用执行某项操作时与之互动的一系列 activity。这些 activity 排列在一个堆栈中,称为“返回堆栈”,按照每个 activity 打开的顺序排列。
例如,电子邮件应用可能有一个用于显示新邮件列表的 activity。当用户选择某条消息时,系统会打开一个新的 activity 来查看该消息。此新 activity 会添加到返回堆栈中。然后,当用户点按或通过手势执行“返回”操作时,该新 activity 会完成并从堆栈中弹出。
任务及其返回堆栈的生命周期
设备主屏幕是大多数任务的起点。当用户点按应用启动器或主屏幕中的应用或快捷方式图标时,该应用的任务就会转到前台。如果应用没有任务,系统会创建一个新任务,并将该应用的主 activity 作为堆栈中的根 activity 打开。
当当前 activity 启动另一个 activity 时,新 activity 会被推送到堆栈顶部,并获得焦点。之前的 activity 仍保留在堆栈中,但处于停止状态。当 activity 停止时,系统会保留其界面的当前状态。当用户执行返回操作时,当前 activity 会从堆栈顶部弹出并销毁。之前的 activity 会恢复,其界面的先前状态也会恢复。
堆栈中的 activity 绝不会重新排列,只会在启动时推入堆栈,在用户使用返回按钮或手势关闭时弹出堆栈。因此,返回堆栈采用后进先出结构,如图 1 所示。
当用户继续点按或通过手势执行“返回”操作时,堆栈中的每个 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 及其任务移至后台,并为用户提供更一致的应用间导航体验。
后台和前台任务
任务是一个内聚单元,当用户开始新任务或返回主屏幕时,任务可以移至后台。在后台运行时,任务中的所有 activity 都会停止,但任务的返回堆栈仍会保持不变,也就是说,当另一个任务发生时,该任务将失去焦点,如图 2 所示。然后,任务可以返回到前台,以便用户可以从他们离开的位置继续。
请考虑以下任务流,其中当前任务 A 的堆栈中有三个 activity,包括当前 activity 下的两个 activity:
用户使用主屏幕按钮或手势,然后从应用启动器启动新应用。
当主屏幕出现时,任务 A 会进入后台。当新应用启动时,系统会为该应用启动一个任务(任务 B),该任务有自己的 activity 堆栈。
与该应用互动后,用户再次返回主屏幕,并选择最初启动任务 A 的应用。
现在,任务 A 来到前台,其堆栈中的所有三个 activity 都完好无损,并且堆栈顶部的 activity 恢复运行。此时,用户还可以通过以下方式切换回任务 B:返回主屏幕并选择启动该任务的应用图标,或者从“最近使用的应用”屏幕中选择该应用的任务。
多个 activity 实例
由于返回堆栈中的 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(API 级别 31)及更高版本中,根启动器 activity 的此行为有所不同。
Activity 可以多次实例化,甚至是从其他任务对其进行实例化。
管理任务
Android 通过将连续启动的所有 activity 放入同一任务(采用“后进先出”堆栈)中来管理任务和返回堆栈。这对于大多数应用来说非常有效,您通常不必担心 activity 如何与任务相关联,也不必担心它们如何存在于返回堆栈中。
不过,您可能希望中断正常行为。例如,您可能希望应用中的某个 activity 在启动时开始新任务,而不是放置在当前任务中。或者,在启动 activity 时,您可能希望调出它的现有实例,而不是在返回堆栈顶部创建新实例。或者,您可能希望在用户离开任务时,从返回堆栈中清除除根 activity 之外的所有 activity。
您可以使用 <activity> 清单元素中的属性以及您传递给 startActivity() 的 intent 中的标志来执行这些操作以及更多其他操作。
以下是可用于管理任务的主要 <activity> 属性:
taskAffinitylaunchModeallowTaskReparentingclearTaskOnLaunchalwaysRetainTaskStatefinishOnTaskLaunch
以下是您可以使用的主要 intent 标志:
以下部分介绍了如何使用这些清单属性和 intent 标志来定义 activity 与任务的关联方式以及 activity 在返回堆栈中的行为。
以下部分还讨论了系统如何在“最近用过”屏幕中表示和管理任务和 activity。通常,您应让系统定义任务和 activity 在“最近使用的应用”屏幕中的表示方式,并且无需修改此行为。如需了解详情,请参阅“最近用过”屏幕。
定义启动模式
通过启动模式,您可以定义 activity 的新实例如何与当前任务相关联。您可以通过以下两种方式定义启动模式,详见以下各部分:
-
在清单文件中声明 activity 时,您可以指定 activity 在启动时如何与任务关联。
-
调用
startActivity()时,您可以在Intent中添加一个标志,用于声明新 activity 如何(或是否)与当前任务相关联。
因此,如果 Activity A 启动 Activity B,Activity B 可以在其清单中定义它与当前任务的关联方式,而 Activity A 可以使用 intent 标志来请求 Activity B 与当前任务的关联方式。
如果两个 activity 都定义了 activity B 如何与任务关联,则系统会优先遵循 activity A 的请求(在 intent 中定义),而不是 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 在顶部)。一个 intent 到达类型 D 的 activity。如果 D 具有默认的
"standard"启动模式,系统会启动该类的新实例,堆栈将变为 A-B-C-D-D。不过,如果 D 的启动模式为"singleTop",则 D 的现有实例会通过onNewIntent()接收 intent,因为它位于堆栈顶部,而堆栈仍为 A-B-C-D。另一方面,如果 intent 针对的是 B 类型的 activity,则系统会向堆栈添加 B 的新实例,即使其启动模式为"singleTop"也是如此。"singleTask"- 系统会在新任务的根位置创建 activity,或将该 activity 放置在具有相同 affinity 的现有任务上。如果已存在 activity 实例,则系统会通过调用该实例的
onNewIntent()方法(而非创建新的 activity 实例),向其传送 intent。同时,该 activity 之上的所有其他 activity 都会被销毁。
"singleInstance"- 其行为与
"singleTask"相同,唯一不同的是系统不会将任何其他 activity 启动到包含该实例的任务中。该 activity 始终是其任务中的唯一 activity。由此 activity 启动的任何 activity 都会在单独的任务中打开。
"singleInstancePerTask"- 此 activity 只能作为任务的根 activity、作为创建该任务的第一个 activity 运行,因此在任务中该 activity 只能有一个实例。与
singleTask启动模式相反,如果设置了FLAG_ACTIVITY_MULTIPLE_TASK或FLAG_ACTIVITY_NEW_DOCUMENT标志,此 activity 可以在不同任务中启动多个实例。
例如,Android 浏览器在 <activity> 中指定 singleTask,因此其 activity 不会放置在应用的任务中。而是会为浏览器启动一个新任务,或者将现有的浏览器后台任务调到前台。
无论 activity 是在新任务中启动,还是在启动它的 activity 所在的任务中启动,返回按钮和手势都会始终将用户带到上一个 activity。不过,如果您启动指定了 singleTask 启动模式的 activity,并且该 activity 的实例存在于后台任务中,则整个任务都会转到前台。此时,返回堆栈包含调到前台的任务中的所有 activity,这些 activity 位于堆栈的顶部。图 4 展示了此类场景。
"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> 元素的 taskAffinity 属性修改 activity 的相似性。
taskAffinity 属性采用字符串值,该值必须不同于 <manifest> 元素中声明的默认软件包名称,因为系统使用该名称来标识应用的默认任务亲和性。
亲和性可在两种情况下发挥作用:
当启动 activity 的 intent 包含
FLAG_ACTIVITY_NEW_TASK标志时。默认情况下,新的 activity 会启动到调用
startActivity()的 activity 的任务中。它会被推送到与调用方相同的返回堆栈。不过,如果传递给
startActivity()的 intent 包含FLAG_ACTIVITY_NEW_TASK标志,系统会寻找其他任务来容纳新 activity。这通常是一项新任务。不过,情况并非总是如此。如果存在与新 activity 具有相同相似性的现有任务,则会将该 activity 启动到该任务中。如果不存在,则会启动一个新任务。如果此标志导致 activity 开始新任务,并且用户使用“主屏幕”按钮或手势离开该任务,则用户必须能够通过某种方式返回到该任务。某些实体(例如通知管理器)始终在外部任务中启动 activity,而不会将其作为自身任务的一部分,因此它们始终会将
FLAG_ACTIVITY_NEW_TASK放入传递给startActivity()的 intent 中。如果外部实体可以使用此标志调用您的 activity,请为用户提供一种独立的返回方式,例如通过任务根 activity 上的
CATEGORY_LAUNCHER过滤器提供启动器图标。如需了解详情,请参阅启动任务。当 activity 的
allowTaskReparenting属性设置为"true"时。在这种情况下,当具有亲和性的任务进入前台时,activity 可以从其启动的任务移至该任务。
例如,假设某个报告所选城市天气状况的 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 设置为任务的入口点:
<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,请参阅“最近使用的应用”屏幕。