启动信息全掌握,Android 15 重磅 API:ApplicationStartInfo-实战

时间:2024-06-08 20:48:39

下面我们试着采集几种情况下系统返回的 ApplicationStartInfo 信息。

首先我们需要知道如何获取 ApplicationStartInfo 实例,了解 App 启动的同学可能会猜到应该归属 ActivityManager 的处理范畴。

果然,笔者在 ActivityManager 类里发现 Android 15 新增了几个 ApplicationStartInfo 相关的配套 API:

  • addStartInfoTimestamp(key, timestampNs):允许开发者针对指定的 key 添加时间戳
  • getHistoricalProcessStartReasons(maxNum):进程启动的时候获取历史的启动信息 ApplicationStartInfo list,需要指定获取的 size 上限(指定 0 的话,会输出所有记录)
  • addApplicationStartInfoCompletionListener(executor, listener):添加 ApplicationStartInfo 发生变化时候的监听器,当进程完成启动的时候会在 executor 代表的线程里回调 listener,需要留意的是 listener 不能为 null,否则会触发 IllegalArgumentException
  • removeApplicationStartInfoCompletionListener():删除上面的监听器

实战的代码很简单:

  1. 在 Application 里通过 ActivityManager 拿到历史 ApplicationStartInfo list 并打印
  2. 并添加 ApplicationStartInfo 发生变化时候的监听
 class OSVApplication : Application() {
     ...
     override fun onCreate() {
         super.onCreate()
         Log.d("AppStart", "OSVApplication#onCreate()")val activityManager = getSystemService(ActivityManager::class.java)
         val applicationStartInfoList = activityManager.getHistoricalProcessStartReasons(3)
         val applicationStartConsumer = Consumer<ApplicationStartInfo> {
             Log.d("AppStart", "changed applicationStartInfo:${it.printApplicationStartInfo()}")
         }
 ​
         Log.d("AppStart", "Original applicationStartInfo list:\n")
         for (info in applicationStartInfoList) {
             Log.d("AppStart", "${info.printApplicationStartInfo()}")
         }
 ​
         activityManager.addApplicationStartInfoCompletionListener(
             executor,
             applicationStartConsumer
         )
     }
 }

测试的 DEMO 里只提供了 Activity 组件,我们针对该组件进行测试。

对于 Activity 画面来说,一般的启动方式有如下几种:

  • 最常见的从 Launcher 上直接启动
  • 偶尔的从 History 恢复启动
  • 别的 App 通过 Action 或包名启动(后面我们用 adb 模拟)

我们将尝试如上几种启动场景,看会输出怎样的 ApplicationStartInfo 结果。

首次通过 Launcher 启动 App

安装下测试 DEMO,并首次从 Launcher 上启动 App,看下 log。

 03-30 20:46:27.461  4499  4499 D AppStart: OSVApplication#onCreate()
 03-30 20:46:27.477  4499  4499 D AppStart: Original applicationStartInfo list:
 03-30 20:46:27.484  4499  4499 D AppStart: ApplicationStartInfo{intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:6  startType:1 startupState:0 startupTimestamps:{0=56169205650, 3=56491075233} wasForceStopped:false}
 ​
 03-30 20:46:27.638  4499  4499 D AppStart: AppStartActivity#onCreate()
 03-30 20:46:27.961  4499  4563 D AppStart: ApplicationStartInfo{intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:6  startType:1 startupState:2 startupTimestamps:{0=56169205650, 3=56491075233} wasForceStopped:false}

可以看到:

  1. 获取到的 ApplicationStartInfo 记录只有 1 条,符合预期
  2. intent 内容显示是从 Launcher 过来的启动请求
  3. launchMode 是 0 即 LAUNCH_MODE_STANDARD,因为咱们测试 Activity 的 launchMode 没声明,自然是默认值
  4. pid 是 0,这点有点奇怪,理论上来说应该是 App 的进程号 4499
  5. reason 是 6 即 START_REASON_LAUNCHER,表示是从 Launcher 上启动的
  6. startType 是 1 即 START_TYPE_COLD,表示进程冷启动
  7. startupState 是 0 即 STARTUP_STATE_STARTED,表示进程启动了
  8. wasForceStopped 是 false,因为是首次安装,还没启动过,自然没有被强制停止过,合理~
  9. 当目标 Activity 完成启动,在 listener 里回调了此次启动记录,所以信息都一致,只有 startupState 变化了,是 2 即 STARTUP_STATE_FIRST_FRAME_DRAWN,表示进程完成了第 1 帧的描画

kill 后从 History 启动

接着,我们手动 kill 进程之后,再通过 Launcher 上的 History 画面恢复进程看看 log:

 03-30 20:48:47.472  5218  5218 D AppStart: OSVApplication#onCreate()
 03-30 20:48:47.475  5218  5218 D AppStart: Original applicationStartInfo list:
 ​
 03-30 20:48:47.476  5218  5218 D AppStart: ApplicationStartInfo{intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:6  startType:1 startupState:0 startupTimestamps:{0=196526136925} wasForceStopped:true}
 ​
 03-30 20:48:47.476  5218  5218 D AppStart: ApplicationStartInfo{intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:6  startType:1 startupState:2 startupTimestamps:{0=56169205650, 3=56491075233} wasForceStopped:false}
 ...
  1. ApplicationStartInfo 信息增加到了 2 条,因为这是第 2 次启动了,可以理解
  2. 最新的 1 条里的 wasForceStopped 变成了 true,因为上次咱们手动 kill 了进程,所以系统正确地提供了这是被强制 kill 之后的首次启动
  3. intent 信息里 flg 是不同的,因为 Launcher 上对于 icon 启动和 History 恢复是不一样的 launch flags

但有一点出乎意外的是,最新的 1 条的 reason 并非预期的 7 即 START_REASON_LAUNCHER_RECENTS。不知道这的偏差是 DP 阶段的 bug 还是笔者的理解有 gap。

kill 后从 adb 启动

最后,我们再手动 kill 进程,然后用如下的 adb 模拟外部的调用:

 adb shell am start -n com.ellison.demo/.appStart.AppStartActivity

再看下 log:

 03-30 20:50:52.262  5456  5456 D AppStart: OSVApplication#onCreate()
 03-30 20:50:52.264  5456  5456 D AppStart: Original applicationStartInfo list:
 ​
 03-30 20:50:52.265  5456  5456 D AppStart: ApplicationStartInfo{intent:Intent { flg=0x10000000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:11  startType:1 startupState:0 startupTimestamps:{0=321414404318} wasForceStopped:true}
 ​
 03-30 20:50:52.265  5456  5456 D AppStart: ApplicationStartInfo{intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10100000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:6  startType:1 startupState:2 startupTimestamps:{0=196526136925} wasForceStopped:true}
 ​
 03-30 20:50:52.265  5456  5456 D AppStart: ApplicationStartInfo{intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.ellison.demo/.appStart.AppStartActivity } launchMode:0 packageUid:10197 realUid:10197 pid:0 processName:com.ellison.demo reason:6  startType:1 startupState:2 startupTimestamps:{0=56169205650, 3=56491075233} wasForceStopped:false}
 ...
  1. 启动信息增加到了 3 条,

  2. 最新的 1 条有如下信息:

    • intent 里正确打印了 adb 启动的命令信息
    • reason 是 11 即 START_REASON_START_ACTIVITY,成功显示这是启动 Activity 的调用
    • wasForceStopped 是 false,成功显示上次被强制 kill 了