1.2 Activity 启动模式和任务栈 - TomeOkin/Learning-Notes GitHub Wiki

Activity 启动模式和任务栈

任务栈(tasks-and-back-stack

创建任务栈

在两种情况下,有可能会创建一个新的任务栈:
(1)以 FLAG_ACTIVITY_NEW_TASK 方式启动一个 activity;
(2)在 AndroidManifest.xml 中配置了 android:taskAffinity

如果我们使用 startActivity 启动一个 Activity,没有添加任何 flag 时,新启动的 Activity 会与调用 startActivity 的 Activity 位于同一个任务栈中;在默认情况下,应用内的所有 Activity 都会被存放在以包名命名的任务栈中。

当我们以 FLAG_ACTIVITY_NEW_TASK 方式启动一个 Activity 时,如果某个任务栈里已经有该 Activity 了,那么会将该任务栈从后台切换到前台,也即从停止状态切换到活跃状态,否则先创建一个新的任务栈,将创建一个 Activity 实例,将该实例作为 Root Activity(任务栈最底部的 Activity)。
不过,如果同时也添加了 Intent.FLAG_ACTIVITY_MULTIPLE_TASK 属性,那么系统会无条件创建一个新的任务栈。

如果要启动的 Activity 配置了 android:taskAffinity,那么系统会将该 Activity 放到对应的任务栈中。

销毁任务栈

  • 当我们从最近任务中移除对应的任务时,该任务栈也会被移除。从 Android 5.x 起,重启后任务栈可以被恢复。(see[持久化的任务栈][持久化的任务栈])
  • 当任务栈中已经没有 Activity 时,该任务栈会被自动销毁。
  • 当系统内存较少时,任务栈也有可能被移除,只保留重建该任务栈需要的信息。

当任务栈被销毁时,之前在任务栈中的 Activity 关联的服务会触发 onTaskStopped() 回调,可以决定是否要结束服务;如果在 AndroidManifest.xml 设置 android:stopWithTask="true"(默认 false),则不会接收到该回调,同时服务会自动被销毁。不过在 Android 4.4 上,由于存在 bug(参 Issue 104308Issue 63618),即使没有设置 android:stopWithTask="true",当任务栈被销毁时,对应的服务也会被销毁。

当任务栈是正常销毁的(比如通过最近历史销毁),Activity 的 onDestroy 会被调用,然而如果是被异常结束(如通过第三方应用结束),则 onDestroy 不会被调用。

恢复任务栈

有四种情况下会恢复一个任务栈,

  • 通过最近运行历史启动一个任务栈;
  • FLAG_ACTIVITY_NEW_TASK 且非 FLAG_ACTIVITY_MULTIPLE_TASK 方式运行一个已经运行过的 Activity;
  • if the taskAffinity for the activity being started ties it to another task
  • if the launch mode for the activity being started ties it to another task

使用 flag 实现 Activity 实例的复用

默认情况下,启动一个 Activity 时,会创建一个新的实例,要改变这种行为,可以使用 flag 或者启动模式。

  • FLAG_ACTIVITY_REORDER_TO_FRONT:如果某任务栈中存在要启动的 Activity 的实例,则将其调整到栈顶。调整到栈顶的过程中不会清除其上的实例。当然,没有 Activity 创建时,onCreate 也就不会执行。如果已经添加了 FLAG_ACTIVITY_CLEAR_TOP,则该 flag 会被忽略。
  • FLAG_ACTIVITY_CLEAR_TASK:如果某任务栈中存在要启动的 Activity,则将该 Activity 之外的其他 Activity 清除,将其作为 Root Activity。一般与 FLAG_ACTIVITY_NEW_TASK 组合使用。如果想要每次启动该 Activity 时都具有该行为,可以设置 android:clearTaskOnLaunch="true"
  • FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:如果某任务栈中已经存在该 Activity 了,则启动该 Activity 或者位于其上方合适的 Activity(触发[Reparenting Tasks][Reparenting Tasks])。比如存在 A 启动 B,此时以 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 方式启动 A,则会显示 B;如果使用的是 FLAG_ACTIVITY_CLEAR_TASK,则显示的是 A。一般,桌面启动器启动一个 Activity 时,使用的就是 FLAG_ACTIVITY_NEW_TASKFLAG_ACTIVITY_RESET_TASK_IF_NEEDED

对于合适的 Activity,有如下要求:
(1)该 Activity 不是以 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 方式启动。
(2)该 Activity 不是位于某个以 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 方式启动的 Activity 之上。
比如:存在任务栈中存在 A - B - C - D,C 是以 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 方式启动的,此时如果我们以 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 方式启动 A 或 B,都会显示的是 B。

Reparenting Tasks

一个 Activity 实例所属的任务栈可以被改变。

通过配置 android:allowTaskReparenting="true",我们可以实现一个特殊的效果。例如,如果应用 B 启动了 A 的一个 Activity,接着回到桌面,如果这时我们访问 A 的默认 Activity,会出现被调用的 Activity。也即该 Activity 实例从 B 的某个任务栈中转移到 A 的某个任务栈(包括新建的)中,并作为要显示的 Activity。默认情况下(没有特别指定所属的任务栈), 启动一个 Activity 时,如果存在设置了 android:allowTaskReparenting="true" 的 Activity,并且其他任务栈中找到了对应的实例,就将其挪到应用默认的任务栈中。

自动销毁的 Activity

在某些情况下,我们想要在离开某些 Activity 时,将其销毁。除了可以使用 finish 来完成,还可以使用 android:noHistoryFLAG_ACTIVITY_NO_HISTORY 来实现。对于这类 Activity,是不能使用 startActivityForResult() 来启动的。

android:finishOnTaskLaunch 也会自动销毁 Activity,不过是在重新会到该 Activity 的时候才销毁旧的 Activity。

alwaysRetainTaskState

默认情况下,如果一个任务栈超过一定的时间没有访问(30分钟以上),那么下次访问就会回到 Root Activity。如果不希望出现这种情况,可以给 Root Activity 设置 android:alwaysRetainTaskState="true"

不显示最近历史记录的 Activity

同样的,可以通过 android:excludeFromRecents="true" 或者 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 来设置,不过只适用于 Root Activity。

持久化的任务栈

默认情况下,当任务栈被销毁后,如果从最近任务中访问该任务栈,则只会显示任务栈里的 Root Activity。5.x 起,应用可以为 Activity 配置独立的持久化数据,用于在重建任务栈时恢复原来的执行状态。在进行任务栈重建时,会先执行以下方法对应的单参数版本,然后执行该方法:

public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState);
public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState);
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState);

需要注意的是 PersistableBundle 中 不能存放ParcelableSerializable 类型的数据。

而至于恢复到哪个 Activity,则取决与 Root Activity 的 persistableMode:

  • persistNever:重启不持久化。
  • persistRootOnly:默认,只持久化 Root Activity。
  • persistAcrossReboots:持久化所有设置了 persistAcrossReboots 并且不是以 FLAG_CLEAR_TASK_WHEN_RESET 启动的 Activity。

Document

5.x 起,Android 还支持一种称为 Document 的启动模式。这种模式有点类似浏览器的多标签页,比如用户打开了多个文档,每个文档都有独立的上下文,反映在最近运行任务列表里,每一个文档都有一个独立的项。

使用方法有两种: (1)设置 android:documentLaunchMode

  • always:总是在新 document 中启动;
  • intoExisting:由同一个类、启动意图的 Uri 相同的,都放到同一个 document 中。
  • never:该 Activity 不以新 document 的形式启动。
  • none:默认,只有当使用 flags 声明要以新 document 方式启动才会以新 document 方式启动。

(2)设置 flags:

  • FLAG_ACTIVITY_NEW_DOCUMENT:效果与 intoExisting相似,如果要效果类似 always,则还应该设置 Intent.FLAG_ACTIVITY_MULTIPLE_TASK

默认情况下,系统不会自动去除 document,如果需要自动去除 document,可以在 Root Activity 上使用 android:maxRecents 设置最多要保留的 document 数量,系统会自动应用 LRU 算法来确定要保留哪些 document。默认情况下,android:maxRecents 的值为 16。另外,还可以设置 android:autoRemoveFromRecents="true",这样当 Root Activity 退出时,document 就会被清除。如果要手动清除,也可以使用 finishAndRemoveTask()(see [与任务栈操作有关的其他方法][与任务栈操作有关的其他方法])。

四种启动模式(launchMode

  • standard:标准模式,默认,每次启动都会创建一个新的实例。

在 5.0 之前新启动的 Activity 实例会放入发送 Intent 的 Task 的栈的顶部,即使它们属于不同的程序;在5.0之后,如果是跨进程调用,则会创建一个新的 Task,将新启动的 Activity 放入刚创建的 Task 中。

  • singleTop:栈顶复用模式,通过 onNewIntent 处理新的意图,相当于以 FLAG_ACTIVITY_SINGLE_TOP 启动 Activity。

  • singleTask:栈内复用模式,通过 onNewIntent 处理新的意图,具有 clearTop 效果,启动这种模式的 Activity 时,如果栈内有其它 Activity 位于其上,启动的同时这些 Activity 会被清除。如果启动的 Activity 位于某个后台任务栈中,该任务栈会被整个切换到前台,同样清除其上的所有 Activity,而退出该 Activity 后会继续执行该 Activity 下方的其它 Activity,直到该任务栈为空,才会回到原来的任务栈中。(注意考虑:每个 Activity 都有所属的默认任务栈名称)
    backstack singletask

  • singleInstance:单实例模式,通过 onNewIntent 处理新的意图,这种 Activity 会单独位于一个任务栈中。

android:taskAffinity

某些情况下,我们想要将某些 Activity 存放到其他任务栈中,以达到某些目的。这时,我们可以通过该属性设置任务栈的名称,也可以对整个 Application 进行设置。

启动模式与返回值的情况

launch_mode_and_start_activity_for_result

与任务栈操作有关的其他方法

  • finishAffinity():清除当前任务栈中自身以及所有与当前 Activity 具有相同 taskAffinity 的 Activity,如果当前任务栈中存在多种的 taskAffinity,只会清除那些相同的。
  • finishAndRemoveTask():立刻清除当前任务栈中的所有 Activity 和任务栈。比如在退出登录时,就可以执行 finishAndRemoveTask(),接着启动登录页面。
  • getTaskId():获得当前 Activity 所在的任务栈 Id。(测试时,可以用 android.os.Process.myPid() 获得进程 Id)
  • isTaskRoot():判断是否是 Root Activity。
  • moveTaskToBack():将任务栈切换到后台中,类似与点击 Home 键的效果。(据说退出微信的时候就是这样的)
  • setTaskDescription():Android 5.0 起,可以使用该方法修改 ActivityManager.TaskDescription,以更改历史任务窗口中当前任务栈的图标、标题、标题背景颜色、缩略图等。

扩展阅读

参考