Android Activity——任务和返回堆栈

三味码屋 2022年07月23日 458次浏览

Activity、任务、返回堆栈

  任务是一系列 Activity 的集合,这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。
  应用启动之后,该应用的任务就会转到前台运行,如果系统中不存在该应用对应的任务,则会创建一个新的任务,并且该应用的“主”Activity 将会作为堆栈的根 Activity 打开。
  在当前 Activity 启动另一个 Activity 时,新的 Activity 将被推入到堆栈顶部并获得焦点,上一个 Activity 仍保留在堆栈中,但会停止。当 Activity 停止时,系统会保留其界面的当前状态。当返回时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。
  堆栈中的 Activity 永远不会重新排列,只会被送入和退出,Activity 子启动时被送入堆栈,在离开时从堆栈中退出。返回堆栈按照“后进先出”的对象结构运作,如图所示:
Activity返回堆栈运作机制
  移除堆栈中的所有 Activity 后,该任务将不复存在。
  任务是一个整体单元,当开始一个新任务或通过主屏幕按钮进入主屏幕时,当前任务将移至“后台”,在后台时,任务中的所有 Activity 都会停止,但任务的返回堆栈会保持不变。
  Android支持多任务,任务处于前台时,获得焦点,任务处于后台时,失去焦点,任务的前后台状态是可以切换的。

注意:虽然任务可以在后台运行,但如果后台任务过多占用了大量内存,系统可能会为了恢复内存而销毁后台 Activity,导致 Activity 状态丢失。

  由于返回堆栈中的 Activity 不会被重新排列,所以如果应用允许从多个地方启动特定的 Activity,系统默认会创建该 Activity 的新实例并将其推入到堆栈中(而不是将该 Activity 的某个先前的实例移至堆栈顶部)。这样一来,应用中的一个 Activity 就可能被多次实例化(甚至是在不同任务中进行实例化),如图所示:
单个 Activity 会被多次实例化
  Activity 和任务的默认行为总结如下:

  • 当 Activity A 启动 Activity B 时,Activity A 会停止,但系统会保留其状态(例如滚动位置和输入到表单中的文本)。如果在 Activity B 中按返回按钮,系统会恢复 Activity A 及其状态。
  • 当离开任务时,当前 Activity 会停止,其任务会转到后台。系统会保留任务中每个 Activity 的状态。当恢复任务时,任务会进入前台并恢复堆栈顶部的 Activity。
  • 如果用户按返回按钮,当前 Activity 将从堆栈中退出并销毁。堆栈中的上一个 Activity 将恢复。Activity 被销毁后,系统不会保留该 Activity 的状态。
  • Activity 可以多次实例化,甚至是从其他任务对其进行实例化。

管理任务

可以借助清单文件 <activity> 节点的属性,或者给 startActivity()intent 添加标记来实现任务管理。
和任务相关的主要的<activity> 属性包括:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

和任务相关的主要的主要 intent 标记包括:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

Activity 启动模式

可以通过启动模式定义 Activity 的新实例如何与当前任务关联。
可以通过两种方式定义不同的启动模式:

  1. 使用清单文件
    在清单文件中声明 Activity 时,可以指 Activity 在启动时如何与任务关联。
  2. 使用 Intent 标记
    调用 startActivity() 时,可以在 Intent 中添加标记,用于声明新 Activity 如何(或是否)与当前任务相关联。
使用清单文件

在清单文件中声明 Activity 时,可以使用 <activity> 元素的 launchMode 属性指定 Activity 应该如何与任务关联。可以为 launchMode 属性指定 4 种不同的启动模式:

  • 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,则会在堆栈中添加 B 的新实例,即使其启动模式为 "singleTop" 也是如此。

注意:创建 Activity 的新实例后,用户可以按返回按钮返回到上一个 Activity。但是,当由 Activity 的现有实例处理新 intent 时,用户将无法通过按返回按钮返回到 onNewIntent() 收到新 intent 之前的 Activity 状态。

  • singleTask
    系统会尝试创建新任务并实例化新任务的根 Activity,但是并不一定会创建新任务
    例如:
    如果另外的任务中已存在该 Activity 的实例,则系统会通过调用其 onNewIntent() 方法将 intent 转送到该现有实例,而不是创建新实例。Activity 一次只能有一个实例存在。
    如果当前已存在和该 ActivitytaskAffinity 相同的任务,则不会创建新的任务,而是将该 Activity 直接添加到对应的任务中。

注意:虽然 Activity 在新任务中启动,但用户按返回按钮仍会返回到上一个 Activity。

  • singleInstance
    singleTask 相似,但是并不相同。
    如果当前已存在包含该 Activity 实例的任务,则不会创建新的任务,而是直接把包含该 Activity 实例的任务换回前台,并调用该 Activity 实例的 onNewIntent() 方法将 intent 传递给现有实例。
    如果当前不存在包含该 Activity 实例的任务,则一定会创建新的任务,并将该 Activity 实例作为唯一成员添加到任务中,注意,这个任务不管是现在还是在未来,都只会包含这一个 Activity 实例,不会再添加其它任何 Activity 实例,由该 Activity 启动的任何其它 Activity 也都会在其他的任务中打开。
使用 Intent 标记

可以在传送给 startActivity()intent 中添加相应的标记来设置 Activity 与其任务的关联关系。可以使用以下标记来修改默认行为:

  • FLAG_ACTIVITY_NEW_TASK
    singleTask launchMode 值产生的行为相同。

  • FLAG_ACTIVITY_SINGLE_TOP
    singleTop launchMode 值产生的行为相同。

  • FLAG_ACTIVITY_CLEAR_TOP
    如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其它 Activity,并通过 onNewIntent()intent 传送给 Activity 实例(现在位于堆栈顶部)。
    launchMode 属性没有可产生此行为的值。
    FLAG_ACTIVITY_CLEAR_TOP 最常与 FLAG_ACTIVITY_NEW_TASK 结合使用。将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将其置于能够响应 intent 的位置。

注意:如果指定 Activity 的启动模式为 standard,系统也会将其从堆栈中移除,并在它的位置启动一个新实例来处理传入的 intent。这是因为当启动模式为 standard 时,始终会为新 intent 创建新的实例。

处理亲和性

“亲和性”表示 Activity 倾向于属于哪个任务。
默认情况下,同一应用中的所有 Activity 彼此具有亲和性。因此,在默认情况下,同一应用中的所有 Activity 都倾向于位于同一任务。
不过,可以修改 Activity 的默认亲和性。在不同应用中定义的 Activity 可以具有相同的亲和性,或者在同一应用中定义的 Activity 也可以被指定不同的任务亲和性。
可以使用 <activity> 元素的 taskAffinity 属性修改 Activity 的亲和性。
taskAffinity 属性采用字符串值,该值必须不同于 <manifest> 元素中声明的默认软件包名称,因为系统使用该名称来标识应用的默认任务亲和性
亲和性可在两种情况下发挥作用:

  • 当启动 Activityintent 包含 FLAG_ACTIVITY_NEW_TASK 标记时。
    默认情况下,新 Activity 会启动到调用 startActivity()Activity 的任务中。它会被推送到调用方 Activity 所在的返回堆栈中。但是,如果传递给 startActivity()intent 包含 FLAG_ACTIVITY_NEW_TASK 标记,则系统会寻找其他任务来容纳新 Activity通常会是一个新任务,但也可能不是。如果已存在与新 Activity 具有相同亲和性的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务
    如果此标记导致 Activity 启动一个新任务,而用户按下主屏幕按钮离开该任务,则必须为用户提供某种方式来返回到该任务,例如使用启动器图标(任务的根 Activity 具有一个 CATEGORY_LAUNCHER intent 过滤器)。

  • ActivityallowTaskReparenting 属性设为 "true" 时。
    在这种情况下,一旦和 Activity 有亲和性的任务进入前台运行,Activity 就可从其启动的任务转移到该任务。
    例如,假设一款旅行应用中定义了一个报告特定城市天气状况的 Activity,该 Activity 与同一应用中的其他 Activity 具有相同的亲和性(默认应用亲和性),并通过设置 allowTaskReparenting 为 "true" 支持重新归属。当某个 Activity 启动该天气预报 Activity 时,该天气预报 Activity 最初会和启动它的 Activity 同属于一个任务,不过,当旅行应用的任务进入前台运行时,该天气预报 Activity 就会被重新分配给该任务并显示在其中。

提示:如果一个 APK 文件中包含了就用户角度而言的多个“应用”,可能需要使用 taskAffinity 属性为每个“应用”所关联的 Activity 指定不同的亲和性。

清除返回堆栈

如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。
可以使用一些 Activity 属性来修改此行为:

  • alwaysRetainTaskState
    如果在任务的根 Activity 中将该属性设为 "true",则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity
  • clearTaskOnLaunch
    如果在任务的根 Activity 中将该属性设为 "true",那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState 正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。
  • finishOnTaskLaunch
    该属性与 clearTaskOnLaunch 类似,但它只作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根Activity。如果将该属性设为 "true",则 Activity 仅在当前会话中归属于任务,如果用户离开任务再返回,则该任务将不再存在。

参考

任务和返回堆栈