1. Activity 为什么需要启动模式
- 默认情况下,多次启动同一个 Activity,系统会重复创建多个实例,a little stupid
- 应该是主要从性能角度考虑、基于复用的思想,所以提供了启动模式 LaunchMode 来给开发者可以修改系统默认行为
2. Activity 四种启动模式的含义
standard: 标准模式
- 这也是系统的默认模式。每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否已经存在
- 被创建的实例的生命周期符合典型情况下的 Activity 的生命周期,它的
onCreate()
、onStart()
、onResume()
方法都会被调用 - 这是一种典型的多实例实现,一个任务栈中可以有多个相同的实例,每个实例也可以属于不同的任务栈
- 在 standard 模式下,谁启动了这个 Activity,那么这个 Activity 就运行在启动它的那个 Activity 所在的栈中
- 当用 ApplicationContext 去启动 standard 模式的 Activity 时会报错:
E/AndroidRuntime(***): android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
- 上述报错是因为 standard 模式的 Activity 默认会进入启动它的 Activity 所属的任务栈中,但是由于非 Activity 类型的 Context(如 ApplicationContext)并没有所谓的任务栈
- 解决这个问题的方法可以是为待启动的 Activity 指定 FLAG_ACTIVITY_NEW_TASK 标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候待启动 Activity 实际上是以 singleTask 模式启动的
- 当用 ApplicationContext 去启动 standard 模式的 Activity 时会报错:
singleTop: 栈顶复用模式
- 在 singleTop 模式下,如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的
onNewIntent()
方法会被调用,通过此方法的参数我们可以取出当前请求的信息 - 需要注意的是,这个 Activity 的
onCreate()
和onStart()
方法不会被系统调用,因为它并没有发生改变 - 如果新 Activity 的实例已存在但不是位于栈顶,那么新 Activity 仍然会重新创建
- 在 singleTop 模式下,如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的
singleTask: 栈内复用模式
- 这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例。和 singleTop 一样,系统也会回调其
onNewIntent()
方法 - 当一个具有 singleTask 模式的 Activity 请求启动后,系统首先会寻找是否存在这个 Activity 想要的任务栈。如果不存在这个任务栈,就创建一个任务栈,然后创建这个 Activity 的实例后把这个 Activity 放到栈中;如果存在这个 Activity 所需的任务栈,这时要看这个 Activity 是否在栈中有实例存在,如果有实例存在,那么系统就会把这个 Activity 调到栈顶并调用它的
onNewIntent()
方法,如果实例不存在,就创建 Activity 的实例并把 Activity 压入栈中 - 需要注意的是,singleTask 模式默认具有 clearTop 效果,会导致要启动的 Activity 上面的其它所有 Activity 全部出栈
- 这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例。和 singleTop 一样,系统也会回调其
singleInstance: 单实例模式
- 这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性外,还加强了一点,那就是具有此模式的 Activity 只能单独地位于一个任务栈中
- 当启动具有 singleInstance 模式的 Activity 后,系统会为它创建一个新的任务栈,然后这个 Activity 独自存放在这个新的任务栈中,由于栈内复用的特性,后续的启动请求均不会创建新的 Activity,除非这个独特的任务栈被系统销毁
3. 如果系统中存在 2 个任务栈,前台任务栈和后台任务栈,且后台任务栈中的 Activity 均为 singleTask 模式,此时启动后台任务栈以及按 back 键的交互情况
启动后台任务栈中的栈顶 Activity D: 整个后台任务栈都会被切换到前台
启动后台任务栈中的非栈顶 Activity C: 整个后台任务栈都会被切换到前台且栈顶 Activity D 出栈
4. 承载 Activity 的任务栈的含义
这要从一个参数说起:TaskAffinity,可以翻译为任务相关性
这个参数标识了一个 Activity 所需要的任务栈的名字,默认情况下,所有 Activity 所需的任务栈的名字为应用的包名
- 在 AndroidMenifest.xml 文件中,activity 标签下的 taskAffinity 属性的值为字符串,且中间必须含有包名分隔符 .
- 我们可以为每个 Activity 都单独指定 TaskAffinity 属性,这个属性值必须不能和包名相同,否则就相当于没有指定
- TaskAffinity 属性主要和 singleTask 启动模式或者 allowTaskReparenting 属性配对使用,在其他情况下没有意义
- 当 TaskAffinity 和 singleTask 启动模式配对使用的时候,它是具有该模式的 Activity 的目前任务栈的名字,待启动的 Activity 会运行在名字和 TaskAffinity 相同的任务栈中
- 当 TaskAffinity 和 allowTaskReparenting 结合的时候,这种情况比较复杂,会产生特殊的效果。当一个应用 A 启动了另一个应用 B 的某个 Activity 后,如果这个 Activity 的 allowTaskReparenting 属性为 true 的话,那么当应用 B 被启动后,此 Activity 会直接从应用 A 的任务栈转移到应用 B 的任务栈中
任务栈分为前台任务栈和后台任务栈,后台任务栈中的 Activity 位于暂停状态,用户可以通过切换将后台任务栈再次调到前台
5. 给 Activity 指定启动模式的方式及区别
通过 AndroidMenifest.xml 在 activity 标签下指定,比如:android:launchMode=”singleTask”
通过在代码 Intent 中设置标志位指定,比如:
1
2
3
4Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);两种方式的区别:
- 代码方式比配置文件方式的优先级高,当两种同时存在时,以第二种方式为准
- 两种方式的限定范围不同,第一种方式无法直接为 Activity 设定 FLAG_ACTIVITY_CLEAR_TOP 标识;第二种方式无法为 Activity 指定 singleInstance 模式
6. 指定为 singleTask 启动模式的 Activity,当再次启动时,其生命周期方法的调用情况
onPause() -> onNewIntent() -> onResume()
7. 获取 Activity 运行情况的 adb 命令
adb shell dumpsys activity
- dumpsys 是一种在 Android 设备上运行的工具,可提供有关系统服务的信息
- 可以使用 adb 从命令行调用 dumpsys,获取在连接的设备上运行的所有系统服务的诊断输出,比如检查输入、RAM、电池或网络诊断等
8. Activity 常见的标记位 Flags
FLAG_ACTIVITY_NEW_TASK:
- 这个标记位的作用是为 Activity 指定 singleTask 启动模式,其效果和在 XML 文件中指定该启动模式相同
FLAG_ACTIVITY_SINGLE_TOP:
- 这个标记位的作用是为 Activity 指定 singleTop 启动模式,其效果和在 XML 文件中指定该启动模式相同
FLAG_ACTIVITY_CLEAR_TOP:
- 当启动具有此标记位的 Activity 时,在同一个任务栈中所有位于它上面的 Activity 都要出栈
- 这个标记位一般会和 singleTask 启动模式一起出现。实际上,singleTask 启动模式默认就具有此标记位的效果
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:
- 具有这个标记的 Activity 不会出现在历史 Activity 的列表中,某些情况下当我们不希望用户通过历史列表回到我们的 Activity 的时候这个标记比较有用
- 它等同于在 XML 中指定 Activity 的属性
android:excludeFromRecents="true"
9. Android 系统清除返回堆栈的设计逻辑
默认情况下,如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity
- 当用户再次返回到该任务时,只有根 Activity 会恢复
- 系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作
我们可以使用下面三个 Activity 属性来修改此默认行为:
alwaysRetainTaskState:
- 如果在任务的根 Activity 中将该属性设为 true,则不会发生系统的默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity
clearTaskOnLaunch:
- 如果在任务的根 Activity 中将该属性设为 true,那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity
- 它与 alwaysRetainTaskState 正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此
finishOnTaskLaunch:
- 该属性与 clearTaskOnLaunch 类似,但它只会作用于单个 Activity 而非整个任务
- 它还可以导致任何 Activity 消失,包括根 Activity
- 如果将该属性设为 true,则 Activity 仅在当前会话中归属于任务,如果用户离开任务再返回,则该任务将不再存在