先区分 TTID 和 TTFD
- TTID(Time to Initial Display):系统接收到启动请求后,到应用绘制首帧所需的时间。
- TTFD(Time to Full Display):应用的主要内容达到可用状态所需的时间。应用需要在内容真正准备好时报告 fully drawn;否则 Macrobenchmark 无法得到有意义的 TTFD。
只看首帧可能漏掉“页面已经出现,但用户还不能操作”的等待。冷启动优化应同时观察这两个指标,并用相同设备、系统版本、应用构建和启动路径做对比。
用 Macrobenchmark 采集启动指标
Macrobenchmark 的 StartupTimingMetric 会输出启动时间统计。下面是测试模块中的基本结构,包名、依赖和目标设备需要替换成项目自己的配置:
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun coldStartup() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
setupBlock = { pressHome() },
) {
startActivityAndWait()
}
}
对比多次运行的中位数,并保留每次 benchmark 的输出和 trace。不要把单台开发机的一次结果当作所有设备的承诺。若 TTFD 没有数据,先确认应用是否在主内容可用时调用了 reportFullyDrawn;Compose 项目可以根据当前 AndroidX 文档使用 ReportDrawn、ReportDrawnWhen 或 ReportDrawnAfter。
从 trace 找出首帧前的工作
在 Android Studio Profiler 或 Perfetto 中检查从进程启动到首帧的时间线,重点找:
Application.onCreate()、ContentProvider 和依赖注入容器中的同步工作。- 首屏前不需要的磁盘 I/O、JSON 解析、反射扫描和网络等待。
- 多个 SDK 的初始化是否重复、是否依赖彼此,以及哪些初始化可以在用户首次需要时再进行。
- 主线程是否被锁等待、Binder 调用、GC 或大对象分配阻塞。
每次只延迟或拆分一类工作,再运行同一组 Macrobenchmark。延迟初始化要保留错误处理和加载状态;如果某项能力是首屏正确显示所必需的,就不能只为缩短 TTID 把它移到页面出现之后。
AndroidX App Startup 的作用
AndroidX App Startup 提供集中声明和组织初始化器依赖的方式。它能帮助梳理初始化顺序,但不会自动缩短启动时间。先用 trace 找到实际耗时,再决定是否将分散的初始化器纳入统一管理、移除不需要的自动初始化,或改成按需加载。
修改 Manifest 合并规则前,核实 SDK 是否依赖对应的 ContentProvider。误删初始化入口可能造成运行时故障,不能把“删 Provider”当作通用优化步骤。
SplashScreen 和首屏就绪
Android 12 及以上使用系统 SplashScreen 能保持启动视觉连续性,但启动画面本身不会减少真实初始化耗时。检查启动主题是否提供合适的窗口背景,并避免让 SplashScreen 因非关键请求或永不结束的加载状态一直停留。
TTID 和 TTFD 应分别记录:TTID 反映首帧是否及时,TTFD 反映主要内容何时可用。优化后还要回归深链、登录恢复、离线状态和首次安装等会影响启动路径的场景。
本文基于官方文档整理测量与排查流程,没有关联某个应用的实测数字或项目复盘。
参考资料
总结
先用 StartupTimingMetric 固定采集冷启动 TTID/TTFD,再沿 trace 找到首帧前的同步工作。只有基线、设备和启动场景一致,前后对比才有意义;App Startup 与 SplashScreen 是工程工具,不是无需测量的性能结论。
