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

启动优化专项

启动速度是用户对 App 的第一印象。作为 SDK 开发者,你需要知道如何做到既不拖累宿主,又能精准测量性能瓶颈。

一、启动分类与指标

启动可划分为三种典型场景,各自的耗时与指标不同:

  • 冷启动(Cold Start):最慢。系统为 App 创建进程 -> 实例化 Application -> 实例化首个 Activity -> 渲染第一帧。
  • 温启动(Warm Start):较快。App 进程存在,但 Activity 被回收需重新创建,或者用户从退出后返回重新将后台转前台并重建视图。
  • 热启动(Hot Start):最快。App 进程和 Activity 都在后台,仅将后台应用切到前台。

关键监控指标

  • 首帧时间 (TTID):首个可见 Activity 的第一帧渲染完成的时间。
  • 可交互时间 (TTFI):主线程空闲,用户可以开始操作界面的时间。

二、Application 初始化治理

很多冷启动问题源头是 Application.onCreate 堆积了过多第三方 SDK 的初始化操作。

延迟与异步初始化

并非所有 SDK 都必须在 Application.onCreate 主线程同步初始化:

  • 异步初始化:网络库、日志库等可在子线程初始化。
  • 延迟/按需初始化:地图、分享等 SDK,等用户真正点击进入相关页面前再初始化。

Jetpack App Startup

用来管理库初始化的官方方案:

  • 原理:利用一个统一的 ContentProvider 集中分发和按序初始化各组件。
  • 优势:减少多个第三方 SDK 各自带 ContentProvider 自启带来的性能损耗(每个 Provider 的创建和调用都有 IPC 与生命周期开销)。

三、ContentProvider 自动初始化陷阱

很多 SDK(如早期的 Firebase、WorkManager 等)为了对开发者透明,悄悄在库内声明 ContentProvider

  • 问题:系统启动应用时,会先实例化所有声明的 Provider,再调 Application.onCreate。过多 Provider 会严重拖慢启动。
  • 解法:通过 tools:node="remove" 在 manifest 中剔除不需要自启的 Provider,转为手动在适当的时机初始化。

四、排查路径与流程(本地 vs 线上)

本地诊断与线上监控是有本质区别的:

阶段关注点工具 / 手段
本地诊断找出具体的耗时方法与阻塞点adb shell am start -W 定量、Android Studio Profiler (CPU/Trace)、Perfetto / Systrace
线上监控掌握宏观大盘数据与用户体验情况Macrobenchmark、自定义埋点上报 TTID/TTFI、APM 平台聚合数据

定位流程:

  1. 定量分析:本地通过 adb shell am start -W 包名/Activity 获取 ThisTimeTotalTime,或线上看 APM 启动耗时分位数。
  2. 抓取 Trace:使用 Perfetto 抓取启动阶段的 trace 文件。
  3. 分析主线程 (Main Thread):在 Perfetto 中查看主线程的 slice,寻找导致阻塞的长任务(如文件 IO Open / Read、锁等待、长计算等)。
  4. 改造与验证:针对长任务进行异步、懒加载改造,再用 Macrobenchmark(避免 JIT 与系统状态差异)进行 A/B 对比测试。

五、SplashScreen API (Android 12+)

Android 12 引入了标准的 SplashScreen API,应用冷/温启动时系统会自动显示闪屏。

  • 优势:规范了启动体验,消除了应用自己实现闪屏页带来的白屏/黑屏,且首帧可立即进入业务 Activity,减少中间 Activity 跳转耗时。
  • 适配:通过配置主题属性(如 windowSplashScreenBackgroundwindowSplashScreenAnimatedIcon)修改系统闪屏样式,或使用 androidx.core:core-splashscreen 兼容低版本。

高频面试题

Q1: 冷启动的整个系统流程是怎样的? 从点击桌面图标 -> Launcher 通过 Binder 通知 AMS -> AMS 创建进程请求 -> Zygote fork 进程 -> ActivityThread.main -> 实例化 Application -> 实例化 Activity -> setContentView -> 渲染完成第一帧。

Q2: 为什么有些第三方 SDK 接入后会拖慢冷启动,即使用户还没调用它? 可能是 SDK 内部通过 ContentProvider 实现了自动初始化。系统在进程创建后、调用 Application.onCreate 之前,会先实例化并调用所有注册 Provider 的 onCreate,若里面有耗时操作或 IO,就会阻塞启动。

Q3: 如何用工具定位启动慢的具体代码行? 使用 Perfetto 或 Android Studio CPU Profiler (Call Chart/Flame Chart) 录制启动阶段。如果是代码插桩模式能看到具体方法耗时,如果是 System Trace 模式,重点看 Main Thread 上的长耗时 Slice 和线程状态(Blocked/Runnable 等)。

易错点 / 追问

  • 易错点: 把异步初始化当万能药。如果首屏 UI 恰好强依赖某个被异步到子线程初始化的数据/SDK,会导致数据还没准备好 UI 已经展示,或产生竞态崩溃。
  • 追问: 如果启动优化已经把能异步的都异步了,还能怎么优化?(答:类预加载、资源预加载、减少首屏布局层级/延迟加载非首屏 Fragment、甚至更底层的 PGO 等技术)。
  • 误区: 线上监控直接使用 System.currentTimeMillis() 算差值。线上容易受到系统休眠、时间跳变影响,建议用 SystemClock.uptimeMillis()