任务和返回堆栈

“任务”是用户在尝试执行应用中的某些操作时与之交互的 Activity 的集合。这些 Activity 以堆栈的形式排列,称为“返回堆栈”,其顺序是每个 Activity 打开的顺序。

例如,电子邮件应用可能有一个 Activity 用于显示新消息列表。当用户选择一条消息时,会打开一个新的 Activity 来查看该消息。这个新的 Activity 会被添加到返回堆栈中。然后,当用户轻触或手势返回时,那个新的 Activity 会结束并从堆栈中弹出。

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

设备主屏幕是大多数任务的起点。当用户在应用启动器或主屏幕上轻触应用图标或快捷方式时,该应用的任务会进入前台。如果该应用没有现有任务,则会创建一个新任务,并且该应用的主 Activity 作为堆栈中的根 Activity 打开。

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

堆栈中的 Activity 永远不会重新排列,只会随着它们由当前 Activity 启动并通过用户的返回按钮或手势关闭而被推入和弹出堆栈。因此,返回堆栈的操作类似于“后进先出”对象结构。图 1 显示了一个时间线,其中 Activity 被推入和弹出返回堆栈。

图 1. 任务中每个新 Activity 如何将一个项添加到返回堆栈的表示。当用户轻触或手势返回时,当前 Activity 会被销毁,上一个 Activity 会恢复。

随着用户继续轻触或手势返回,堆栈中的每个 Activity 都会被弹出以显示上一个 Activity,直到用户返回到主屏幕或任务开始时运行的任何 Activity。当所有 Activity 都从堆栈中移除时,任务就不再存在了。

根启动器 Activity 的返回轻触行为

根启动器 Activity 是声明了同时包含 ACTION_MAINCATEGORY_LAUNCHERintent 过滤器的 Activity。这些 Activity 很独特,因为它们充当从应用启动器进入应用的入口点,并用于启动任务

当用户从根启动器 Activity 轻触或手势返回时,系统会根据设备运行的 Android 版本以不同方式处理此事件。

Android 11 及更低版本上的系统行为
系统会结束 Activity。
Android 12 及更高版本上的系统行为

系统会将 Activity 及其任务移到后台,而不是结束 Activity。此行为与使用主屏幕按钮或手势退出应用时的默认系统行为一致。

在大多数情况下,此行为意味着用户可以更快地从暖状态恢复您的应用,而不必从冷状态完全重新启动应用。

如果您需要提供自定义返回导航,我们建议使用 AndroidX Activity API,而不是覆盖 onBackPressed()。如果没有任何组件拦截系统返回轻触,AndroidX Activity API 会自动推迟到适当的系统行为。

但是,如果您的应用覆盖了 onBackPressed() 来处理返回导航并结束 Activity,请更新您的实现,以调用 super.onBackPressed() 而不是结束。调用 super.onBackPressed() 会在适当的时候将 Activity 及其任务移到后台,并为用户提供跨应用更一致的导航体验。

后台和前台任务

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

任务是一个内聚单元,当用户开始新任务或进入主屏幕时,它可以移动到“后台”。在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈保持不变——任务失去焦点,而另一个任务发生,如图 2 所示。然后,任务可以返回到“前台”,以便用户可以从上次离开的地方继续。

考虑当前任务 A 的以下任务流,其堆栈中有三个 Activity,其中两个在当前 Activity 之下:

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

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

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

    现在,任务 A 进入前台——其堆栈中的所有三个 Activity 都完好无损,堆栈顶部的 Activity 会恢复。此时,用户还可以通过进入主屏幕并选择启动该任务的应用图标,或通过从最近使用屏幕中选择该应用的任务,来切换回任务 B。

多个 Activity 实例

图 3. 一个 Activity 可以实例化多次。

由于返回堆栈中的 Activity 永远不会重新排列,如果您的应用允许用户从多个 Activity 启动特定 Activity,则会创建该 Activity 的新实例并将其推到堆栈上,而不是将 Activity 的任何先前实例带到顶部。因此,您的应用中的一个 Activity 可能会被多次实例化,即使来自不同的任务,如图 3 所示。

如果用户使用返回按钮或手势向后导航,Activity 的实例会按其打开的顺序显示,每个实例都有自己的 UI 状态。但是,如果您不想多次实例化 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> 清单元素中的属性和传递给 startActivity() 的 Intent 中的标志来完成这些操作及更多操作。

以下是可用于管理任务的主要 <activity> 属性:

以下是可用于此目的的主要 Intent 标志:

以下部分讨论了如何使用这些清单属性和 Intent 标志来定义 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 在顶部)。一个针对类型 D 的 Activity 的 Intent 到达。如果 D 具有默认的 "standard" 启动模式,则会启动该类的新实例,堆栈变为 A-B-C-D-D。但是,如果 D 的启动模式是 "singleTop",则 D 的现有实例通过 onNewIntent() 接收 Intent,因为它在堆栈顶部,并且堆栈保持为 A-B-C-D。另一方面,如果针对类型 B 的 Activity 的 Intent 到达,即使其启动模式为 "singleTop",也会向堆栈添加一个新的 B 实例。

  3. "singleTask"
    系统会在新任务的根目录创建 Activity,或者在具有相同 affinity 的现有任务上找到 Activity。如果 Activity 的实例已经存在,系统会通过调用其 onNewIntent() 方法将 Intent 路由到现有实例,而不是创建新实例。同时,它上面的所有其他 Activity 都会被销毁。
  4. "singleInstance".
    行为与 "singleTask" 相同,不同之处在于系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务的唯一成员。由该 Activity 启动的任何 Activity 都会在单独的任务中打开。
  5. "singleInstancePerTask".
    Activity 只能作为任务的根 Activity 运行,即创建任务的第一个 Activity,因此在任务中只能有一个此 Activity 的实例。与 singleTask 启动模式不同,如果设置了 FLAG_ACTIVITY_MULTIPLE_TASKFLAG_ACTIVITY_NEW_DOCUMENT 标志,则此 Activity 可以在不同任务中以多个实例启动。

再举一个例子,Android 浏览器应用声明网页浏览器 Activity 始终通过在 <activity> 元素中指定 singleTask 启动模式来在其自己的任务中打开。这意味着如果您的应用发出 Intent 来打开 Android 浏览器,其 Activity “不会”放置在与您的应用相同的任务中。相反,要么为浏览器启动一个新任务,要么如果浏览器已经在后台运行一个任务,则该任务会带到前台来处理新 Intent。

无论 Activity 是在新任务中启动,还是在启动它的 Activity 的相同任务中启动,返回按钮和手势始终会将用户带到上一个 Activity。但是,如果您启动了一个指定 singleTask 启动模式的 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 的新实例。Intent 会通过 onNewIntent() 传递给恢复的 Activity 实例(现在位于顶部)。

launchMode 属性没有产生此行为的值。

FLAG_ACTIVITY_CLEAR_TOP 最常与 FLAG_ACTIVITY_NEW_TASK 结合使用。当一起使用时,这些标志会在另一个任务中找到一个现有 Activity,并将其置于可以响应 Intent 的位置。

处理亲和性

“亲和性”表示 Activity “倾向于”属于哪个任务。默认情况下,来自同一应用的所有 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 Intent 过滤器。有关更多信息,请参阅关于启动任务的部分。

  2. 当 Activity 的 allowTaskReparenting 属性设置为 "true" 时。

    在这种情况下,Activity 可以在其启动的任务中移动到其具有亲和性的任务,当该任务进入前台时。

    例如,假设一个报告选定城市天气状况的 Activity 被定义为旅行应用的一部分。它与同一应用中的其他 Activity 具有相同的亲和性,即默认应用亲和性,并且可以使用此属性进行重新父级。

    当您的 Activity 启动天气报告 Activity 时,它最初属于与您的 Activity 相同的任务。但是,当旅行应用的 task 进入前台时,天气报告 Activity 会被重新分配到该任务并在其中显示。

清除返回堆栈

如果用户长时间离开某个任务,系统会清除该任务中的所有 Activity,只保留根 Activity。当用户返回该任务时,只会恢复根 Activity。系统之所以这样操作,是基于这样一个假设:经过长时间后,用户已经放弃了之前正在做的事情,并返回任务以开始新的事情。

有一些 Activity 属性可以用来修改此行为:

alwaysRetainTaskState
当此属性在任务的根 Activity 中设置为 "true" 时,上述默认行为不会发生。即使经过很长一段时间,任务也会保留其堆栈中的所有 Activity。
clearTaskOnLaunch

当此属性在任务的根 Activity 中设置为 "true" 时,每当用户离开任务并返回时,任务都会被清除到只剩下根 Activity。换句话说,它与 alwaysRetainTaskState 相反。即使用户只离开了任务片刻,也总是会以其初始状态返回任务。

finishOnTaskLaunch

此属性类似于 clearTaskOnLaunch,但它作用于单个 Activity,而不是整个任务。它还可能导致除根 Activity 之外的任何 Activity 结束。当它设置为 "true" 时,Activity 只在当前会话中作为任务的一部分。如果用户离开然后返回任务,它将不再存在。

启动任务

您可以通过为其提供一个 Intent 过滤器来将 Activity 设置为任务的入口点,其中指定操作为 "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_MAINCATEGORY_LAUNCHER 过滤器时,才使用标记 Activity 始终启动任务的两种启动模式:"singleTask""singleInstance"

例如,想象一下如果缺少过滤器可能会发生什么:一个 Intent 启动一个 "singleTask" Activity,启动一个新任务,用户在该任务中工作了一段时间。然后用户使用主页按钮或手势。任务现在被发送到后台且不可见。现在用户无法返回该任务,因为它未在应用启动器中表示。

对于您不希望用户能够返回到 Activity 的情况,请将 <activity> 元素的 finishOnTaskLaunch 设置为 "true"。有关更多信息,请参阅有关清除返回堆栈的部分。

有关任务和 Activity 在最近使用屏幕中的表示和管理的更多信息,请参见最近使用屏幕

更多资源