什么是冷启动、温启动和热启动?如何测量它们的耗时?
解读
面试官问“冷/温/热启动”时,真正想考察的是:
- 你对 AMS 启动流程的理解深度(Zygote fork、Application 创建、ActivityThread 主线程消息调度、生命周期回调顺序);
- 能否把“用户体验”翻译成可量化指标(系统日志、Systrace、Perfetto、AGP 插件、Google Play Vitals、国内厂商 APM);
- 是否具备线上灰度定位能力(T+1 看 Vitals、T+0 看自研埋点、回捞用户日志);
- 优化手段是否体系化(Theme 预渲染、Splash 轻量化、启动任务 DAG、类预加载、IO 收敛、GC 抑制、CPU 绑核、线程优先级)。
回答时要先给定义,再给测量方案,最后给出国内工程落地细节(华为/小米/OPPO 后台管控、微信/支付宝双进程保活、国内 ROM 对 GMS 的裁剪导致 startTime 字段缺失等),让面试官听到“你确实在 亿级 DAU 项目里踩过坑”。
知识点
-
系统定义(AOSP 12 源码)
- 冷启动:ProcessRecord == null,AMS 通过 Zygote fork 新进程,startTime 记录在 ProcessRecord.startUptime;
- 温启动:进程存活但 Activity 被回收(TRIM_MEMORY_UI_HIDDEN),走 realStartActivityLocked,不 fork;
- 热启动:Activity 仍在后台栈,只触发 onRestart → onStart → onResume,无 Application 重建。
-
关键时间戳
- 系统侧:Process.start 通过 socket 写给 Zygote,Zygote 返回 pid 后 AMS 记录 startUptime;
- 应用侧:ActivityThread.main() 到 Activity.onResume() 无阻塞时首帧上屏(Choreographer 回调);
- 用户侧:icon 点击到 SurfaceFlinger 合成第一帧(SF 的 VSYNC-app 信号)。
-
国内测量痛点
- 华为 EMUI 把 Process.start 时间截断到 0,需 hook libcutils 的 android_log_print 回捞;
- 小米 MIUI 对后台进程做 freezer,温启动会被误判成冷启动,需结合 /proc/[pid]/cgroup 判断;
- 国内渠道包无 GMS,无法使用 Google Play Vitals,需自建 MPM(Mobile Performance Monitor)后台。
-
工具链
- 线下:Systrace/Perfetto 抓 trace 标记,AGP 7.0+ 的 Macrobenchmark 库可输出冷启动 medianTime;
- 线上:字节码插桩在 Application.attachBaseContext 到 Activity.onResume 首尾写本地埋点,上报时携带 processStartUptime(反射 ActivityThread.AppBindData);
- 厂商:华为 HiSight、OPPO Diagnosis、vivo Vitals SDK 提供系统级 startTime,误差 < 5 ms。
答案
冷启动:应用进程不存在,系统从 Zygote fork 新进程,完整执行 Application → Activity 生命周期,耗时最长。
温启动:进程仍在,但 Activity 因内存压力被回收,只走 realStartActivityLocked,不重建 Application,耗时中等。
热启动:Activity 仅被 stop,仍在任务栈,直接 onRestart → onResume,耗时最短。
测量方法(国内工程化方案):
-
线下实验室
- 使用 Perfetto 抓 trace,在 ActivityThread.handleMessage 中搜索 “activityResume” 作为结束点,起点取 Process.start 的 uptimeMillis;
- AGP Macrobenchmark 连续 10 次冷启动,取 P50 与 P90,输出到 CI 门禁,超过 1100 ms 自动打回。
-
线上灰度
- 在 Application.attachBaseContext 首行记录 t0 = SystemClock.uptimeMillis();
- 在 Activity.onResume 首帧 Choreographer 回调后记录 t1;
- 用反射拿到 Process.startUptime(ActivityThread.AppBindData.startUptime),若拿不到则 fallback 到 t0;
- 计算冷启动耗时 = t1 – Process.startUptime,温/热启动耗时 = t1 – t0;
- 上报时携带 processImportance(区分前后台)、isFirstLaunch(区分新装)、romName(区分厂商),后台按版本+渠道聚合,看 P50/P90/P99;
- 对华为/小米等 ROM 做校准:读取 /proc/[pid]/stat 的 starttime 字段,换算成 uptime 修正系统截断问题。
-
阈值与告警
- 冷启动 P50 目标 ≤ 800 ms,P90 ≤ 1200 ms;
- 温启动 P90 ≤ 500 ms;
- 热启动 P90 ≤ 200 ms;
- 超过阈值自动触发回捞,拉取用户 10 s 级 Systrace(需提前内置 perfetto trace 探针)。
拓展思考
- 折叠屏/多窗口场景:大屏同时启动双 Activity,系统复用同一进程,但并行执行两个 onCreate,需把启动耗时拆成“首 Activity onResume”与“末 Activity onResume”,否则 P90 会被拉高 15%。
- 5G 弱网场景:国内运营商 IPv6 双栈导致 DNS 解析从 20 ms 涨到 200 ms,虽然属于网络耗时,但用户感知仍是“启动慢”,需在埋点里把“首帧”与“首包”拆开,避免冤枉客户端。
- 隐私沙盒 Android 14 Restricted SDK:getProcessStartUptime 被标记为 @hide 且加入灰名单,需使用 Jetpack Startup 提供的 StartupMetrics(内部走 binder 调用 system_server 新接口),否则明年 targetSdk=34 直接崩溃。
- 车载与 Wear:车机 ROM 把 Zygote 预加载做成快照(Snapshot),冷启动可降到 400 ms,但快照版本号升级时第一次启动会回退到常规冷启动,需单独做“快照版本”维度监控,防止指标突刺误判为回归。