Task和BackStack
一个Android应用通常包含多个Activity,每个Activity均应围绕用户可以进行的特定操作设计,并且能够启动其他Activity。
任务(Task)是指在执行特定作业时与用户交互的一系列Activity,这些Activity按照各自的打开顺序排列在堆栈中(即返回栈BackStack)。
手机的主页是大多数Task的起点,当用户点击应用图标时,该应用的Task将出现在前台,如果应用不存在Task,则会创建一个新Task,并且该应用中的主Activity会作为堆栈中的根Activity打开。
当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。 堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。 因此,返回栈以“后进先出”对象结构运行。
image.png
上图展示了back stack的一个工作过程。当所有Activity都从栈中弹出后,Task也不复存在。
任务是一个有机整体,当用户开始新任务或通过“主页”按钮转到主屏幕时,可以移动到“后台”。 尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已。
注:后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失。请参阅下面有关 Activity 状态的部分。
Activity的启动模式:
启动模式允许程序定义 Activity 的新实例如何与当前任务关联。 我们可以通过两种方法定义不同的启动模式:
(1)在manifest文件中定义
在清单文件中声明 Activity 时,您可以使用 <activity> 元素的 launchMode 属性指定 Activity 应该如何与任务关联。launchMode 属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给 launchMode 属性的启动模式共有四种:
-
standard(默认模式):
系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。 -
singleTop(栈顶复用模式):
如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 onNewIntent() 方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。 -
singleTask:
系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的onNewIntent() 方法向其传送 Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。
注:尽管 Activity 在新任务中启动,但是用户按“返回”按钮仍会返回到前一个 Activity。
- singleInstance:
与 "singleTask" 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。
我们再来看另一示例,Android 浏览器应用声明网络浏览器 Activity 应始终在其自己的任务中打开(通过在 <activity> 元素中指定 singleTask 启动模式)。这意味着,如果应用发出打开 Android 浏览器的 Intent,则浏览器的 Activity 与应用位于不同的任务中。同时,系统会为浏览器启动新任务,或者如果浏览器已有任务正在后台运行,则会将该任务上移一层以处理新 Intent。
无论Activity是在新任务中启动,还是在与启动它的Activity相同任务中启动,用户点“返回”都会转到前一个Activity。但是,如果Activity的模式是singleTask,则当后台任务存在该Activity实例时,整个任务都会转移到前台,此时返回栈包含上移到栈顶的所有Activity,如下图。
image.png
如需了解有关在清单文件中使用启动模式的详细信息,请参阅 <activity> 元素文档,其中更详细地讨论了 launchMode 属性和可接受的值。
注:使用 launchMode 属性为 Activity 指定的行为可由 Intent 附带的 Activity 启动标志替代,下文将对此进行讨论。
(2)使用Intent标志
在启动Activity时,可以通过传递给startActivity的Intent中加入相关的flag,修改Activity与其任务的默认关联方式,比如下面这些flag:
启动 Activity 时,您可以通过在传递给 startActivity() 的 Intent 中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括:
在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 onNewIntent() 中收到新 Intent。这会产生与 "singleTask"launchMode 值相同的行为。
如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则 现有实例会接收对 onNewIntent() 的调用,而不是创建 Activity 的新实例。这会产生与 "singleTop"launchMode 值相同的行为。
如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 onNewIntent() 将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。
产生这种行为的 launchMode 属性没有值。
FLAG_ACTIVITY_CLEAR_TOP 通常与 FLAG_ACTIVITY_NEW_TASK 结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent 的位置。
注:如果指定 Activity 的启动模式为 "standard",则该 Activity 也会从堆栈中移除,并在其位置启动一个新实例,以便处理传入的 Intent。 这是因为当启动模式为 "standard" 时,将始终为新 Intent 创建新实例。
TaskAffinity:
TaskAffinity指示Activity优先属于哪个任务,默认情况下,同一应用的所有Activity具有相同的TaskAffinity,那就是包名,不过我们可以修改TaskAffinity,可以让不同应用的Activity拥有相同的TaskAffinity也可以让同一个应用有不同的TaskAffinity。通过 <activity> 元素的 taskAffinity 属性(是一个字符串值)修改任何给定 Activity 的关联。
在两种情况下,关联会起作用:
-
启动 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 重新分配给该任务并显示在其中。
清理Back Stack:
如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
我们可以使用下列几个 Activity 属性修改此行为:
-
alwaysRetainTaskState
如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。 -
clearTaskOnLaunch
如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 换而言之,它与alwaysRetainTaskState 正好相反。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。 -
finishOnTaskLaunch
此属性类似于 clearTaskOnLaunch,但它对单个 Activity 起作用,而非整个任务。 此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 设置为 "true" 时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。