Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

启动优化专项

启动优化要先定义用户可感知里程碑, 再区分本地 / CI 基准测试与线上真实用户监控. 不要用单次 am start -W 或平均值代替完整证据. 方法论, 指标口径与跨域取舍见 性能优化总览.

一, 启动类型与官方指标

  • 冷启动: 进程不存在, 系统创建进程, Application 和首个 Activity, 再绘制首帧.
  • 温启动: 进程仍在, 但 Activity 需要重新创建或恢复部分状态.
  • 热启动: 进程和 Activity 基本保留, 主要恢复到前台.

Android 常用指标:

  • TTID(Time to Initial Display): 从启动到第一帧显示, 代表用户第一次看到界面.
  • TTFD(Time to Full Display): 从启动到应用报告 “关键内容已可用”, 通常通过 reportFullyDrawn() 或相关 API 标记.

如果项目使用 TTFI 等自定义指标, 必须定义起止点, 交互条件和采集方法, 不能与官方 TTFD 混用.

二, Application 与初始化治理

  1. 盘点 Application, ContentProvider, Startup Initializer 和首屏依赖.
  2. 只有首帧前必需且线程安全要求明确的任务才同步执行.
  3. 可延迟功能按首次使用初始化; 可并行任务仍需避免主线程等待和锁竞争.
  4. SDK 必须公开初始化成本, 线程模型, 幂等性和禁用自动初始化的方法.
  5. 异步并不天然更快: 如果首屏马上等待结果, 只是把阻塞换了位置.

三, 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. 线上指标要考虑采样, 设备性能, 安装状态, 网络, 实验分组和异常值.

五, 排查与验证流程

  1. 定义启动场景, TTID/TTFD 里程碑和目标分位数.
  2. 固定设备, 构建类型, 预热状态和迭代次数, 建立基线.
  3. 用 Perfetto 定位主线程长任务, Binder 等待, I/O, 锁和 RenderThread/GPU 阶段.
  4. 改造初始化依赖图, 首屏数据路径, 布局和 Profile.
  5. 用 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 放在线上监控栏.
  • 不用单次耗时或平均值宣布优化成功.
  • 不把所有初始化都扔到后台线程; 先检查首屏依赖和线程安全.

版本与参考资料