发动信息全把握,Android 15 重磅 API:ApplicationStartInfo

本文为稀土技能社区首发签约文章,30天内制止转载,30天后未获授权制止转载,侵权必究!

前语

App 进程发动的时分,开发者很难获悉到本次发动的详细信息,比方:

  • 是冷发动的、暖发动的、仍是热发动的?
  • 是被 Broadcast 拉起来的、Activity 拉起来的、仍是 ContentProvider 拉起来的?

针对这些 pain-points,Android 15 引入了全新 API:ApplicationStartInfo,用以供给 App 进程发动时分的各种信息。包含:发动类型、来源、进程等等,开发者经过这些信息,能够清晰地把握发动的情况按需处理。

API

首先,咱们来查阅 ApplicationStartInfo 文档,看看所能供给的关键办法,进行逐个论述。

getIntent() & getLaunchMode()

getIntent() 能够获取发动该 App 的 Intent 信息,包含 Activity、Broadcast、Service 三种组件的情况。

getLaunchMode() 指导本 App 应当以哪种 LaunchMode 来处理该 Intent 恳求。发动模式包含:

  • LAUNCH_MODE_STANDARD(0)
  • LAUNCH_MODE_SINGLE_TOP(1)
  • LAUNCH_MODE_SINGLE_INSTANCE(2)
  • LAUNCH_MODE_SINGLE_TASK(3)
  • LAUNCH_MODE_SINGLE_INSTANCE_PER_TASK(4)

上述常量来自于恳求方的 intent 参数和方针 App 在 Manifest 里 launchMode 的参数。

getPackageUid() & getRealUid()

getPackageUid() 获取的是本 App 在装置时期所属的用户组 ID。

getRealUid() 获取的则是本 App 在运行时分所属的真实用户组 ID。

Uid 和 App 的级别有关,决议了 App 所能拜访的资源权限。

getPid() & getProcessName()

getPid() 获取的是本 App 的进程 ID。

getProcessName() 获取的是本 App 的进程称号。

getReason()

获取该进程被发动时分的原因。包含如下界说:

  • START_REASON_ALARM(0):由于 alarm 机制发动的进程
  • START_REASON_BACKUP(1):由于履行 backup 操作发动的进程
  • START_REASON_BOOT_COMPLETE(2):由于履行体系发动广播而发动的进程
  • START_REASON_BROADCAST(3):由于履行 Broadcast 而发动的进程
  • START_REASON_CONTENT_PROVIDER(4):由于履行 ContentProvider 的拜访而发动的进程
  • START_REASON_JOB(5):由于履行 JobService 而发动的进程
  • START_REASON_LAUNCHER(6):由于履行 Launcher 上点击 icon 发动的进程
  • START_REASON_LAUNCHER_RECENTS(7):由于履行 Launcher 上的前史康复而发动的进程
  • START_REASON_START_ACTIVITY(11):由于履行明示的 Activity 恳求而发动的进程

其间省掉的有由于 Service、Push 通知等场景发动的 Reason 界说。

getStartType() & getStartupState()

getStartType() 获取的是 App 进程发动的类型:

  • START_TYPE_UNSET(0):未知的发动状况
  • START_TYPE_COLD(1):进程彻底冷发动,
  • START_TYPE_WARM(2):进程暖发动,SavedInstanceState 数据还存在
  • START_TYPE_HOT(3):进程热发动,比方从后台进入前台

getStartupState() 获取的是 App 进程的发动状况:

  • STARTUP_STATE_STARTED(0):表明进程处于已经发动
  • STARTUP_STATE_ERROR(1):表明进程发动失败
  • STARTUP_STATE_FIRST_FRAME_DRAWN(2):表明进程已经发动并完结了第一帧的制作

getStartupTimestamps()

获取发动进程里花费的时间,以 ns 为单位。

wasForceStopped()

告知 App 本次发动是否是被强制中止后的发动,App 能够经过该值决议是否要从头注册 Alarm、JobService 等等。

实战

下面咱们试着收集几种情况下体系返回的 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 了

编译留意

面向 Android 15 DP 版开发,需要依照开始运用 Android 15 进行 IDE 配置。

另外,一定要升级 AGP 和 Gradle 的版别,否则会遇到如下的编译过错:

resource linking failed …/Library/Android/sdk/platforms/android-VanillaIceCream/android.jar.

AGP 建议:8.3.0,Gralde 建议:8.4.0。

结语

咱们总结了新 API ApplicationStartInfo 所能供给的信息内容,并结合几种常见的 Activity 发动场景进行了实战论述。除了个别信息与预期不符以外,大部分都是按期输出了发动信息。至于其他场景下的 App 进程发动:比方 ServiceBroadcastConentProvider 等,没有进行逐个的测验,感兴趣的同学能够自行研讨。

总的来说,经过该 API 能够轻松拿到发动时分的背景信息,十分方便。而此前 App 想要获取这些信息需要做很多内部记载和判别,十分繁琐。基于这些信息,开发者能够从进程发动这种新视角来 track 和剖析 App 各种运用进口的发动情况,还能够根据不同的发动信息在代码上按需处理发动逻辑。

参考资料