当你手指轻触桌面上的微信图标,屏幕瞬间亮起熟悉的界面。这看似简单的"点击",背后却是一场跨越多个进程、涉及数十次 Binder 通信的精密接力赛。
AMS(ActivityManagerService) 是总指挥,Zygote 是兵工厂,Binder 是传令兵,而 ActivityThread 则是前线的执行者。
本篇我们将像侦探一样,追踪从"点击图标"到 onResume() 被调用的完整链路,揭开 Android 应用启动的神秘面纱。
第一步:Launcher 发起冲锋 ------ 请求启动
一切始于 Launcher (桌面应用)。当你点击图标时,Launcher 并不知道微信的具体代码在哪里,它只知道一个 Intent。
代码位置 :packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
ini
// Launcher 中的点击事件处理
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) tag;
Intent intent = info.intent;
// 【关键】启动 Activity
// 这里最终会调用 startActivitySafely -> startActivity
startActivity(intent);
}
}
底层动作 :
startActivity 最终通过 Binder 调用 AMS 的 startActivityAsUser 方法。
此时状态:Launcher 进程挂起(等待 AMS 裁决),控制权移交系统服务。
第二步:AMS 的决策 ------ 寻找或创建进程
AMS 收到请求后,开始进行复杂的逻辑判断。
代码位置 :frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
AMS 决策流程图:

关键逻辑伪代码:
scss
// AMS 内部简化逻辑
final int startActivityAsUser(...) {
// 1. 权限检查 (SELinux, Manifest 权限)
enforceNotIsolatedCaller("startActivity");
// 2. 解析 ResolveInfo (找到具体的 Activity 类名)
ResolveInfo rInfo = mAppIntents.resolveIntent(intent, ...);
// 3. 获取或创建进程记录 (ProcessRecord)
ProcessRecord targetApp = getProcessRecordLocked(targetPackage, targetUid);
if (targetApp == null || targetApp.thread == null) {
// 【核心】进程不存在,需要启动新进程
startProcessLocked(targetPackage, ...);
} else {
// 进程已存在,直接调度
targetApp.thread.scheduleLaunchActivity(...);
}
return START_SUCCESS;
}
第三步:Zygote 再次登场 ------ 孵化新进程
如果目标 App 未运行,AMS 会通过 Socket 向 Zygote 发送指令。
通信协议 :
AMS 写入 Socket 的数据包包含:uid, gid, gids, debugFlags, runtimeFlags, mountExternal, seInfo, niceName, fdsToClose, fdsToIgnore, isTopApp, disabledCompatChanges, pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppDataDirs, bindMountAppStorageDirs, args[] (其中包含 --activity-thread 等参数)。
Zygote 响应 :
Zygote 收到后,执行 forkAndSpecialize,生成新进程。新进程入口是 ActivityThread.main。
第四步:App 进程的自我修养 ------ ActivityThread.main
新进程诞生后,它首先是一个普通的 Java 进程,还没有"Application"的概念。它需要自己初始化。
代码位置 :frameworks/base/core/java/android/app/ActivityThread.java
arduino
// 文件:frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
// 1. 初始化主线程 Looper
Looper.prepareMainLooper();
// 2. 实例化 ActivityThread (当前进程的代表)
ActivityThread thread = new ActivityThread();
// 3. 【关键】绑定 Application
// 调用 AMS 的 attachApplication,注册自己
thread.attach(false, initialPid);
// 4. 进入消息循环
Looper.loop();
}
attach 方法的秘密:
java
private void attach(boolean system, long startSeq) {
// 获取 AMS 的代理对象 (IActivityManager)
final IActivityManager mgr = ActivityManager.getService();
try {
// 告诉 AMS:"我启动了,这是我的 Binder 代理"
// AMS 会保存这个代理,以便后续直接回调我们
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// 等待 AMS 下发指令 (如 bindApplication, launchActivity)
// 这些指令会通过 Binder 异步到达,放入 Handler 队列
}
Binder 魔法 :此时,AMS 手中持有了新进程的
IApplicationThread代理。以后 AMS 想启动 Activity,只需调用appThread.scheduleLaunchActivity(),请求就会瞬间发送到新进程的 Binder 线程池,再转发给主线程 Handler。
第五步:生命周期的大合唱 ------ 从 onCreate 到 onResume
AMS 确认进程就绪后,开始编排 Activity 的生命周期。这是一次典型的 RPC(远程过程调用) 舞蹈。
Activity 启动全流程

核心代码模拟 (ActivityThread.handleLaunchActivity) :
scss
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 1. 加载类
Class<?> clazz = r.loadClass(mClassLoader);
// 2. 实例化 Activity
Activity activity = (Activity) clazz.newInstance();
// 3. 创建 Context (包裹 Activity)
ContextImpl appContext = createBaseContextForActivity(r, activity);
// 4. 调用生命周期
Instrumentation inst = mInstrumentation;
// --- 用户代码执行点 ---
activity.attach(appContext, ...);
inst.callActivityOnCreate(activity, r.state); // 调用 onCreate
activity.performStart(); // 调用 onStart
inst.callActivityOnResume(activity); // 调用 onResume
// ----------------------
return activity;
}
关键点 :所有的生命周期回调(onCreate, onStart, onResume)都是由 Instrumentation 类辅助调用的。这也是为什么我们可以用测试框架(如 Espresso)拦截这些回调的原因。
🔹 第六步:窗口绘制 ------ 终于看到了界面
onResume 返回后,Activity 认为自己是"可见"的,但屏幕上可能还是黑的。真正的绘制由 WindowManagerService (WMS) 和 SurfaceFlinger 完成。
- Activity 创建
PhoneWindow,设置ContentView。 - ViewRootImpl 请求 WMS 添加窗口 (
addToDisplay)。 - WMS 分配一个 Surface (图形缓冲区)。
- App 进程 在 Surface 上绘制 UI (Canvas/OpenGL)。
- SurfaceFlinger 合成所有图层,输出到屏幕。
这个过程是异步的,通常在 onResume 之后的几帧内完成。
本篇小结
| 阶段 | 关键角色 | 核心动作 | 通信方式 |
|---|---|---|---|
| 发起 | Launcher | 构建 Intent,调用 startActivity | Binder (to AMS) |
| 决策 | AMS | 检查进程,决定 Fork 或复用 | 内存/锁 |
| 孵化 | Zygote | Fork 新进程,设置 UID/GID | Socket (from AMS) |
| 绑定 | ActivityThread | 调用 attach,注册到 AMS | Binder (to AMS) |
| 调度 | AMS | 发送 scheduleLaunchActivity | Binder (to App) |
| 执行 | ActivityThread | 反射创建 Activity,调用生命周期 | 本地调用 |
| 回调 | ActivityThread | 通知 AMS "Resumed" | Binder (to AMS) |
技术洞察:
- 双向 Binder:AMS 持有 App 的代理(用于下发指令),App 持有 AMS 的代理(用于上报状态)。
- Handler 机制:所有跨进程的生命周期回调,最终都变成了主线程 MessageQueue 中的一条消息,保证了串行执行。
- Instrumentation:它是 Hook 生命周期的关键入口,也是单元测试的基石。
下篇预告
App 启动了,界面显示了。但如果你快速滑动列表,或者打开相机,系统如何保证不卡顿?
- Choreographer 是如何同步 VSync 信号的?
- View 的测量、布局、绘制 三部曲究竟发生了什么?
- GPU 是如何介入渲染流程的?
下篇我们将深入 UI 渲染管线,揭秘 Android 流畅度背后的"帧"的秘密。
敬请期待:《60FPS 的秘密:UI 渲染管线与 Choreographer 的舞蹈》