任务是用户尝试在您的应用中执行操作时与之互动的一系列 activity。这些 activity 按照每个 activity 的打开顺序排列在一个名为“返回堆栈”的堆栈中。
例如,电子邮件应用可能有一个 activity 用于显示新邮件列表。用户选择一条消息后,系统会打开一个新的 activity 来查看该消息。系统会将这个新 activity 添加到返回堆栈。然后,当用户点按或手势“返回”时,该新的 activity 会完成并从堆栈中弹出。
任务及其返回堆栈的生命周期
大多数任务都从设备主屏幕上启动。当用户轻触应用启动器或主屏幕上的应用图标或快捷方式时,该应用的任务会进入前台。如果应用不存在任务,系统会创建一个新任务,并将该应用的主 activity 作为堆栈中的根 activity 打开。
当前 activity 启动另一个 activity 时,这个新 activity 会被推送到堆栈顶部并获得焦点。前一个 activity 仍保留在堆栈中,但会停止。当某个 activity 停止时,系统会保留其界面的当前状态。当用户执行返回操作时,当前 activity 会从堆栈顶部弹出并销毁。上一个 activity 将恢复,并恢复其界面的上一个状态。
堆栈中的 activity 绝不会重新排列,只会被推送到堆栈中的 activity 和从堆栈中弹出,因为相应 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()
,而不是完成 activity。调用super.onBackPressed()
会在适当时将 activity 及其任务移至后台,并为各应用中的用户提供更一致的导航体验。
后台和前台任务
任务是一个统一的单元,可在用户开始��任务或转到主屏幕时移至后台。在后台时,任务中的所有 activity 都会停止,但任务的返回堆栈保持不变,即当其他任务发生时,任务会失去焦点,如图 2 所示。然后,任务可以返回到前台,以便用户从上次停下的地方继续。
假设当前任务 A 的堆栈中有三个 activity,包括当前 activity 下的两个 activity,请考虑以下任务流:
用户使用主屏幕按钮或手势,然后从应用启动器启动新应用。
出现主屏幕时,任务 A 会进入后台。新应用启动时,系统会使用自己的 activity 堆栈为该应用启动一项任务(任务 B)。
与该应用互动后,用户再次返回主屏幕并选择最初启动任务 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 或更高版本的设备上运行时,根启动器 activity ������行为有所不同。
Activity 可以多次实例化,甚至是从其他任务对其进行实例化。
管理日常事务
Android 通过将连续启动的所有 activity 置于同一任务中的后进先出堆栈中,来管理任务和返回堆栈。这对于大多数应用来说非常棒,并且您通常不必担心 activity 与任务的关联方式或任务如何存在于返回堆栈中。
但是,您可能会决定要中断正常行为。例如,您可能希望应用中的 activity 在其启动时启动新任务,而不是将其放置在当前任务中。或者,当您启动某个 activity 时,您可能希望前置该 activity 的现有实例,而不是在返回堆栈之上创建新实例。或者,您可能希望在用户离开任务时清除返回堆栈中的所有 activity(根 activity 除外)。
您可以使用 <activity>
清单元素中的属性和传递给 startActivity()
的 intent 中的标志来执行这些操作。
以下是可用于管理任务的主要 <activity>
属性:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
以下是您可以使用的主要 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 如何与任务关联,则 intent 中定义的 activity A 的请求将优先于 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。另一方面,如果 intent 到达 B 类型的 activity,则系统会向堆栈中添加 B 的新实例,即使其启动模式为"singleTop"
也是如此。"singleTask"
- 系统会在新任务的根位置创建 activity,或将该 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>
元素中指定 singleTask
启动模式,声明网络浏览器 activity 始终在其自己的任务��打开。这意味着,如果您的应用发出打开 Android 浏览器的 intent,则其 activity 不会与应用置于同一任务中。相反,系统会为浏览器启动新任务;或者,如果浏览器已有一个任务在后台运行,系统会将该任务上移一层以处理新的 intent。
无论 activity 是在新任务中启动的,还是与启动它的 activity 在同一任务中启动,返回按钮和手势始终会将用户转到上一个 activity。不过,如果您启动指定了 singleTask
启动模式的 activity,且后台任务中存在该 activity 的实例,那么整个任务都会转到前台运行。此时,返回堆栈包含移至堆栈顶部的任务中的所有 activity。图 4 显示了此类情况。
如需详细了解如何在清单文件中使用启动模式,请参阅 <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 可以被分配不同的任务相似性。
您可以使用 <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,绝不会作为自己的 activity 的一部分,因此它们始终将
FLAG_ACTIVITY_NEW_TASK
放入传递给startActivity()
的 intent 中。如果可能使用此标志的外部实体可以调用您的 activity,请确保用户可以独立地返回到启动的任务(例如使用启动器图标),其中任务的根 activity 具有
CATEGORY_LAUNCHER
intent 过滤器。如需了解详情,请参阅启动任务部分。当 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_MAIN
和 CATEGORY_LAUNCHER
过滤器时,请仅使用 "singleTask"
和 "singleInstance"
这两种启动模式,这两种启动模式会将 activity 标记为始终启动任务。
例如,想象一下,如果缺少过滤器,会发生什么情况:intent 会启动一个 "singleTask"
activity,再启动一个新任务,而用户花了一些时间来处理该任务。然后,用户使用主屏幕按钮或手势。此时,该任务会转到后台,不再可见。现在,用户无法返回到任务,因为它未显示在应用启动器中。
如果您不希望用户能够返回 activity,请将 <activity>
元素的 finishOnTaskLaunch
设置为 "true"
。如需了解详情,请参阅清除返回堆栈部分。
如需详细了解如何在“最近使用的应用”屏幕中表示和管理任务及 activity,请参阅“最近使用的应用”屏幕。