任务和返回堆栈

任务是用户在尝试通过应用执行某项操作时与之互动的一系列 activity。这些 activity 排列在一个堆栈中,称为“返回堆栈”,按照每个 activity 打开的顺序排列。

例如,电子邮件应用可能有一个用于显示新邮件列表的 activity。当用户选择某条消息时,系统会打开一个新的 activity 来查看该消息。此新 activity 会添加到返回堆栈中。然后,当用户点按或通过手势执行“返回”操作时,该新 activity 会完成并从堆栈中弹出。

任务及其返回堆栈的生命周期

设备主屏幕是大多数任务的起点。当用户点按应用启动器或主屏幕中的应用或快捷方式图标时,该应用的任务就会转到前台。如果应用没有任务,系统会创建一个新任务,并将该应用的主 activity 作为堆栈中的根 activity 打开。

当当前 activity 启动另一个 activity 时,新 activity 会被推送到堆栈顶部,并获得焦点。之前的 activity 仍保留在堆栈中,但处于停止状态。当 activity 停止时,系统会保留其界面的当前状态。当用户执行返回操作时,当前 activity 会从堆栈顶部弹出并销毁。之前的 activity 会恢复,其界面的先前状态也会恢复。

堆栈中的 activity 绝不会重新排列,只会在启动时推入堆栈,在用户使用返回按钮或手势关闭时弹出堆栈。因此,返回堆栈采用后进先出结构,如图 1 所示。

图 1. 有关任务中的每个新 Activity 如何添加到返回堆栈的图示。当用户点按或通过手势执行“返回”操作时,当前 activity 会被销毁,之前的 activity 会恢复。

当用户继续点按或通过手势执行“返回”操作时,堆栈中的每个 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 及其任务移至后台,并为用户提供更一致的应用间导航体验。

后台和前台任务

图 2. 两项任务:任务 B 在前台接收用户互动,而任务 A 在后台等待恢复。

任务是一个内聚单元,当用户开始新任务或返回主屏幕时,任务可以移至后台。在后台运行时,任务中的所有 activity 都会停止,但任务的返回堆栈仍会保持不变,也就是说,当另一个任务发生时,该任务将失去焦点,如图 2 所示。然后,任务可以返回到前台,以便用户可以从他们离开的位置继续。

请考虑以下任务流,其中当前任务 A 的堆栈中有三个 activity,包括当前 activity 下的两个 activity:

  1. 用户使用主屏幕按钮或手势,然后从应用启动器启动新应用。

    当主屏幕出现时,任务 A 会进入后台。当新应用启动时,系统会为该应用启动一个任务(任务 B),该任务有自己的 activity 堆栈。

  2. 与该应用互动后,用户再次返回主屏幕,并选择最初启动任务 A 的应用。

    现在,任务 A 来到前台,其堆栈中的所有三个 activity 都完好无损,并且堆栈顶部的 activity 恢复运行。此时,用户还可以通过以下方式切换回任务 B:返回主屏幕并选择启动该任务的应用图标,或者从“最近使用的应用”屏幕中选择该应用的任务。

多个 activity 实例

图 3. 单个 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> 属性:

以下是您可以使用的主要 intent 标志:

以下部分介绍了如何使用这些清单属性和 intent 标志来定义 activity 与任务的关联方式以及 activity 在返回堆栈中的行为。

以下部分还讨论了系统如何在“最近用过”屏幕中表示和管理任务和 activity。通常,您应让系统定义任务和 activity 在“最近使用的应用”屏幕中的表示方式,并且无需修改此行为。如需了解详情,请参阅“最近用过”屏幕。

定义启动模式

通过启动模式,您可以定义 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 属性分配五种启动模式:

  1. "standard"
    默认模式。系统会在启动该 activity 的任务中创建该 activity 的新实例,并向其传送 intent。此 activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例。
  2. "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" 也是如此。

  3. "singleTask"
    系统会在新任务的根位置创建 activity,或将该 activity 放置在具有相同 affinity 的现有任务上。如果已存在 activity 实例,则系统会通过调用该实例的 onNewIntent() 方法(而非创建新的 activity 实例),向其传送 intent。同时,该 activity 之上的所有其他 activity 都会被销毁。
  4. "singleInstance"
    其行为与 "singleTask" 相同,唯一不同的是系统不会将任何其他 activity 启动到包含该实例的任务中。该 activity 始终是其任务中的唯一 activity。由此 activity 启动的任何 activity 都会在单独的任务中打开。
  5. "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 展示了此类场景。

图 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> 元素中声明的默认软件包名称,因为系统使用该名称来标识应用的默认任务亲和性。

亲和性可在两种情况下发挥作用:

  1. 当启动 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 过滤器提供启动器图标。如需了解详情,请参阅启动任务。

  2. 当 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,请参阅“最近使用的应用”屏幕。

更多资源