App冷启动分析
冷启动,温启动,热启动对比
冷启动
冷启动指 App 进程不存在时,用户启动 App。
冷启动过程中主要有以下步骤:
- 系统创建进程
- 加载 Application
- 执行 Application.onCreate()
- 创建首个 Activity
- 执行 Activity.onCreate() / onStart() / onResume()
- 加载布局或 Compose 首屏
- 完成第一次绘制
- 用户看到并可以操作首屏
点击图标
↓
系统创建进程
↓
Application 初始化
↓
Activity 初始化
↓
首屏 UI 构建
↓
首帧绘制 / 首屏可用
温启动
用户点击 App
↓
复用已有进程
↓
创建 / 恢复 Activity
↓
执行 Activity.onCreate() / onStart() / onResume()
↓
加载 UI
↓
首帧显示
常见温启动场景:
- 后台 Activity 被系统回收,进程还在
- 用户从最近任务返回,但 Activity 需要重建,比如 App 在后台待了一段时间,系统保留了进程,但任务栈里的 Activity 被销毁或需要恢复。
- 配置变更后重建界面。如:横竖屏切换,深色模式切换,语言切换等
- 从通知点击进入某个页面,如果 App 进程还在,但目标 Activity 不在,点击通知可能会重新创建目标 Activity。
- 从外部链接 / Deeplink 拉起 App。外部链接 / Deeplink 拉起 App,如果进程还活着,但承接 deeplink 的 Activity 要重新创建,就属于温启动。
热启动
App 进程还在,Activity 实例也还在,用户只是把已经在后台的页面重新带回前台。它是三种启动里通常最快的一种。
冷启动优化
一般说的 Android 启动优化,主要指的就是冷启动优化。因为冷启动最慢、链路最长、最容易被用户感知,也是启动性能治理里的重点。
检测启动时间
在说具体优化前我们先看一看启动时间是怎么检测的,方便后续对比分析
Android 启动时间一般有三种获取方式:命令行粗测、代码埋点、专业工具/自动化测试。实际项目里通常会组合使用。
用 adb 快速获取
adb shell am start -W 包名/启动Activity
例如
adb shell am start -W com.example.app/.MainActivity
注意在测冷启动时一般需要先关闭程序。可以用以下命令:
adb shell am force-stop com.hhmp.health
也可以直接使用am start -S -W -n com.hhmp.health/.module.home.WelcomeActivity。-S 标识先关闭APP
完整示例:
adb shell am force-stop com.hhmp.health
adb shell am start -W -n com.hhmp.health/.module.home.WelcomeActivity
Starting: Intent { cmp=com.hhmp.health/.module.home.WelcomeActivity }
Status: ok
LaunchState: COLD
Activity: com.hhmp.health/.module.home.WelcomeActivity
TotalTime: 1053
WaitTime: 1055
Complete
重点字段含义
| 字段 | 含义 |
|---|---|
| Status | 启动状态,ok 表示成功 |
| LaunchState | 启动类型,可能是 COLD、WARM、HOT |
| Activity | 实际启动的 Activity |
| ThisTime | 最后一个 Activity 启动耗时,ThisTime 是最后一个 Activity 的启动耗时。 |
| TotalTime | 整个 Activity 启动链路耗时,一般粗测启动时间主要看这个 |
| WaitTime | ActivityManager 等待启动完成的总耗时, 命令从发起启动到等待完成的总时间,包含系统调度、等待等额外开销。它通常会略大于 TotalTime。 |
在粗测时一般不建议只测一次,最好连续测 5 到 10 次。然后看中位数,而不是只看单次结果。
注意:上面可以看到我测的是启动时加载第一个页面WelcomeActivity所用的时间,但时在业务上。用户可操作界面时HomeActivity(WelcomeActivity中做一些初始化加载后会自动条找到HomeActivity).所以实际的冷启动应该是到HomeActivity,但是并不能直接使用“am start -S -W -n com.hhmp.health/.module.home.HomeActivity。”
可以使用以下命令:
adb shell am start -S -W -n com.hhmp.health/.module.home.WelcomeActivity
几秒后,等HomeActivity加载了
adb logcat -d | findstr /i "Displayed com.hhmp.health"
可能会看到
ActivityTaskManager: Displayed com.hhmp.health/.module.home.WelcomeActivity: +1s050ms
ActivityTaskManager: Displayed com.hhmp.health/.module.home.HomeActivity: +1s850ms
HomeActivity 的 Displayed,这个可以粗略表示:HomeActivity 首次显示出来的时间。。
注意:HomeActivity 的 Displayed,这个不是不是总启动时间。只是HomeActivity启动到显示的时间。并没有统计进去WelcoleActivity中初始化的具体时间。我们可以通过日志中的日志打印时间来分析,示例如下:
PS C:\Users\Administrator> adb logcat -c
PS C:\Users\Administrator> adb shell am start -S -W -n com.hhmp.health/.module.home.WelcomeActivity
Stopping: com.hhmp.health
Starting: Intent { cmp=com.hhmp.health/.module.home.WelcomeActivity }
Status: ok
LaunchState: COLD
Activity: com.hhmp.health/.module.home.WelcomeActivity
TotalTime: 1127
WaitTime: 1129
Complete
PS C:\Users\Administrator> Start-Sleep -Seconds 6
PS C:\Users\Administrator> adb logcat -d -v time | findstr /c:"Displayed com.hhmp.health"
05-29 15:38:31.051 I/ActivityTaskManager( 1326): Displayed com.hhmp.health/.module.home.WelcomeActivity: +1s127ms
05-29 15:38:34.233 I/ActivityTaskManager( 1326): Displayed com.hhmp.health/.module.home.HomeActivity: +250ms
PS C:\Users\Administrator> adb logcat -d -v time | findstr /c:"Displayed com.hhmp.health"
05-29 15:38:31.051 I/ActivityTaskManager( 1326): Displayed com.hhmp.health/.module.home.WelcomeActivity: +1s127ms
05-29 15:38:34.233 I/ActivityTaskManager( 1326): Displayed com.hhmp.health/.module.home.HomeActivity: +250ms
05-29 15:38:43.763 D/nemuinit( 1092): start name: clipboard, operation: adb logcat -d -v time | findstr /c:"Displayed com.hhmp.health", id: 0
05-29 15:38:43.765 D/nemuinit( 1092): Proxy: use callback: 1326, handleNemuInitMessage, name: clipboard, params: adb logcat -d -v time | findstr /c:"Displayed com.hhmp.health", res: nemu init proxy clipboard handled
计算冷启动开始 -> HomeActivity 显示,需要HomeActivity 显示时间 - 冷启动开始时间
冷启动开始时间可以用 WelcomeActivity 显示时间减去它自己的耗时
15:38:31.051 - 1.127s = 15:38:29.924, 15:38:34.233 - 15:38:29.924 = 4.309s
所以这次最终结果是:
冷启动到 WelcomeActivity 显示:1.127s
冷启动到 HomeActivity 显示:约 4.309s
HomeActivity 自身显示:0.250s
所以优化重点不是 HomeActivity 慢,而是 WelcomeActivity -> HomeActivity 之间有接近 3 秒 的等待。这个地方优先查。
使用 reportFullyDrawn() 获取完整首屏时间
是 Android 提供的一个启动性能标记方法,用来告诉系统:
这个 Activity 的关键内容已经完整绘制完成,用户现在可以正常使用了。
一般来说系统知道一个activity绘制完成时间也就是上面的Displayed com.hhmp.health/.module.home.HomeActivity: +250ms
,但是他不知道的是,数据是否加载完,页面是否真的可用,所以需要我们自己再合适的时机调用reportFullyDrawn,告诉系统:我已经完整显示了。注意:它只是一个信号,不是优化方法本身,也不会自动减少启动耗时
例如我的界面是WelcomeActivity->HomeActivity,HomeActivity 中加载数据(3s左右)
再HomeActivity使用下面代码模拟加载数据
lifecycleScope.launch {
delay(3000)
reportFullyDrawn()
}
日志中大概会看到:
2026-06-04 10:22:10.812 1546-1591 ActivityTaskManager system_server I Displayed com.hhmp.health/.module.home.WelcomeActivity for user 0: +1s606ms
2026-06-04 10:22:14.059 1546-1591 ActivityTaskManager system_server I Displayed com.hhmp.health/.module.home.HomeActivity for user 0: +322ms
2026-06-04 10:22:16.963 1546-1591 ActivityTaskManager system_server I Fully drawn com.hhmp.health/.module.home.HomeActivity: +3s226ms
分析可以得到
- 冷启动到 WelcomeActivity 显示:1.606s
- WelcomeActivity到 HomeActivity 首次显示:10:22:14.059 - 10:22:10.812 = 3.247s
- HomeActivity启动到完全可用时间:3.226s
- 第二条里面包含了HomeActivity启动时间第三条里面也包含了启动时间所以要减去HomeActivity启动时间(启动到页面展示)
- 总时间1.606s+3.247s+3.226s-0.332s=7.747s
常见的优化方式
-
优化 ContentProvider
- 很多三方 SDK 会通过 ContentProvider 自动初始化,发生在 Application.onCreate() 之前。改成手动初始化,移除无用 SDK
-
精简 Application
- 只保留启动必须任务
- SDK 初始化分级
- 非首屏 SDK 延迟初始化
- 用到时再初始化
- 避免主线程 I/O
- 避免同步等待网络、数据库、文件
- 减少大对象创建
- 减少全局单例提前初始化
-
避免主线程阻塞
-
首屏数据分层加载不要等所有接口都回来才显示首页。
-
使用 Baseline Profile
-
R8 和包体优化,包体和 Dex 越大,启动类加载、资源加载成本可能越高。
-
线程和任务调度,异步不是万能的。启动时开太多线程也会抢 CPU。
-
缓存策略,通过缓存用户数据,配置,图片等内容,同过优先加载这些缓存来提高启动速度,最后等网络回来后再刷新
评论区