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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

高中数学:数列-基础概念

一、什么是数列&#xff1f; 一般地&#xff0c;我们把按照确定的顺序排列的一列数称为数列&#xff0c;数列中的每一个数叫做这个数列的项&#xff0c;数列的第一项称为首项。 项数有限个的数列叫做有穷数列&#xff0c;项数无限个的数列叫做无穷数列。 二、一般形式 数列和…

24考研408大变化,25考研高分上岸规划+应对策略

巧了&#xff0c;我有现成的经验&#xff1a; 数学和专业课的成绩都不高不低&#xff0c;刚好够用&#xff0c;其实408想上岸&#xff0c;不仅仅要学好408&#xff0c;还要学好考研数学&#xff0c;这是我的肺腑之言&#xff0c;我复试的时候&#xff0c;我知道的那些没有进复试…

搭建 Langchain-Chatchat 详细过程

前言 本文参考官网和其他多方教程&#xff0c;将搭建 Langchain-Chatchat 的详细步骤进行了整理&#xff0c;供大家参考。 我的硬件 4090 显卡win10 专业版本 搭建环境使用 chatglm2-6b 模型 1. 创建虚拟环境 chatchat &#xff0c;python 3.9 以上 conda create -n chat…

光伏电站绘制软件的基本方法

随着可再生能源的快速发展&#xff0c;光伏电站的建设日益受到重视。为了提高光伏电站设计的效率和准确性&#xff0c;光伏电站绘制软件的应用变得至关重要。本文将介绍光伏电站绘制软件的基本方法&#xff0c;包括绘制屋顶、屋脊、障碍物和参照物&#xff0c;铺设光伏板&#…

二叉树的实现(初阶数据结构)

1.二叉树的概念及结构 1.1 概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合&#xff1a; 1.或者为空 2.由一个根结点加上两棵别称为左子树和右子树的二叉树组成 从上图可以看出&#xff1a; 1.二叉树不存在度大于2的结点 2.二叉树的子树有左右之分&#xff0c;次序不能…

2024年全国大学生数据统计与分析竞赛B题论文和代码:电信银行卡诈骗检测数据分析和机器学习模型构建

2024年全国大学生数据统计与分析竞赛B题论文和代码已完成&#xff0c;代码为B题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模型的建立和求解、问题3模型的建立和求解&#x…

SpringBoot Elasticsearch06-以黑马商场为例-黑马程序员学习笔记

黑马商城作为一个电商项目&#xff0c;商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的&#xff0c;存在很多问题。 首先&#xff0c;查询效率较低。 由于数据库模糊查询不走索引&#xff0c;在数据量较大的时候&#xff0c;查询性能很…

统计信号处理基础 习题解答10-8

题目 一个随机变量具有PDF 。希望在没有任何可用数据的情况下估计的一个现实。为此提出了使最小的MMSE估计量&#xff0c;其中期望仅是对求的。证明MMSE估计量为。将你的结果应用到例10.1&#xff0c;当把数据考虑进去时&#xff0c;证明最小贝叶斯MSE是减少的。 解答 在贝叶…

2024年如何通过完善的工程化,从0到1手把手建立个人 react 组件库

本文聚焦于快速创建并部署个人的组件库&#xff0c;方便平时开发中将通用的组件抽出&#xff0c;也可用于简历上展示个人的组件成果~ 组件库体验地址&#xff1a;components-library 关于以上内容&#xff0c;你是否好奇如何实现的&#xff0c;对于大多数项目&#xff0c;诸如…

计算机网络基础-VRRP原理与配置

目录 一、了解VRRP 1、VRRP的基本概述 2、VRRP的作用 二、VRRP的基本原理 1、VRRP的基本结构图 2、设备类型&#xff08;Master&#xff0c;Backup&#xff09; 3、VRRP抢占功能 3.1&#xff1a;抢占模式 3.2、非抢占模式 4、VRRP设备的优先级 5、VRRP工作原理 三…

素颜个人引导页源码

源码介绍 素颜个人引导页源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果预览 源码下载 素颜个人引导页源码

Hadoop3:MapReduce源码解读之Map阶段的数据输入过程整体概览(0)

一、MapReduce中数据流向 二、MapTask并行度 1、原理概览 数据块&#xff1a;Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位。 数据切片&#xff1a;数据切片只是在逻辑上对输入进行分片&#xff0c;并不会在磁盘上将其切分成片进行存储。数据切片是MapRed…

视频去水印电脑版,视频去水印软件

视频去水印怎么去&#xff0c;一直是视频编辑者们的热门话题。那么&#xff0c;如何去除频水印呢&#xff1f;接下来&#xff0c;我们将为您详细介绍视频去水印方法。 第一种方法&#xff1a; 首先通过浏览器打开 “ 51视频处理官网” 的网站。打开网站后&#xff0c;我们上传…

Linux--标准IO库

一、标准IO简介 所谓标准 I/O 库则是标准 C 库中用于文件 I/O 操作&#xff08;譬如读文件、写文件等&#xff09;相关的一系列库函数的集合&#xff0c;通常标准 I/O 库函数相关的函数定义都在头文件 <stdio.h> 中&#xff0c;所以我们需要在程序源码中包含 <s…

Windows 11中删除分区的几种方法,总有一种适合你

序言 想从Windows 11 PC中删除一个分区,以便将空间重新分配给现有分区或创建一个新分区吗?我们将为你介绍删除Windows 11分区的多种方法。 删除Windows上的分区时会发生什么 删除分区时,Windows会擦除该分区的内容,并将该分区从电脑上的任何位置删除。你将丢失保存在该分…

【启程Golang之旅】协程和管道操作

欢迎来到Golang的世界&#xff01;在当今快节奏的软件开发领域&#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面&#xff0c;Golang&#xff08;又称Go&#xff09;无疑是一个备受瞩目的选择。在本文中&#xff0c;带领您探索Golang的世界&#xff0c;一步步地了…

美国演员工会SAG-AFTRA 要求人工智能在广告中使用演员声音需征得同意并付费

SAG-AFTRA 的新豁免允许在人工智能生成的广告中使用演员的声音&#xff0c;但需要同意、补偿和安全措施 美国演员工会&#xff08;SAG-AFTRA&#xff09;推出了一项新的豁免&#xff0c;以保护会员免受未经授权的人工智能在广告中使用其声音的影响。动态人工智能音频广告豁免定…

C语言----字符串、字符数组

一、定义 C语言中的字符串是以字符数组的形态存在的 在C语言中&#xff0c;没有字符串类型&#xff0c;字符串实际上是使用空字符\0结尾的一维字符数组。因此&#xff0c;\0是用于标记字符串的结束。 二 、如何创建字符串&#xff1f; 1.通过字符数组来创建字符串&#xff0…

哈尔滨三级等保测评需要测哪些设备?

哈尔滨三级等保测评需要测的设备&#xff0c;主要包括物理安全设备、网络安全设备和应用安全设备三大类别。这些设备在保障哈尔滨地区信息系统安全方面发挥着至关重要的作用。 首先&#xff0c;物理安全设备是确保信息系统实体安全的基础。在哈尔滨三级等保测评中&#xff0c;物…

[Vue3:Vite构建项目]:安装router实现登录页面路由跳转

文章目录 一&#xff1a;前置依赖查看依赖安装vite npm create vitelatest sys-instruction-0607 --template vue-ts安装路由&#xff1a;npm install vue-router4安装elementUI&#xff1a;npm install element-plus --save 二&#xff1a;配置文件&#xff1a;views&#xff…