“任务”是用户在尝试执行应用中的某些操作时与之交互的 Activity 的集合。这些 Activity 以堆栈的形式排列,称为“返回堆栈”,其顺序是每个 Activity 打开的顺序。
例如,电子邮件应用可能有一个 Activity 用于显示新消息列表。当用户选择一条消息时,会打开一个新的 Activity 来查看该消息。这个新的 Activity 会被添加到返回堆栈中。然后,当用户轻触或手势返回时,那个新的 Activity 会结束并从堆栈中弹出。
任务及其返回堆栈的生命周期
设备主屏幕是大多数任务的起点。当用户在应用启动器或主屏幕上轻触应用图标或快捷方式时,该应用的任务会进入前台。如果该应用没有现有任务,则会创建一个新任务,并且该应用的主 Activity 作为堆栈中的根 Activity 打开。
当当前 Activity 启动另一个 Activity 时,新 Activity 会被推到堆栈顶部并获得焦点。上一个 Activity 留在堆栈中,但处于停止状态。当 Activity 停止时,系统会保留其用户界面的当前状态。当用户执行返回操作时,当前 Activity 会从堆栈顶部弹出并销毁。上一个 Activity 会恢复,并且其 UI 的先前状态会恢复。
堆栈中的 Activity 永远不会重新排列,只会随着它们由当前 Activity 启动并通过用户的返回按钮或手势关闭而被推入和弹出堆栈。因此,返回堆栈的操作类似于“后进先出”对象结构。图 1 显示了一个时间线,其中 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()而不是结束。调用super.onBackPressed()会在适当的时候将 Activity 及其任务移到后台,并为用户提供跨应用更一致的导航体验。
后台和前台任务
任务是一个内聚单元,当用户开始新任务或进入主屏幕时,它可以移动到“后台”。在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈保持不变——任务失去焦点,而另一个任务发生,如图 2 所示。然后,任务可以返回到“前台”,以便用户可以从上次离开的地方继续。
考虑当前任务 A 的以下任务流,其堆栈中有三个 Activity,其中两个在当前 Activity 之下:
用户使用主屏幕按钮或手势,然后从应用启动器启动一个新应用。
主屏幕出现时,任务 A 进入后台。当新应用启动时,系统会为该应用启动一个任务(任务 B),其中包含其自己的 Activity 堆栈。
与该应用交互后,用户再次返回主屏幕并选择最初启动任务 A 的应用。
现在,任务 A 进入前台——其堆栈中的所有三个 Activity 都完好无损,堆栈顶部的 Activity 会恢复。此时,用户还可以通过进入主屏幕并选择启动该任务的应用图标,或通过从最近使用屏幕中选择该应用的任务,来切换回任务 B。
多个 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> 属性:
taskAffinitylaunchModeallowTaskReparentingclearTaskOnLaunchalwaysRetainTaskStatefinishOnTaskLaunch
以下是可用于此目的的主要 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 如何与任务关联,则 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 在顶部)。一个针对类型 D 的 Activity 的 Intent 到达。如果 D 具有默认的
"standard"启动模式,则会启动该类的新实例,堆栈变为 A-B-C-D-D。但是,如果 D 的启动模式是"singleTop",则 D 的现有实例通过onNewIntent()接收 Intent,因为它在堆栈顶部,并且堆栈保持为 A-B-C-D。另一方面,如果针对类型 B 的 Activity 的 Intent 到达,即使其启动模式为"singleTop",也会向堆栈添加一个新的 B 实例。"singleTask"- 系统会在新任务的根目录创建 Activity,或者在具有相同 affinity 的现有任务上找到 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 始终通过在 <activity> 元素中指定 singleTask 启动模式来在其自己的任务中打开。这意味着如果您的应用发出 Intent 来打开 Android 浏览器,其 Activity “不会”放置在与您的应用相同的任务中。相反,要么为浏览器启动一个新任务,要么如果浏览器已经在后台运行一个任务,则该任务会带到前台来处理新 Intent。
无论 Activity 是在新任务中启动,还是在启动它的 Activity 的相同任务中启动,返回按钮和手势始终会将用户带到上一个 Activity。但是,如果您启动了一个指定 singleTask 启动模式的 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 的新实例。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,从不作为其自身的一部分,因此它们始终将
FLAG_ACTIVITY_NEW_TASK放入传递给startActivity()的 Intent 中。如果可能使用此标志的外部实体可以调用您的 Activity,请注意用户必须有独立的方式返回到已启动的任务,例如使用启动器图标,其中任务的根 Activity 具有
CATEGORY_LAUNCHERIntent 过滤器。有关更多信息,请参阅关于启动任务的部分。当 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_MAIN 和 CATEGORY_LAUNCHER 过滤器时,才使用标记 Activity 始终启动任务的两种启动模式:"singleTask" 和 "singleInstance"。
例如,想象一下如果缺少过滤器可能会发生什么:一个 Intent 启动一个 "singleTask" Activity,启动一个新任务,用户在该任务中工作了一段时间。然后用户使用主页按钮或手势。任务现在被发送到后台且不可见。现在用户无法返回该任务,因为它未在应用启动器中表示。
对于您不希望用户能够返回到 Activity 的情况,请将 <activity> 元素的 finishOnTaskLaunch 设置为 "true"。有关更多信息,请参阅有关清除返回堆栈的部分。
有关任务和 Activity 在最近使用屏幕中的表示和管理的更多信息,请参见最近使用屏幕。