5、Activity 启动流程(下):AMS内部处理、进程创建、ActivityThread调度
好,我们接着往下聊。上一节我们把 Activity 启动的“前半程”走完了——从 Launcher 的点击事件一路追到了 AMS 的 startActivity 方法。说白了,那会儿还只是“请求刚进门”,真正的重头戏,全在 AMS 内部怎么处理、怎么创建进程、怎么把 ActivityThread 拉起来干活。
我个人习惯把这一节分成三个大块:AMS 内部的状态机流转、Zygote 进程孵化、以及ActivityThread 的 main 方法调度。这三块串起来,就是一次完整的 Activity 启动下半场。
AMS 内部处理:一场精心编排的状态机
AMS 收到启动请求后,不会立刻就去创建进程。它得先做一堆“家务活”。
- 检查 caller 的权限和状态——比如 caller 是否还在前台,有没有被暂停。
- 解析 Intent——找到目标 Activity 所在的进程和包名。
- 判断是否需要新进程——如果目标 Activity 和 caller 在同一个进程,那就省事了;否则就得走进程创建流程。
我记得有一次在项目里遇到一个诡异的问题:App 启动后黑屏了好几秒才显示第一个 Activity。查了半天,发现是 AMS 在检查权限时,因为一个 ContentProvider 的授权校验卡住了。嗯,这种边界情况,平时真不容易想到。
AMS 内部维护了一个 ActivityRecord 栈,每个 Activity 对应一个 record。启动时,AMS 会先看看栈顶是不是同一个 record,如果是,可能就直接复用(onNewIntent)。如果不是,那就得走完整的启动链路。
核心要点:AMS 在这一阶段会决定“目标进程是否存在”。如果存在,直接通过 Binder 通知该进程的 ActivityThread 去创建 Activity;如果不存在,就得先创建进程。
进程创建:Zygote 的 fork 艺术
当 AMS 发现目标进程还没跑起来,它会向 Zygote 进程 发送一个 socket 请求。Zygote 收到后,会 fork 出一个新的子进程。
你想想看,Zygote 本身是一个 Java 虚拟机进程,它里面预加载了各种系统资源(比如常用类、主题、字体)。fork 出来的子进程会直接继承这些资源,所以新 App 进程启动速度会快很多。
// Zygote 收到请求后的核心逻辑(简化)
int pid = Zygote.forkAndSpecialize(…);
if (pid == 0) {
// 子进程:执行 ActivityThread.main()
RuntimeInit.applicationInit(…);
ActivityThread.main(…);
} else {
// 父进程:记录子进程 pid,后续通过 Binder 通信
}
这里有个细节:fork 之后,子进程会立即执行 ActivityThread.main()。也就是说,进程创建和 ActivityThread 的启动是“一气呵成”的。
避坑指南:我曾经在优化冷启动时,发现 Zygote 的预加载资源如果太多,会导致 fork 时间变长。后来我们精简了预加载的类列表,启动速度提升了 15% 左右。所以,如果你在做系统定制,记得关注 Zygote 的预加载策略。
ActivityThread 调度:主线程的 Looper 循环
新进程启动后,ActivityThread.main() 就是它的入口。这个方法会做三件关键的事:
- 创建主线程的 Looper——让主线程进入消息循环。
- 创建 ActivityThread 实例——并绑定到 Application 上。
- 调用 attach() 方法——通过 Binder 告诉 AMS:“我准备好了,可以给我派活儿了”。
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false); // 绑定到 AMS
Looper.loop(); // 进入消息循环
}
AMS 收到 attach 后,会通过 Binder 回调 scheduleLaunchActivity()。这个调用会往主线程的消息队列里发一个 LAUNCH_ACTIVITY 消息。主线程 Looper 取出消息后,最终调用 handleLaunchActivity(),然后通过反射创建 Activity 实例,调用 onCreate()。
说白了,整个调度就是“AMS 发指令 → 主线程 Handler 处理 → 反射创建 Activity”。
注意:主线程的 Looper 不能阻塞。一旦阻塞,AMS 发过来的消息就处理不了,会导致 ANR。我在项目中遇到过因为主线程做了太多 IO 操作,导致 Activity 启动慢了好几秒的情况。后来我们把 IO 挪到了子线程,问题就解决了。
核心流程总览
下面这张图,我把整个下半场的流程串起来了。你可以对照着看,会更清晰。
几个容易踩的坑
| 场景 | 原因 | 解决思路 |
|---|---|---|
| 冷启动黑屏 | Zygote fork 耗时,或主线程被阻塞 | 减少主线程初始化工作,使用启动优化 |
| 进程已存在但 Activity 没反应 | AMS 的 Binder 调用被卡住 | 检查主线程是否有耗时操作 |
| 多进程场景下 Activity 启动慢 | 跨进程 Binder 通信频繁 | 考虑使用 android:process 合理分配 |
总结一下:Activity 启动的下半场,核心就是“AMS 决策 → Zygote 创建进程 → ActivityThread 调度”。这三步环环相扣,任何一环出问题,都会导致启动失败或卡顿。我个人建议,你在做性能优化时,可以重点关注 Zygote 的 fork 耗时和主线程的消息处理速度。