下面我们试着采集几种情况下系统返回的 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
():删除上面的监听器
实战的代码很简单:
- 在 Application 里通过 ActivityManager 拿到历史 ApplicationStartInfo list 并打印
- 并添加 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}
可以看到:
- 获取到的 ApplicationStartInfo 记录只有 1 条,符合预期
- intent 内容显示是从 Launcher 过来的启动请求
- launchMode 是 0 即 LAUNCH_MODE_STANDARD,因为咱们测试 Activity 的 launchMode 没声明,自然是默认值
- pid 是 0,这点有点奇怪,理论上来说应该是 App 的进程号 4499
- reason 是 6 即 START_REASON_LAUNCHER,表示是从 Launcher 上启动的
- startType 是 1 即 START_TYPE_COLD,表示进程冷启动
- startupState 是 0 即 STARTUP_STATE_STARTED,表示进程启动了
- wasForceStopped 是 false,因为是首次安装,还没启动过,自然没有被强制停止过,合理~
- 当目标 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}
...
- ApplicationStartInfo 信息增加到了 2 条,因为这是第 2 次启动了,可以理解
- 最新的 1 条里的 wasForceStopped 变成了 true,因为上次咱们手动 kill 了进程,所以系统正确地提供了这是被强制 kill 之后的首次启动
- 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}
...
-
启动信息增加到了 3 条,
-
最新的 1 条有如下信息:
- intent 里正确打印了 adb 启动的命令信息
- reason 是 11 即 START_REASON_START_ACTIVITY,成功显示这是启动 Activity 的调用
- wasForceStopped 是 false,成功显示上次被强制 kill 了