简介
Activity 有四种不同的启动模式,这四种模式分别是:standard,singleTop,singleTask,singleInstance。
这四种模式中,standard模式是默认的模式,其他三个想要使用的话,要在 AndroidMainFest 文件中进行修改(通过给对应的 activity 设置 launchMode 属性,例如:
预备知识
任务栈(Task Stack) 介绍(任务栈也叫返回栈(Back Stack)):
- 任务栈用于存放用户开启的 Activity
- 在应用程序创建之初,系统会启动一个任务栈(默认一个),并存储根 Activity
- 新启动的 Activity 将会位于栈顶
- 当任务栈的最后一个 Activity 被销毁时,将清除任务栈并退出程序,下次再进入程序时会创建新的任务栈
taskAffinity 介绍:
- taskAffinity 是 Activity 的一个属性,可在 Manifest 文件中设置。
- Task 也有该属性,它的值由第一个入栈的 Activity 决定
- Application 也有 taskAffinity 属性,它的值为 Manifest 的包名
- 默认情况下(没有显示设置 Activity 的 taskAffinity),所有 Activity 的 taskAffinity 属性都从 Application 继承,也就是说所有 Activity 的 taskAffinity 值都相同,为包名。
- taskAffinity 的值应该是 xxx.xxx.xxx 这种样式,如果只是普通的字符串 xxx,是安装不了应用的。
如何在代码中获取 Activity 的 taskAffinity 属性值和 Activity 所在 Task:
1
2
3
4
5
6
7
8
9// 当前 Activity 的 taskAffinity 属性值
String taskAffinity = "";
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(),
PackageManager.GET_META_DATA);
taskAffinity = activityInfo.taskAffinity;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
1 | // 当前 Activity 所在 Task |
四种模式
概述
- standard:默认的启动方式。在该模式中,每启动一个新活动,都会创建一个新的实例,并在任务栈中入栈,处于栈顶的位置。假设你不停地进入同一个活动,你点击了十次,就要返回十次才能退出程序,因为你在返回栈中创造了十个相同的实例,尽管活动是一样的。
- singleTop:启动一个新活动时,系统会检查该活动是否已经位于任务栈栈顶,如果是的话直接使用已存在的栈顶活动,否则就创建新活动并压入栈顶。
- singleTask:和 singleTop 类似,不过这里是检查整个任务栈的活动,如果发现已经存在该活动就将位于该活动上方的活动全部出栈,该活动成为新的栈顶。
- singleInstance:最特殊的模式,系统为该模式的活动分配一个独立的任务栈,该任务栈有且只有一个该活动实例。也就是说,如果已经创建过目标活动实例,那么将不会创建新的任务栈,而是唤醒之前创建过的活动实例。
活动生命周期的变化
- 在 standard 模式下,启动一个新活动时,旧活动和新活动依次执行的生命周期方法如下:
1
2
3
4
52019-09-13 14:56:58.287 9848-9848/com.feng.startoptimizationtest D/fzh: onPause: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 14:56:58.346 9848-9848/com.feng.startoptimizationtest D/fzh: onCreate: com.feng.startoptimizationtest.FirstActivity@4511a17, run
2019-09-13 14:56:58.351 9848-9848/com.feng.startoptimizationtest D/fzh: onStart: com.feng.startoptimizationtest.FirstActivity@4511a17, run
2019-09-13 14:56:58.353 9848-9848/com.feng.startoptimizationtest D/fzh: onResume: com.feng.startoptimizationtest.FirstActivity@4511a17, run
2019-09-13 14:56:58.840 9848-9848/com.feng.startoptimizationtest D/fzh: onStop: com.feng.startoptimizationtest.FirstActivity@162e105, run
可以看到,先执行了旧活动的 onPause,然后依次执行新活动的 onCreate,onStart 和 onResume,最后执行旧活动的 onStop
- 在 singleTop, singleTask 或 singleInstance 模式下,启动一个和当前栈顶活动相同的活动时,依次执行的生命周期方法如下:
1
22019-09-13 15:08:21.972 12508-12508/com.feng.startoptimizationtest D/fzh: onPause: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:08:21.973 12508-12508/com.feng.startoptimizationtest D/fzh: onResume: com.feng.startoptimizationtest.FirstActivity@162e105, run
可以看到,这时不会创建新的活动,而是先后执行当前活动的 onPause 和 onResume 方法
- FirstActivity 为 singleTask 模式,先启动 FirstActivity,然后启动 SecondActivity,最后再一次启动 FirstActivity,其中,最后一次启动 FirstActivity 涉及的生命周期方法如下:
1
2
3
4
5
62019-09-13 15:20:30.938 13922-13922/com.feng.startoptimizationtest D/fzh: onPause: com.feng.startoptimizationtest.SecondActivity@45089c1, run
2019-09-13 15:20:30.957 13922-13922/com.feng.startoptimizationtest D/fzh: onRestart: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:20:30.959 13922-13922/com.feng.startoptimizationtest D/fzh: onStart: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:20:30.960 13922-13922/com.feng.startoptimizationtest D/fzh: onResume: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:20:31.396 13922-13922/com.feng.startoptimizationtest D/fzh: onStop: com.feng.startoptimizationtest.SecondActivity@45089c1, run
2019-09-13 15:20:31.399 13922-13922/com.feng.startoptimizationtest D/fzh: onDestroy: com.feng.startoptimizationtest.SecondActivity@45089c1, run
可以看到,在 singleTask 模式下,如果任务栈已经有要启动的活动,则一次调用该活动的 onRestart, onStart 和 onResume,而在栈中位于该活动之上的其他活动将会被销毁(这里为 SecondActivity)
- FirstActivity 为 singleInstance 模式,先启动 FirstActivity,然后启动 SecondActivity,最后再一次启动 FirstActivity,其中,最后一次启动 FirstActivity 涉及的生命周期方法如下:
1
2
3
4
52019-09-13 15:38:51.995 15361-15361/com.feng.startoptimizationtest D/fzh: onPause: com.feng.startoptimizationtest.SecondActivity@51dbca3, run
2019-09-13 15:38:52.008 15361-15361/com.feng.startoptimizationtest D/fzh: onRestart: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:38:52.009 15361-15361/com.feng.startoptimizationtest D/fzh: onStart: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:38:52.011 15361-15361/com.feng.startoptimizationtest D/fzh: onResume: com.feng.startoptimizationtest.FirstActivity@162e105, run
2019-09-13 15:38:52.668 15361-15361/com.feng.startoptimizationtest D/fzh: onStop: com.feng.startoptimizationtest.SecondActivity@51dbca3, run
可以看到,由于 FirstActivity 位于不同的任务栈,并且该任务栈只会存在一个活动实例,所以重新启动 FirstActivity 时不会再次创建实例,而是复用之前的实例,所以会依次执行 FirstActivity 的 onRestart, onStart 和 onResume。
添加 FLAG 后对 Activity 启动的影响
上面分析的启动模式都是静态设置的,也就是在 Manifest 文件中设置的,并且只分析了 Activity 启动 Activity 的情况,采用的是默认的 Intent,没有额外添加任何 FLAG。
下面就来分析一下给 Intent 设置了 FLAG 后,对 Activity 的启动由什么影响:
Intent.FLAG_ACTIVITY_NEW_TASK
- 该 FLAG 的作用:
这个 FLAG 跟 SingleInstance 有点相似,设置了该 FLAG 后,会检查是否存在与目标 Activity 的 taskAffinity 值相同的 Task,如果不存在的话就新建一个这样的 Task 并将目标 Activity 压入栈顶。
要注意的是,默认情况下,同一个应用的所有 Activity 的 taskAffinity 值是相同的,为应用包名。所以如果没有在 Manifest 文件中将目标活动的 launchMode 属性设置为 singleInstance,那么启动新活动时添加了 Intent.FLAG_ACTIVITY_NEW_TASK 这个 FLAG 并不会产生影响,新活动还是在同一个栈中。
而如果没有设置该 FLAG 的话,即使两个 Activity 的 taskAffinity 值不一样,也不会新建一个 Task。
- 和 SingleInstance 的区别:
- Intent.FLAG_ACTIVITY_NEW_TASK 这个 FLAG 只是决定是否新建一个 Task,没有规定新的 Task 只能有一个活动实例,而 SingleInstance 模式下新的 Task 只能有一个活动实例
- 在默认情况下,同一个应用的所有 Activity 的 taskAffinity 值相同,此 FLAG 不会创建新的 Task,而 SingleInstance 模式下会创建新的 Task
- 根据启动模式可分为两类:
- singleTask 和 singleInstance 在 AMS 中被预处理后,隐性地设置了 Intent.FLAG_ACTIVITY_NEW_TASK 这个 FLAG
- standard 和 singleTop 则需要显性地设置该 FLAG,即通过 Intent.setFlags 设置
- 非 Activity 启动 Activity 都必须添加 Intent.FLAG_ACTIVITY_NEW_TASK 才行。例如在 Service 中启动 Activity,需要这样写:
1
2
3Intent intent = new Intent(StartActivityService.this, FirstActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
如果没有添加该 FLAG 的话,就会抛出异常(据说在 Android 7.0 和 8.0 版本不会抛出异常,不过在 Android 9.0 已经修复了这个 bug)
为什么要这样限制呢?
如果某个活动不是通过 Activity 启动的,说明不是用户主动的行为,也就是说这个活动可能会出现在任何 APP 的活动之上,这时如果不用 Intent.FLAG_ACTIVITY_NEW_TASK 将这个活动限制在自己的 Task 中,就可能让用户误以为新的活动是属于当前 APP,这是不合理的。
- 有一种情况比较特殊,并不会启动新的 Activity:
如果新启动 Activity 的 taskAffinity 和当前 task 的该属性一致,此时如果新 Activity 的 Intent 和 当前 task 的根 Activity 的 Intent 一致的话,就不启动新 Activity,否则启动新 Activity。
下面用一张图来说明:(此图出处:https://www.jianshu.com/p/b3a95747ee91)
Intent.FLAG_ACTIVITY_CLEAR_TASK
- 首先要注意,Intent.FLAG_ACTIVITY_CLEAR_TASK 这个 FLAG 必须和 Intent.FLAG_ACTIVITY_NEW_TASK 配合使用,即:
1
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
单独使用 Intent.FLAG_ACTIVITY_CLEAR_TASK 是没有作用的。
- 两个 FLAG 配合使用的效果:
如果目标 Task 已存在,就先清空目标 Task(将目标 Task 原有的 Activity 全部出栈),然后新 Activity 称为目标 Task 的根 Activity。如果目标 Task 还不存在,就新建目标 Task,新 Activity 成为根 Activity(这和单独设置 Intent.FLAG_ACTIVITY_NEW_TASK 的效果一样)。
- 和启动模式的搭配:
- 如果新 Activity 的 launchMode 为 standard, singleTop 或 singleTask,那么启动模式对 FLAG 没有影响,还是会清除原有 Task 的 Activity。
- 如果新 Activity 的 launchMode 为 singleInstance,分两种情况:
- 新 Activity 没有创建过,那么该 FLAG 就不起作用,只有 singleInstance 其作用,新 Activity 成为一个新建 Task 的根 Activity,其他 Activity 不受影响。
- 新 Activity 已经创建过了,那么该 FLAG 就其作用了,目标 Task 中原有的实例会被销毁,然后新 Activity 成为目标 Task 的根 Activity。
Intent.FLAG_ACTIVITY_SINGLE_TOP
这个 FLAG 的效果和在 Manifest 文件设置 launchMode 为 singleTop 是一样的。
当然,它还可以和 Intent.FLAG_ACTIVITY_NEW_TASK,Intent.FLAG_ACTIVITY_CLEAR_TASK 等 FLAG 配合使用,从而产生和 singleTop 不一样的效果。
Intent.FLAG_ACTIVITY_CLEAR_TOP
- 单独使用 Intent.FLAG_ACTIVITY_CLEAR_TOP,没有设置 launchMode 的情况:
假如 Task 中由下往上依次有 A、B、C,这时 C 启动 B,并且带有 Intent.FLAG_ACTIVITY_CLEAR_TOP,产生的结果是:Task 中原有的 B,C 出栈,新创建的 B 入栈,成为新的栈顶。
也就是说单独使用这个 FLAG 时,无论新 Activity 是否已经存在于栈中,它都会被创建。
- 和其它 launchMode 结合的情况:
- 当新 Activity 的 launchMode 为 singleTop 时,和该 FLAG 结合后,作用相当于 singleTask。
- 当新 Activity 的 launchMode 为 singleTask 或 singleInstance 时,该 FLAG 不起作用,由相应的 launchMode 主导。
- Intent.FLAG_ACTIVITY_CLEAR_TOP 和 Intent.FLAG_ACTIVITY_SINGLE_TOP 配合使用的情况:
由于 Intent.FLAG_ACTIVITY_SINGLE_TOP 相当于 launchMode 为 singleTop 的情况,所以由上面第二点可知,两者结合使用的效果就相当于 launchMode 为 singleTask。