4、冷启动、热启动与温启动的概念及区别

启动类型这个概念,说白了就是区分你的 App 从「死」到「活」到底经历了多少步骤。不同场景下,Activity 的创建成本天差地别。我个人习惯在埋点分析时先分清楚是哪种启动,不然你盯着一个 5 秒的启动耗时在发愁,结果发现是冷启动——其实已经算优化得很好了。

嗯,我们先看一张总览图,把三种启动的核心路径画出来。我管它叫「启动三兄弟」。

Activity 启动类型总览 用户点击图标 冷启动 温启动 热启动 进程不存在 创建进程 → Application → 创建 Main Activity 进程存活 Activity 被回收 重新创建 Activity 进程存活 Activity 存活在栈顶 直接 onResume 注意:温启动的 onCreate 耗时和冷启动接近,但少了 Application 初始化过程

冷启动:从零开始

冷启动,就是用户点击图标时,你的 App 进程根本就不存在。需要系统先 fork 一个进程,加载 Application,再走一遍 Activity 的完整生命周期。

我曾经排查过一个线上的长启动问题,发现冷启动里 Application 的 onCreate 竟然干了将近 800ms 的 IO 操作。你想想看,用户从点击到看到首页,光 Application 就占了快一秒,后面的 Activity 还得再走一遍。后来我把那些操作扔到子线程,冷启动直接砍掉 40%。

冷启动的典型特征:

  • 进程不存在,需要新建
  • Application 的 onCreate 完整执行
  • Activity 全部生命周期(构造 → onCreate → onStart → onResume)
  • View 树、布局、资源首次加载

实测数据参考(来自我之前的项目):

阶段耗时范围(毫秒)
进程创建 + Application200~800
Activity onCreate150~500
onStart + onResume50~150
首帧渲染完成50~200

注意:这里还没算 Application 里库的初始化,有些第三方 SDK 能吃掉 1 秒以上。

热启动:几乎零成本

热启动是什么?用户按了 Home 键,你的 Activity 还活在后端,系统没杀掉它。再点图标时,系统直接把 Activity 从栈里拉到前台,只走 onRestart → onStart → onResume。不会重新创建 Activity,更不会重建进程。

说白了,就是最快的一种启动。我见过很多同学把热启动的耗时也统计到「启动优化」里,其实没必要,热启动的 onResume 通常就几十毫秒,甚至比用户点感还快。重点要关注的永远是冷启动。

触发场景:

  • 用户按 Home 键后立即再次点击图标
  • 从最近任务列表切换回来
  • 通知栏点击后返回(如果 Activity 未被回收)

一个小技巧:埋点时用 procStats 或者 ActivityManager.getProcessMemoryInfo() 判断进程是否存活,这样就能区分冷启动和热启动了。我曾经用这个办法发现某款手机在 6GB 内存下,后台 15 个应用后就会杀掉我们的进程,导致热启动概率骤降。

温启动:最容易被忽略的中间态

温启动,就是进程还在,但 Activity 已经被回收了。比如用户切到其他 App 玩了一段时间,系统内存紧张,把你的 Activity 对象回收(destory),但进程没杀。这时再回来,系统会重建 Activity——走一遍 onCreate,但 Application 不用重新初始化。

嗯,这里要注意:温启动的 onCreate 耗时和冷启动几乎一样!因为 Activity 的构造、布局加载、数据恢复都得重来。但 Application 不重新走,所以总耗时比冷启动少 200~600ms 左右。

我碰到过一个坑:在温启动场景下,有些同学在 Activity 的 onCreate 里做了「首次启动引导」之类的判断,但温启动时 Application 的某些全局变量可能已经变了(比如用户登录态),导致引导逻辑出错。我曾经因为这个踩过雷——温启动时用户明明已经登录了,还弹出登录引导页。后来加了进程内标记才解决。

温启动的典型特征:

  • 进程存活
  • Activity 实例已被销毁
  • 重新走 onCreate → onStart → onResume
  • Application 的 onCreate 不执行

三种启动的对比总结

比较项冷启动温启动热启动
进程是否存在否是是
Application 是否重新初始化是否否
Activity 是否重建是是(重新创建)否(直接恢复)
走哪些生命周期完整:create→start→resume完整:create→start→resumeonRestart→onStart→onResume
典型耗时(不包含首帧)0.5~2秒0.2~1秒20~100毫秒
埋点参考指标进程创建到 onResume 结束Activity 创建到 onResume 结束onRestart 到 onResume 结束

避坑提示:我曾经犯过一个错误——在埋点上报时,把冷启动和温启动混在一起计算「平均启动耗时」。结果冷启动多的时候平均值很高,温启动多的时候就偏低,数据参考意义不大。正确做法是分开统计,或者只关注冷启动。你想想看,如果用户每次打开都走热启动,你却拿冷启动的标准去考核他,那优化方向就偏了。

如何准确识别这三种启动?

埋点代码里,我习惯用两个标志:一个进程存活标志(比如在 Application 的 attachBaseContext 里 set 一个全局变量),一个 Activity 是否被重建的标志(通过 savedInstanceState 是否为空,或者加一个 Application 级别的计数器)。

// 示例代码:Application 中记录进程启动时间
public class MyApp extends Application {
    public static boolean isProcessCold = false;
    public static long appCreateTime;

    @Override
    public void onCreate() {
        super.onCreate();
        isProcessCold = true;
        appCreateTime = SystemClock.elapsedRealtime();
        // 初始化其他库 ...
    }
}

// Activity 中判断启动类型
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (MyApp.isProcessCold) {
            // 冷启动
        } else if (savedInstanceState == null) {
            // 温启动(进程存活,但 Activity 首次创建)
        } else {
            // 旋转屏幕等重建,不算启动
        }
        MyApp.isProcessCold = false; // 重置
    }
}

注意:这个方案有个小瑕疵——如果 App 在后台待了很久,系统回收了 Activity 但没杀进程,此时 savedInstanceState 可能是 null(取决于系统是否保存)。更严谨的做法是监听 ActivityLifecycleCallbacks 并记录 Activity 个数。

我个人更推荐用 ActivityManager.getProcessRunningInfo() 或者直接读 /proc/self/status 里的进程启动时间来判断。但为了简单,上面的代码已经够应付 90% 的场景了。