23、Activity 冷启动与热启动:AMS在两种启动方式下的差异与优化

说起Activity的启动,很多同学第一反应就是「调startActivity嘛」。但同样是启动,冷启动和热启动背后的机制天差地别。我当年刚接触Framework时,就踩过一个坑——在冷启动流程里加了个耗时操作,结果应用启动直接慢了800ms。后来才明白,这两种启动方式在AMS中的处理路径完全不同。

说白了,冷启动就是进程还没创建,一切从零开始。热启动呢,进程已经活着,Activity实例可能还在栈里。AMS对这两种场景的处理策略,直接决定了用户体验。

冷启动:从头搭建舞台

冷启动的流程,我习惯把它拆成三个阶段:

  1. 进程创建——Zygote fork新进程
  2. Application初始化——创建Application、绑定ContentProvider
  3. Activity启动——创建Activity、attach、onCreate、onStart、onResume

AMS在冷启动中扮演的角色,其实更像一个「总导演」。它先告诉Zygote:「给我开个新进程」。等进程起来后,再通过Binder把启动指令发过去。

核心差异点:冷启动时,AMS需要等待进程创建完成,这个阶段是阻塞的。我曾在项目中遇到过,某些定制ROM在进程创建阶段加了安全检测,导致冷启动时间从200ms飙到1.2s。

来看AMS中冷启动的关键代码路径:

// AMS.startProcessLocked 简化流程
ProcessRecord app = new ProcessRecord(...);
// 1. 通过socket通知Zygote
startProcessByZygote(app);
// 2. 等待进程attach
synchronized(ams) {
    while(app.thread == null) {
        ams.wait();  // 这里会阻塞
    }
}
// 3. 进程已就绪,开始启动Activity
realStartActivityLocked(app, ...);

嗯,这里要注意。AMS在等待进程attach时,用的是wait/notify机制。如果进程创建失败或超时,AMS会抛出ProcessDiedException。我建议你在做性能优化时,重点关注这个阶段的耗时。

热启动:轻装上阵

热启动就简单多了。进程已经存在,AMS只需要做两件事:

  • 检查Activity是否还在栈中
  • 如果存在,直接调用onNewIntentonResume

你想想看,热启动省掉了进程创建、Application初始化、ContentProvider加载这些大头开销。所以热启动的耗时通常只有冷启动的十分之一甚至更少。

个人经验:我做过一个统计,在主流旗舰机上,冷启动平均耗时约500-800ms,而热启动只需要30-80ms。这个差距主要来自进程创建和Application初始化。

AMS在热启动时的处理逻辑:

// AMS.startActivityLocked 热启动分支
ActivityRecord r = mStackSupervisor.findTaskLocked(intent);
if (r != null && r.task != null) {
    // 任务已存在,直接复用
    mStackSupervisor.moveTaskToFrontLocked(r.task, ...);
    // 调用onNewIntent或直接resume
    if (r.state == STOPPED) {
        r.activityState = RESUMED;
        scheduleResumeActivity(r, ...);
    } else {
        r.deliverNewIntentLocked(caller, intent);
    }
    return;
}

说白了,热启动就是「找到已有的Activity,把它拉到前台」。AMS不会重新创建进程,也不会重新初始化Application。这也是为什么热启动感觉「秒开」的原因。

两种启动方式的AMS差异对比

对比维度 冷启动 热启动
进程创建 需要Zygote fork 不需要
Application初始化 完整执行 跳过
ContentProvider 全部加载 跳过
Activity创建 new Activity + attach 复用已有实例
AMS阻塞点 等待进程attach 几乎无阻塞
典型耗时 500-800ms 30-80ms

优化策略:从AMS层面入手

既然知道了差异,优化就有方向了。我总结了几条实战经验:

1. 减少冷启动中的AMS等待时间

AMS等待进程attach时,会持有mService锁。如果进程创建慢,整个系统都会卡住。我曾经在项目中通过预创建进程来优化——在系统空闲时提前fork一个进程,等真正需要时直接复用。

注意:预创建进程会增加内存占用,需要权衡。我建议只在内存充足的设备上开启。

2. 利用热启动的「秒开」特性

如果应用经常被用户切到后台再切回来,可以考虑让Activity保持在内存中。AMS的maxRecentTasks参数可以控制保留的任务数量。我习惯把这个值设大一些,比如32或48。

3. 优化Application初始化

冷启动时,Application的onCreate方法会阻塞AMS的启动流程。我建议把非必要的初始化放到子线程,或者使用IdleHandler延迟加载。

// 优化方案:延迟加载非核心组件
getMainLooper().getQueue().addIdleHandler(() -> {
    // 在这里初始化非核心组件
    initNonCriticalComponents();
    return false; // 只执行一次
});

4. 善用Activity的onNewIntent

热启动时,如果Activity的启动模式是singleTopsingleTask,AMS会调用onNewIntent而不是重新创建。我建议你在onNewIntent中做增量更新,避免全量刷新UI。

SVG流程图:冷启动与热启动的AMS处理路径

AMS冷启动 vs 热启动处理路径 冷启动路径 热启动路径 startActivity 进程存在? 1. Zygote fork新进程 2. AMS等待进程attach 3. Application初始化 4. 创建Activity实例 1. 查找已有ActivityRecord 2. 复用已有进程和实例 3. 调用onNewIntent/resume 4. 直接显示UI Activity可见

从这张图可以清楚看到,冷启动和热启动在AMS中的处理路径差异巨大。冷启动需要经过Zygote fork、进程attach、Application初始化等重操作,而热启动直接复用已有资源。

避坑指南:我曾经在优化冷启动时,试图通过修改AMS源码来跳过Application初始化。结果发现ContentProvider的依赖链太复杂,强行跳过会导致空指针。后来我改用「懒加载」策略,把非核心ContentProvider的初始化延迟到真正使用时。

最后说一句,冷启动优化是个系统工程。AMS层面的优化只是其中一环,还需要配合Application、Activity、布局渲染等多方面协同。但理解了AMS在两种启动方式下的差异,你就抓住了优化的「牛鼻子」。


公众号:蓝海资料掘金营,微信deep3321