0%

Activity 的生命周期和启动模式(二):Activity 的启动模式

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 模式启动的
  • singleTop: 栈顶复用模式

    • 在 singleTop 模式下,如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的 onNewIntent() 方法会被调用,通过此方法的参数我们可以取出当前请求的信息
    • 需要注意的是,这个 Activity 的 onCreate()onStart() 方法不会被系统调用,因为它并没有发生改变
    • 如果新 Activity 的实例已存在但不是位于栈顶,那么新 Activity 仍然会重新创建
  • singleTask: 栈内复用模式

    • 这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会重新创建实例。和 singleTop 一样,系统也会回调其 onNewIntent() 方法
    • 当一个具有 singleTask 模式的 Activity 请求启动后,系统首先会寻找是否存在这个 Activity 想要的任务栈。如果不存在这个任务栈,就创建一个任务栈,然后创建这个 Activity 的实例后把这个 Activity 放到栈中;如果存在这个 Activity 所需的任务栈,这时要看这个 Activity 是否在栈中有实例存在,如果有实例存在,那么系统就会把这个 Activity 调到栈顶并调用它的 onNewIntent() 方法,如果实例不存在,就创建 Activity 的实例并把 Activity 压入栈中
    • 需要注意的是,singleTask 模式默认具有 clearTop 效果,会导致要启动的 Activity 上面的其它所有 Activity 全部出栈
  • singleInstance: 单实例模式

    • 这是一种加强的 singleTask 模式,它除了具有 singleTask 模式的所有特性外,还加强了一点,那就是具有此模式的 Activity 只能单独地位于一个任务栈中
    • 当启动具有 singleInstance 模式的 Activity 后,系统会为它创建一个新的任务栈,然后这个 Activity 独自存放在这个新的任务栈中,由于栈内复用的特性,后续的启动请求均不会创建新的 Activity,除非这个独特的任务栈被系统销毁

3. 如果系统中存在 2 个任务栈,前台任务栈和后台任务栈,且后台任务栈中的 Activity 均为 singleTask 模式,此时启动后台任务栈以及按 back 键的交互情况

  • 启动后台任务栈中的栈顶 Activity D: 整个后台任务栈都会被切换到前台
    任务栈示例 1

  • 启动后台任务栈中的非栈顶 Activity C: 整个后台任务栈都会被切换到前台且栈顶 Activity D 出栈
    任务栈示例 2

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
    4
    Intent 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 仅在当前会话中归属于任务,如果用户离开任务再返回,则该任务将不再存在
-------------------- 本文结束感谢您的阅读 --------------------