Android 启动经常被画成一条箭头:
csharp
Kernel → init → Zygote → system_server → System Service → Launcher
这张图适合记顺序,却不足以解释源码。因为相邻两个名字之间,可能发生的是完全不同的变化:创建新进程、执行另一个程序、从 Native 进入 Java,或者只是在同一进程里创建一个服务对象。
这个系列不追完整 Bootloader、Kernel 和 Launcher 首帧,而是回答一个更窄、也更容易混乱的问题:
Android 的关键用户空间进程是谁创建的,它们各自建立了什么,核心系统能力又怎样从"进程存在"走到"可以使用"?
导读只负责建立地图。init 怎样 fork/exec、ZygoteInit 怎样进入、App socket 协议怎样解析、SystemServiceManager 怎样分发 phase,都留给对应正文用源码证明。
一、先划边界:这不是完整"开机流程"
系列从 Kernel 已经准备启动首个 Android 用户空间程序开始:
csharp
Kernel 已完成基本初始化
↓
init 成为 Android 用户空间 PID 1
系列在 Framework 已建立核心服务与启动阶段语义后收束:
arduino
system_server 已出生
↓
核心服务逐步创建、发布、ready
↓
Framework 进入可以继续建立用户体验的状态
以下内容只标位置,不在主线下钻:
- Boot ROM、Bootloader 与 Kernel 详细启动;
- 驱动 probe、完整 SELinux 策略与全部 HAL/Vendor Service;
- 单个 AMS、PMS、WMS 的内部业务;
- 普通 App 的完整 Activity 启动与 Launcher 首帧。
还要明确这条图的性质:Kernel → init → Zygote → system_server 是"进程出生与 Framework 能力"的投影,不是完整启动依赖 DAG。真实设备上,servicemanager/hwservicemanager、SurfaceFlinger、vold/installd、Native daemon、HAL/vendor service、文件系统与用户数据准备等多条前置路径并行推进,并在不同位置与 Framework 相交。主线适合回答"谁创建谁、能力走到哪",不能据此推断所有组件都严格串行。
固定源码基线: 全系列统一使用 Android 16 QPR2
android-16.0.0_r4。固定 tag 用于证明调用边界;设备上的 Zygote 组合、厂商 rc、模块版本和服务清单仍须以目标构建为准。
二、为什么一条启动链不够?

读图时从左到右分别问三个问题,不要把三列压成一条函数调用链。
主线 A:谁创建谁?
csharp
Kernel
└─ init
└─ Zygote
├─ system_server
└─ 后续 App 进程
这条线只讨论 Linux 进程家谱。它最需要守住的边界是:
csharp
init 建立 Zygote 进程
Zygote 直接创建 system_server
system_server 决定何时需要普通 App,实际孵化仍由 Zygote 体系完成
至于"建立"具体是 fork、exec 还是 fork 后的父子分支,正文再展开。
主线 B:Native 进程怎样开始执行 Java?
scss
app_process
↓
AndroidRuntime / ART / Framework JNI
↓
ZygoteInit.main()
这条线讨论运行环境和语言边界。Native 调用 Java main() 不等于又创建了一个进程;可执行文件、运行时和 Java 入口也不是三个并列进程。
主线 C:进程存在以后,能力怎样 ready?
SystemServer
↓
建立服务容器
↓
创建并发布服务
↓
推进生命周期阶段
↓
进入全局与用户级启动里程碑
这条线讨论的是同一进程内对象和能力状态。它不能被"system_server 已经在 ps 中"替代。
三条线的关系可以压成一句话:
进程出生线提供执行容器,运行环境线让容器能执行 Framework Java,能力就绪线再把 Java 对象组织成可被系统使用的服务。
三、先把文件、进程、类和服务分开
| 名字 | 它是什么 | 容易被误认成什么 |
|---|---|---|
/init、/system/bin/init |
Native 可执行文件 | "Android 所有代码的总 main" |
init |
PID 1 进程 | 普通 Native daemon 或 Java Service |
service zygote ... |
init rc 中的进程配置单元 | Binder Service、四大组件 Service |
app_process64 |
Native 可执行文件 | 与 Zygote 并列的另一个长期进程 |
| Zygote | app_process 进入特定模式后的进程角色 |
模板文件、Java 类名 |
ZygoteInit |
Java 启动类 | Linux 进程 |
system_server |
Linux 进程 | 单个系统服务 |
SystemServer |
system_server 中的 Java 启动组织类 | Binder Service 或 SystemService 子类 |
SystemServiceManager |
system_server 内的生命周期管理对象 | 全系统服务依赖求解器 |
| Binder Service | 发布给 ServiceManager 的 Binder 对象 | 必然独占一个进程 |
后文每次遇到关键名词,都可以先做这个检查:
当前说的是文件、进程、线程、Java 类、Java 对象、Binder 接口,还是配置项?
身份一旦错位,后面的"谁调用谁"通常也会跟着错。
四、为什么不能用一个瞬间定义"启动完成"?
这个系列使用四层完成语义:
| 层级 | 典型观察 | 只说明什么 |
|---|---|---|
| 进程存在 | Zygote、system_server PID 出现 | 对应进程在采样时存在 |
| 对象创建 | 某个 System Service 已构造/启动 | 服务生命周期已开始 |
| 能力可发现或 ready | Binder 名称、Boot Phase、专用 ready 状态 | 对应接口或依赖到达某个明确层级 |
| 用户路径可用 | 显示、SystemUI、Home、用户广播、目标功能 | 需要按具体用户体验继续验证 |
因此这些判断不能互换:
ini
进程存在 ≠ Java 主流程已走完
对象创建 ≠ Binder 接口已发布
接口可发现 ≠ 服务内部依赖 ready
Current phase=N ≠ phase N 的同步与异步工作都完成
sys.boot_completed=1 ≠ 所有用户、UI、HAL 和业务持续健康
第八、九篇会分别解释"服务怎样 ready"和"完成语义为什么分层"。导读到这里不提前给出 phase 顺序和属性写入细节。
五、遇到不同问题,应该从哪篇开始?
编号表示主题位置,不要求机械顺序通读。第一次阅读可先建立主线,再按兴趣补充性能模型和运行期进程工厂。
首读主线:先建立不会混乱的启动模型
- (一)init 到底是什么?
- (二)init.rc 怎样变成进程?
- (三)app_process 怎样成为 Zygote?
- (五)system_server 到底由谁创建?
- (七)SystemServer 怎样建立服务容器?
- (八)系统服务怎样从创建走到可用?
- (九)Android 什么时候才算启动完成?
这条路线刻意跳过两个深入机制,先回答"谁创建谁"和"能力怎样就绪"。
二刷深入:补齐性能模型和运行期进程工厂
第四篇解释"为什么不从零创建每个 Java 进程",第六篇解释"系统运行后怎样把普通 App 创建请求交给 Zygote"。它们重要,但不是第一次建立进程家谱的前置条件。
工程使用:先速查,再下钻命令手册
实战一只负责建立相邻里程碑区间;确认区间后,再进入完整命令手册,查日志、线程、权限和构建差异。
六、读完整个系列,应该能回答什么?
不是背下一串 main(),而是能稳定回答:
csharp
1. init、app_process、ZygoteInit、Zygote 分别属于哪一层?
2. init rc 的 service 与 Binder Service 有什么区别?
3. 哪些调用创建新进程,哪些只是同一进程继续执行?
4. system_server 为什么不是 init 直接 exec,也不是通过普通 App socket 请求创建自己?
5. system_server 与 SystemServer 有什么区别?
6. 服务对象创建、接口发布、Boot Phase 和业务 ready 有什么区别?
7. 一个 PID、一个服务名或一个 property 分别能证明到哪里?
8. 开机异常时,最后成立和第一个缺失的里程碑是什么?
如果这些问题能够被清楚回答,后续再进入任意源码函数,读者都有坐标:当前是谁、在哪个进程、是否发生线程或进程边界、这一行究竟创建了什么,以及它离"能力可用"还有多远。
首次阅读可从第一篇开始:init 到底是什么,为什么它不是普通 Native Service?