启动优化专项
启动优化要先定义用户可感知里程碑, 再区分本地 / CI 基准测试与线上真实用户监控. 不要用单次
am start -W或平均值代替完整证据. 方法论, 指标口径与跨域取舍见 性能优化总览.
一, 启动类型与官方指标
- 冷启动: 进程不存在, 系统创建进程, Application 和首个 Activity, 再绘制首帧.
- 温启动: 进程仍在, 但 Activity 需要重新创建或恢复部分状态.
- 热启动: 进程和 Activity 基本保留, 主要恢复到前台.
Android 常用指标:
- TTID(Time to Initial Display): 从启动到第一帧显示, 代表用户第一次看到界面.
- TTFD(Time to Full Display): 从启动到应用报告 “关键内容已可用”, 通常通过
reportFullyDrawn()或相关 API 标记.
如果项目使用 TTFI 等自定义指标, 必须定义起止点, 交互条件和采集方法, 不能与官方 TTFD 混用.
二, Application 与初始化治理
- 盘点 Application, ContentProvider, Startup Initializer 和首屏依赖.
- 只有首帧前必需且线程安全要求明确的任务才同步执行.
- 可延迟功能按首次使用初始化; 可并行任务仍需避免主线程等待和锁竞争.
- SDK 必须公开初始化成本, 线程模型, 幂等性和禁用自动初始化的方法.
- 异步并不天然更快: 如果首屏马上等待结果, 只是把阻塞换了位置.
三, ContentProvider 与 App Startup
系统会在 Application.onCreate() 前实例化清单中的 Provider. 成本来自组件实例化, 类加载, 初始化逻辑和 manifest 合并; 同进程 Provider 初始化不等于每个 Provider 都发生 IPC.
Jetpack App Startup 用单个 InitializationProvider 统一声明依赖和初始化顺序, 可减少散落 Provider, 但不会自动让耗时工作变快. 应删除不需要的自动初始化项, 并为必要初始化建立 trace 与预算.
四, 本地 / CI 与线上观测
| 场景 | 目标 | 工具与证据 |
|---|---|---|
| 本地定位 | 找到主线程阻塞, 锁, I/O, 类加载和布局瓶颈 | Perfetto, System Trace, CPU Profiler, am start -W 辅助 |
| CI 回归 | 在受控设备上比较版本差异 | Macrobenchmark, Baseline Profile, 稳定迭代与统计分布 |
| 线上监控 | 观察真实用户分布和版本退化 | Android vitals, 项目启动埋点, P50/P90/P95/P99, 版本/机型分层 |
Macrobenchmark 是本地或 CI 基准测试工具, 不是线上 APM. 线上指标要考虑采样, 设备性能, 安装状态, 网络, 实验分组和异常值.
五, 排查与验证流程
- 定义启动场景, TTID/TTFD 里程碑和目标分位数.
- 固定设备, 构建类型, 预热状态和迭代次数, 建立基线.
- 用 Perfetto 定位主线程长任务, Binder 等待, I/O, 锁和 RenderThread/GPU 阶段.
- 改造初始化依赖图, 首屏数据路径, 布局和 Profile.
- 用 Macrobenchmark 做 A/B 对比, 并在线上按版本和设备档位观察回归.
实操: 命令, Splash 与 trace
命令示例 (需 USB 调试, 已安装目标包): 先执行 adb shell am force-stop com.example.app, 再执行 adb shell am start -W -n com.example.app/.MainActivity.
示意输出, 非真实测量:
Status: ok
LaunchState: COLD
Activity: com.example.app/.MainActivity
ThisTime: 318
TotalTime: 421
WaitTime: 438
Complete
Status 表示启动请求是否成功; LaunchState 是系统判断的启动状态, 需结合本次是否 force-stop 解读; Activity 是最终启动组件. ThisTime 是目标 Activity 启动相关耗时, TotalTime 是本次启动链路耗时, WaitTime 包含 shell 等待启动完成的时间, 因此三者不可互换, 也不能用单次值宣布收益. 重复同一冷/温/热条件并记录分布.
Android 12 (API 31) 及以上用 SplashScreen API; 低版本由 AndroidX 兼容实现.上下文片段 (Activity): installSplashScreen() 必须在 super.onCreate 前调用, 保留条件只应等待极短且首屏必需的状态, 不能把重初始化藏在闪屏后.
override fun onCreate(state: Bundle?) { installSplashScreen().setKeepOnScreenCondition { !uiState.value.ready }; super.onCreate(state); setContentView(R.layout.main) }
在 Perfetto 中为 Application 初始化, 首 Activity 创建, 首屏数据绑定加入稳定的 trace section, 按时间看 Provider/Application/Activity/RenderThread slice 是否跨越首帧; 示例 slice 名是定位线索, 不是工具已生成的测量结论.
高频面试题
Q1: TTID 与 TTFD 有什么区别?
TTID 表示第一帧已经显示; TTFD 表示应用定义的关键内容已准备完成. 只有明确调用 fully drawn 报告并定义业务条件, TTFD 才有可比性.
Q2: ContentProvider 为什么可能拖慢启动?
它在 Application 之前实例化, 可能触发类加载, 对象创建和同步初始化. 问题是初始化工作本身及数量, 不是 “每个 Provider 都必然 IPC”.
Q3: 如何证明优化有效?
本地 trace 证明瓶颈消失, Macrobenchmark 证明受控环境下分布改善, 线上分位数证明真实用户没有回退; 三类证据不能互相替代.
易错点 / 追问
- 不把 TTFI 当官方统一指标; 保留时写清项目定义.
- 不把 Macrobenchmark 放在线上监控栏.
- 不用单次耗时或平均值宣布优化成功.
- 不把所有初始化都扔到后台线程; 先检查首屏依赖和线程安全.
版本与参考资料
- 最后核验: 2026-08-07
- App startup time
- Macrobenchmark overview
- App Startup