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

news2024/11/25 3:01:44

android15-base-ApplicationStart.png

前言

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 各种使用入口的启动情况,还可以根据不同的启动信息在代码上按需处理启动逻辑。

参考资料

  • 开始使用 Android 15
  • ApplicationStartInfo API
  • ActivityManager API

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1803844.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ssm物流管理系统-计算机毕业设计源码44323

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…

解决:DevToolsActivePort file doesn‘t exist的问题

DevToolsActivePort file doesn’t exist 报错信息&#xff1a;解决办法&#xff1a;直接把sandbox禁用了。 chromeOptions.addArguments("--headless"); //设置为 headless 模式 chromeOptions.addArguments("--disable-gpu");//谷歌文档提到需要加上这…

代码随想录算法训练营第二十二天

题目&#xff1a;216. 组合总和 III 这道题和上道题非常类似&#xff0c;大体框架一样只不过修改一下终止条件而已 值得注意的是其中的剪枝条件的设置 一是靠现有的元素和已经大于目标和的话就提前终止&#xff0c;另一个是其中循环那个剪枝可以记住 i < n - (k - path.s…

二开版微交易系统

下载地址&#xff1a;二开版微交易系统

Dockerfille解析

用于构建Docker镜像的文本&#xff0c;由一条条指令构成 Docker执行Dockerfile的流程 1. Docker从基础镜像执行一个容器 2. 执行一条指令并对容器进行修改 3. 执行类型Docker commit的命令添加一个新的镜像层 4. Docker再基于新的镜像执行一个新的容器 5. 执行Dockerfile中…

C语言 | Leetcode C语言题解之第132题分割回文串II

题目&#xff1a; 题解&#xff1a; int minCut(char* s) {int n strlen(s);bool g[n][n];memset(g, 1, sizeof(g));for (int i n - 1; i > 0; --i) {for (int j i 1; j < n; j) {g[i][j] (s[i] s[j]) && g[i 1][j - 1];}}int f[n];for (int i 0; i <…

关于修改Python中pip默认安装路径的终极方法

别想了&#xff0c;终极方法就是手动复制&#xff0c;不过我可以给你参考一下手动复制的方法 关于手动移动pip安装包的方法 别想了&#xff0c;终极方法就是手动复制&#xff0c;不过我可以给你参考一下手动复制的方法一、首先确认一下pip默认安装路径二、再确认一下需要移动到…

SAP HCM OPT函数作用

导读 INTRODUCTION OPT函数&#xff1a;SAP HCM工资核算是很多函数的汇总集&#xff0c;原有有兴趣问过SAP的人为什么SCHEMA需要这样设计&#xff0c;SAP的人说是用汇编的逻辑设计的&#xff0c;当时是尽可能用机器语言加速速度读取&#xff0c;每个函数都有对应的业务逻辑代码…

9行超强代码用Python工具快速获取放假日期

9行超强代码用Python工具快速获取放假日期 在很多场景下,我们需要获知国内具体的节假日安排情况,而国内每一年具体的放假安排以及调休情况,都依赖于国务院发布的具体公告,如果不想自己手动整理相关数据的话,我们可以用Python来快速获取最新的放假日期. 可以通过调用公开的 API…

【web前端开发】标签(基础知识详解)

浏览器能识别的标签 编码 <meta charset"UTF-8"> title <title>helloshh</title> 标题 <h1>1级标签</h1> <h2>2级标签</h2> <h3>3级标签</h3> <h4>4级标签</h4> <h5>5级标签</h5> &…

ChatTTS增强版V2,批量导出srt,语速控制,情感控制,支持朗读数字,问题修复

ChatTTS增强版最新版本已经发布&#xff0c;本次更新我主要增加了多文本批量、SRT导出、语速控制、情感控制、停顿控制等新功能&#xff0c;并针对上一版本中存在的数字读音异常、随机uv_break等问题进行了修复。 视频版本 【ChatTTS增强版V2&#xff0c;批量导出srt&#xff…

【计算视觉】学习计算机视觉你不得不膜拜的CVPR大神:何凯明

目录 第一章&#xff1a;CVPR——计算机视觉的终极擂台 第二章&#xff1a;何凯明——计算机视觉领域的耀眼星辰 第三章&#xff1a;高引用论文——计算机视觉研究的璀璨星辰 第四章&#xff1a;何凯明的CVPR论文——深度学习的探索之旅 第五章&#xff1a;结语——向何凯…

网页文档下载不了怎么办 网页文档下载方法

一个方法&#xff0c;搞定所有网页文档下载。如果你也需要从网页下载各种文档&#xff0c;那么本文一定可以帮到你。无须充值会员&#xff0c;各大平台文档下到爽。看到就是赚到&#xff0c;还不赶快学起来。有关网页文档下载不了怎么办&#xff0c;网页文档下载方法的问题&…

Java中的IO流字节流(FileOutputStream与FileInputStream)+编码与解码

目录 ​编辑 IO流 File0utputstream FileOutputstream写数据的3种方式 void write(int b) 一次写一个字节数据 void write(byte[] b) 一次写一个字节数组数据 void write(byte[] b,int off,int len) 一次写一个字节数组的部分数据 FileOutputstream写数据的…

MathType 7.8最新版核心功能特性 及免费汉化版安装包下载地址

大家好&#xff01;今天我要给大家种草一个非常实用的数学公式编辑器——MathType 7.8&#xff01;作为一名软件评测专家&#xff0c;我对这款软件进行了详细的测试和试用&#xff0c;下面来给大家分享一下我的使用体验。 我们来说说MathType 7.8的核心特性吧&#xff01;它是一…

智能网联汽车信息安全风险识别与应对策略研究综述

摘要&#xff1a;随着智能网联汽车技术的飞速发展&#xff0c;其信息安全问题逐渐成为公众关注的焦点。本文概述了智能网联汽车技术的发展背景和信息安全风险的来源&#xff0c;采用STRIDE威胁分析方法对智能网联汽车的四层模型进行风险识别&#xff0c;进一步探讨了抗女巫攻击…

进口电动流量调节阀的选型-美国品牌

进口电动流量调节阀的选型需要综合考虑多个因素&#xff0c;以确保所选阀门能够满足实际应用需求。以下是选型时需要考虑的主要方面&#xff1a; 一、明确应用需求 工作介质&#xff1a;了解介质的性质&#xff0c;包括流体类型、温度、压力以及是否具有腐蚀性或特殊性质。流…

6.7.11 一种新的迁移学习方法可提高乳房 X 线摄影筛查中乳腺癌的诊断率

分割是一种将图像分割成离散区域的技术&#xff0c;以便将感兴趣的对象与周围环境分开。为了制定治疗计划&#xff0c;分割可以帮助医生测量乳房中的组织量。 二元分类问题的目的是将输入数据分为两组互斥的数据。在这种情况下&#xff0c;训练数据根据要解决的问题以二进制格…

基于JSP技术的网络视频播放器

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 首页 管理员界面 用户界…