任务和返回堆栈

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

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

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

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

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

堆栈中的 activity 永远不会重新排列,只有在被当前 activity 启动并由用户通过返回按钮或手势关闭时,才会被推送到堆栈中并从堆栈中弹出。因此,返回堆栈采用“后进先出”对象结构。图 1 显示了一个时间轴,其中显示了 activity 被推送到返回堆栈并从中弹出。

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

当用户继续点按或手势“返回”时,堆栈中的每个 activity 都会弹出以显示前一个 activity,直到用户返回到主屏幕或任务开始时正在运行的任何 activity。从堆栈中移除所有 activity 后,任务将不再存在。

根启动器 activity 的返回点按行为

根启动器 activity 是使用 ACTION_MAINCATEGORY_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 进入后台。当新应用启动时,系统会使用自己的 activity 堆栈为该应用启动一项任务(任务 B)。

  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 或更高版本的设备上运行时,根启动器 activity 的此行为会有所不同

  • Activity 可以多次实例化,甚至是从其他任务对其进行实例化。

管理日常事务

Android 通过将连续启动的所有 activity 放在同一任务中的“后进先出”堆栈中,来管理任务和返回堆栈。这对于大多数应用都非常有效,您通常不必担心 activity 与任务的关联方式,或 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 的新实例,并向其传送 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。另一方面,如果 intent 到达 B 类型的 activity,则会向堆栈中添加 B 的新实例,即使其启动模式为 "singleTop"

  3. "singleTask"
    系统会在新任务的根位置创建 activity,或将该 activity 放置在具有相同相似性的现有任务上。如果已存在 activity 实例,则系统会通过调用现有实例的 onNewIntent() 方法,而非创建新实例,向其传送 intent。同时,它上面的所有其他 activity 都会被销毁。
  4. "singleInstance"
    行为与 "singleTask" 相同,只是系统不会将任何其他 activity 启动到包含该实例的任务中。该 activity 始终是其任务中的唯一 activity。由此 activity 启动的任何 activity 都会在单独的任务中打开。
  5. "singleInstancePerTask"
    该 activity 只能作为任务的根 activity(即创建该任务的第一个 activity)运行,因此在任务中该 activity 只能有一个实例。与 singleTask 启动模式相比,如果设置了 FLAG_ACTIVITY_MULTIPLE_TASKFLAG_ACTIVITY_NEW_DOCUMENT 标志,则可以在不同任务的多个实例中启动此 activity。

再例如,Android 浏览器应用通过在 <activity> 元素中指定 singleTask 启动模式,声明网络浏览器 activity 始终在自己的任务中打开。这意味着,如果您的应用发出打开 Android 浏览器的 intent,其 activity 不会与应用置于同一任务中。相反,系统会为浏览器启动一个新任务,或者如果浏览器已有一项在后台运行的任务,则会将该任务上移一层以处理新的 intent。

无论 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 的新实例。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 同属于一个任务。不过,当旅行应用的任务进入前台时,系统会将天气预报 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_MAINCATEGORY_LAUNCHER 过滤器时,请仅使用 "singleTask""singleInstance" 这两种启动模式,将 activity 标记为始终启动任务。

例如,想象一下,如果缺少过滤器,可能会发生什么情况:intent 会启动 "singleTask" activity,从而启动一个新任务,而用户在该任务中花费一些时间。然后,用户使用主屏幕按钮或手势。此时,该任务会转到后台,不再可见。现在,用户无法返回到该任务,因为它未显示在应用启动器中。

如果您不希望用户能够返回 activity,请将 <activity> 元素的 finishOnTaskLaunch 设置为 "true"。如需了解详情,请参阅清除返回堆栈部分。

如需详细了解如何在“最近使用的应用”屏幕中表示和管理任务与 activity,请参阅“最近使用的应用”屏幕

更多资源