23、Activity 冷启动与热启动:AMS在两种启动方式下的差异与优化
说起Activity的启动,很多同学第一反应就是「调startActivity嘛」。但同样是启动,冷启动和热启动背后的机制天差地别。我当年刚接触Framework时,就踩过一个坑——在冷启动流程里加了个耗时操作,结果应用启动直接慢了800ms。后来才明白,这两种启动方式在AMS中的处理路径完全不同。
说白了,冷启动就是进程还没创建,一切从零开始。热启动呢,进程已经活着,Activity实例可能还在栈里。AMS对这两种场景的处理策略,直接决定了用户体验。
冷启动:从头搭建舞台
冷启动的流程,我习惯把它拆成三个阶段:
- 进程创建——Zygote fork新进程
- Application初始化——创建Application、绑定ContentProvider
- 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是否还在栈中
- 如果存在,直接调用
onNewIntent或onResume
你想想看,热启动省掉了进程创建、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的启动模式是singleTop或singleTask,AMS会调用onNewIntent而不是重新创建。我建议你在onNewIntent中做增量更新,避免全量刷新UI。
SVG流程图:冷启动与热启动的AMS处理路径
从这张图可以清楚看到,冷启动和热启动在AMS中的处理路径差异巨大。冷启动需要经过Zygote fork、进程attach、Application初始化等重操作,而热启动直接复用已有资源。
避坑指南:我曾经在优化冷启动时,试图通过修改AMS源码来跳过Application初始化。结果发现ContentProvider的依赖链太复杂,强行跳过会导致空指针。后来我改用「懒加载」策略,把非核心ContentProvider的初始化延迟到真正使用时。
最后说一句,冷启动优化是个系统工程。AMS层面的优化只是其中一环,还需要配合Application、Activity、布局渲染等多方面协同。但理解了AMS在两种启动方式下的差异,你就抓住了优化的「牛鼻子」。
公众号:蓝海资料掘金营,微信deep3321