Android 系统如何从按下电源走到桌面
Android 系统如何从按下电源走到桌面
开机动画消失,不代表 Android 已经启动完成。它的职责是遮住后台还在忙的部分,并不负责宣布“启动完成”。
所以,动画还在转的时候,system_server 可能还没创建,也可能正在拉起系统服务,甚至 Home 已经发起了。反过来,桌面亮出来,也不意味着你的应用已经收到 BOOT_COMPLETED。这几个节点在画面上挨得很近,源码里走的却是不同的路径。
本文以 AOSP Android 14 android-14.0.0_r75 为基线。我们不打算把启动过程讲成一串名词,只追两件事:每一棒交给了谁,以及卡住的时候,怎样证明系统走到了哪一步。

图的最后一行没有画成一个节点,是有意为之的。从 system_server 往后,“桌面出现”和“开机完成”开始分岔。
四次交接,性质并不相同
启动链路里到处都是“下一个阶段开始了”,但底层动作并不一样。下面这张表列出的是真正发生的事,以及每次交接是否产生了新进程。
| 交接 | 真正发生的事 | 是否产生新进程 |
|---|---|---|
| Bootloader → Kernel | 装载镜像、准备启动参数,跳到内核入口 | 不属于 Android 进程的语义 |
Kernel → /init | 内核创建第一个用户空间进程并装入 /init | 是,init 成为 PID 1 |
init → Zygote | init 根据 rc 服务定义 fork/exec app_process | 是 |
Zygote → system_server | 主 Zygote 调用 forkSystemServer() | 是,子进程继续执行 Java 入口 |
如果把四段都写成“调用下一层”,文章读起来顺,却会抹掉排障时最需要的进程边界。
图中的 Home 指的是“当前用户解析出的桌面应用”,并不保证就是 AOSP 的 Launcher3。厂商桌面、用户选择的默认桌面,以及部分设备形态下的 Home 都可能不同。
第一棒接在 /init
按下电源以后,先运行的是芯片内固化的 Boot ROM,通常接着进入设备的 Bootloader。两者负责把硬件带到能装载系统的状态,完成启动目标选择、镜像校验和内核装载,再把控制权交给 Linux Kernel。
Boot ROM 和完整的 Bootloader 高度依赖 SoC 与设备,AOSP 并没有一份能代表所有手机的统一实现。这一段可以讲稳定的职责,但不能拿某个平台的函数调用链冒充 Android 的通用流程。
内核完成早期初始化后,才把接力棒交给 Android 用户空间。AOSP 中可以直接追踪的入口从 system/core/init/main.cpp 开始。init 不是一次性跑完的脚本:它依次处理 first stage、SELinux setup 和 second stage,然后解析 rc 文件,把 action、property trigger 和 service 放进自己的事件模型里。
system/core/init/init.cpp 的 SecondStageMain() 中会加载启动脚本,随后 early-init、init、late-init 等事件按顺序入队。到了 system/core/rootdir/init.rc 的 on zygote-start 事件块,Zygote 相关的服务才会被拉起:
on zygote-start
wait_for_prop odsign.verification.done 1
exec_start update_verifier
start statsd
start netd
start zygote
start zygote_secondary这段 rc 说明两件事。第一,zygote-start 是一个事件,并不是 init 执行到某一行时直接调用 Zygote。第二,Zygote 启动之前还有明确的门槛,比如这里要等待 odsign.verification.done=1。设备如果卡在这一段,只盯着 Java 层日志,基本找不到原因。
具体启动哪个 Zygote 的 rc 文件,取决于设备的 ABI 配置。以 system/core/rootdir/init.zygote64.rc 为例,服务定义的第一行是:
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote后面的 class main、socket zygote 等行是这个服务的属性配置。
start zygote 同样不是 init 在原进程里调用某个 Zygote 函数。Service::Start() 检查可执行文件、创建 socket 之后,通过 clone() 或 fork() 产生子进程。子进程进入 RunService(),设置 UID、GID、capability 和调度属性,最后对 /system/bin/app_process64 执行 execv。
因此,init.svc.zygote=running 只说明 init 把这个 service 记为运行态。它不能证明 ZygoteInit.main() 已经完成预加载,更不能证明 system_server 已经稳定进入服务启动阶段。
真正被执行的是 app_process64。--zygote 让它进入 Zygote 模式,--start-system-server 则只交给主 Zygote。系统服务进程并不是之后某个应用通过 Zygote socket 请求创建的,而是主 Zygote 启动时就按这个参数主动 fork 出来的。
app_process 是 Native 与 Java 之间的那座桥
frameworks/base/cmds/app_process/app_main.cpp 解析完参数后,会启动 Android Runtime,并把 Java 入口指定为:
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);这一步没有跨进程,也不是 Binder 调用。还是刚才那个 app_process 进程,只是运行环境从 Native 启动代码推进到了 ART 中的 ZygoteInit.main()。
ZygoteInit 会完成预加载、初始化 Zygote 状态并创建服务端 socket。参数中带有 start-system-server,所以主 Zygote 随后调用 forkSystemServer()。Android 14 的主干逻辑很直接:
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
if (r != null) {
r.run(); // 子进程:进入 system_server
return;
}
caller = zygoteServer.runSelectLoop(abiList); // 父进程:继续等待孵化请求forkSystemServer() 为子进程准备 UID 1000、GID、Linux capabilities、进程名,以及 Java 类名 com.android.server.SystemServer。fork 之后分成两路:
- 父进程拿到
null,留在 Zygote 的 socket 循环里,继续孵化应用进程; - 子进程关闭继承来的 Zygote 服务端 socket,经
handleSystemServerProcess()创建 class loader,最终找到并执行SystemServer.main()。
“Zygote 启动 SystemServer”背后并不是一次普通的方法调用,而是一次进程分叉。父子进程从这里开始拥有各自的执行路径。
这里还需要把线程分开看。fork 返回后,子进程中的当前线程继续经过 handleSystemServerProcess()、ZygoteInit.zygoteInit() 和 RuntimeInit.findStaticMain(),最终成为执行 SystemServer.main() 的主线程。与此同时,ZygoteInit.nativeZygoteInit() 会进入 AppRuntime::onZygoteInit(),为子进程启动 Binder 线程池。
所以 SystemServer 的 Java 主入口并不是被某条 Binder 线程回调出来的。主线程沿着 fork 返回的路径继续向前,Binder 线程池在旁边接收后续的 IPC。
SystemServer.main() 不是终点
SystemServer.main() 本身只有一行:
new SystemServer().run();真正的工作都在 run() 里。它会准备 system_server 主线程的 Looper,加载 android_servers Native 库,创建系统 Context 和 SystemServiceManager,然后按四组顺序启动服务:
startBootstrapServices(t);
startCoreServices(t);
startOtherServices(t);
startApexServices(t);Bootstrap、Core、Other、Apex 是启动代码的组织方式,不等于四个“完成状态”。服务真正感知系统进度,靠的是 SystemServiceManager.startBootPhase() 发出的 boot phase。Android 14 中几个容易遇到的阶段包括:
PHASE_ACTIVITY_MANAGER_READY(550):此后系统服务可以发送 Intent;PHASE_THIRD_PARTY_APPS_CAN_START(600):服务可以启动和绑定第三方应用,应用也可以向服务发起 Binder 调用;PHASE_BOOT_COMPLETED(1000):进入启动收尾阶段,Home 已经发起,系统服务可以开放面向用户的交互。
这些 phase 会让 SystemServiceManager 对已启动的服务逐个调用 onBootPhase(),它和 Bootstrap、Core、Other、Apex 四个方法并不是一一对应的。一个服务已经构造,甚至已经注册到 ServiceManager,也不代表它依赖的 phase 已经到达。
startOtherServices() 后半段会调用 ActivityManagerService.systemReady(),之后由回调推进 550 和 600。编排全部结束后,system_server 主线程进入 Looper.loop(),开始长期处理消息。SystemServer 并非运行完就退出的启动程序,它本身就是系统生命周期中常驻的核心进程。
另外,入口编排主要发生在 system_server 主线程,但源码也会把适合并行的初始化交给 SystemServerInitThreadPool。因此,“所有服务都在一条线程上串行启动”同样不准确。
桌面是什么时候被拉起来的
ActivityManagerService.systemReady() 会设置 mSystemReady、清理启动期的进程状态,并令 mProcessesReady = true。随后执行 SystemServer 传入的回调,推进前面提到的 boot phase。
在标准的、非 Headless System User 的手机路径中,AMS 接着设置 mBooting = true,调用链如下:
ActivityManagerService.systemReady()
→ ActivityTaskManagerInternal.startHomeOnAllDisplays()
→ RootWindowContainer.startHomeOnAllDisplays()
→ startHomeOnTaskDisplayArea()
→ resolveHomeActivity()
→ ActivityStartController.startHomeActivity()这一段做的是解析并发起 Home Activity 的启动,不是桌面首帧已经显示。之后还要经过应用进程准备、Activity 生命周期、窗口创建、绘制和 Surface 合成。有些 Android 14 配置还会在主题 overlay 尚未准备好时延后 Home。
所以,日志里出现 startHomeOnAllDisplays,只能说明启动请求走到了这里。要证明用户真的看到了桌面,还得继续看 resumed Activity、焦点窗口和显示链的状态。
开机动画退出之后,才露出被挡住的现场
开机动画并不是由 SystemServer 拉起的。frameworks/base/cmds/bootanimation/bootanim.rc 把它定义为 disabled、oneshot 的 Native 服务。SurfaceFlinger 初始化时,StartPropertySetThread 通过 ctl.start=bootanim 请 init 启动它,并把 service.bootanim.exit 清零。因此,动画可以早于 Java Framework 出现在屏幕上。
正常冷启动的收尾也不是从 ActivityManagerService.ensureBootCompleted() 开始的。主路径是:Home 等 resumed Activity 进入 idle 后,由 ActivityTaskSupervisor.activityIdleInternal() 检查所有 resumed Activity 是否都已 idle,等待太久时还有 timeout 兜底。条件满足后,checkFinishBootingLocked() 通过 ActivityTaskManagerService.postFinishBooting() 向 Handler 投递两件事:
ActivityTaskSupervisor.activityIdleInternal()
→ checkFinishBootingLocked()
→ ActivityTaskManagerService.postFinishBooting()
├─ ActivityManagerService.finishBooting()
└─ WindowManagerService.enableScreenAfterBoot()finishBooting() 如果发现 mBootAnimationComplete 仍为 false,只会设置 mCallFinishBooting=true,先把收尾挂起。另一边,WMS 进入 performEnableScreen(),它会检查窗口策略是否允许撤下动画,并等待启动所需的 system-decor 窗口完成绘制。条件不满足就继续等,直到 30 秒的 boot timeout 到期,强制打开显示。
条件满足后,WMS 设置 service.bootanim.exit=1。这只是退出请求,动画进程不一定已经停止。WMS 随后轮询 init 记录的 bootanim 服务状态,确认进程退出后调用 SurfaceControl.bootFinished(),把 mDisplayEnabled 置为 true、恢复输入分发,再回调 ActivityManagerService.bootAnimationComplete()。刚才挂起的 finishBooting() 就是在这里继续的。
WMS.performEnableScreen()
→ 等待 policy 与 system-decor 窗口
→ setprop service.bootanim.exit 1
→ 等待 init.svc.bootanim=stopped
→ SurfaceControl.bootFinished()
→ 打开显示与输入分发
→ AMS.bootAnimationComplete()
→ AMS.finishBooting() 继续收尾ensureBootCompleted() 在系统错误界面等路径上仍然会兜底,但它不能替代上面的正常调用链。
开机动画会误导人,原因在这里:bootanimation 是独立的 Native 进程,可以在 Java Framework 尚未就绪时播放,也可能在 Home 已经启动、窗口仍不满足显示条件时继续挡在前面。动画负责遮挡,WMS 决定它什么时候退场。

只有当 AMS 同时等到 Framework 的 booted 条件和开机动画完成的信号,finishBooting() 才会继续执行:
- 向系统服务发送
PHASE_BOOT_COMPLETED; - 写入
sys.boot_completed=1和dev.bootcomplete=1; - 调用
UserController.onBootComplete(),允许用户解锁流程继续。
第三步需要单独说明。sys.boot_completed=1 是设备级的 Framework 里程碑,BOOT_COMPLETED 则是按用户派发的广播。UserController 会先让用户从 BOOTING 进入 RUNNING_LOCKED,需要时发送 LOCKED_BOOT_COMPLETED;只有该用户的 CE 存储已经解锁,才会在 finishUserUnlockedCompleted() 中投递 BOOT_COMPLETED。
在多用户设备上,即使当前用户的桌面已经可见、sys.boot_completed 也已经是 1,仍然不能推出其他已启动但尚未解锁的用户已经收到 BOOT_COMPLETED。就算只看当前用户,属性写入、广播入队和接收者执行也不是同一个时刻发生的。设备级启动完成,并不代表每个用户、每个接收者都在同一时刻完成了自己的工作。
排障时先找最后一个成功的节点

遇到开机问题,先采集一份证据快照,比直接对整份 logcat 做 grep 更有效:
# init 管理的 Native 服务状态
adb shell getprop init.svc.surfaceflinger
adb shell getprop init.svc.zygote
adb shell getprop init.svc.bootanim
adb shell getprop service.bootanim.exit
# 进程及父子关系;不要把 Zygote 名称写死为 zygote64
adb shell ps -A -o USER,PID,PPID,NAME \
| grep -E 'zygote|system_server|surfaceflinger|bootanim'
# SystemServer 是否重建过,以及服务推进到哪个 phase
adb shell getprop sys.system_server.start_count
adb shell dumpsys system_server_dumper --name SystemServer
adb shell dumpsys system_server_dumper --name SystemServiceManager
# 当前解析出的 Home、Activity 与窗口焦点
adb shell cmd package resolve-activity --brief --user current \
-a android.intent.action.MAIN \
-c android.intent.category.HOME
adb shell dumpsys activity activities | grep -E 'mResumedActivity|topResumedActivity'
adb shell dumpsys window displays | grep -E 'mCurrentFocus|mFocusedApp'
# 设备级属性与用户状态
adb shell getprop sys.boot_completed
adb shell am get-current-user
adb shell dumpsys activity users
# AOSP 已埋好的启动事件时间线
adb logcat -b events -d \
| grep -E 'boot_progress_(system_run|pms_ready|ams_ready|enable_screen)|wm_boot_animation_done'service.bootanim.exit=1 只表示退出已经被请求。init.svc.bootanim=stopped 才是 init 观察到的动画进程停止。SystemServiceManager 的 Current phase 如果仍小于 550,问题还在系统服务的启动主干上;推进到 600 而 Home 始终没有出现,就应该转向 Home 解析、应用启动和窗口链。dumpsys 依赖 system_server 的响应,命令卡住本身也是证据,不要一直等下去,更不要把它误判成“没有输出”。
下面的表格列出每类证据能证明什么、又不能证明什么:
| 证据 | 能证明什么 | 还不能证明什么 |
|---|---|---|
init.svc.zygote=running | init 认为 Zygote 服务正在运行 | Zygote 预加载成功,system_server 已经稳定 |
存在 system_server PID | 系统进程此刻存在 | 它没有反复崩溃重启,服务也未必已经 ready |
sys.system_server.start_count>1 | 属性记录的 SystemServer 构造次数大于 1,本次启动会被视为运行时重启 | 第一次退出是哪个服务导致的 |
service.bootanim.exit=1 | Framework 已请求动画退出 | bootanimation 进程已经停止 |
init.svc.bootanim=stopped | init 观察到动画服务已停止 | Home 一定正常,应用广播一定送达 |
Current phase=550/600/1000 | SystemServiceManager 已推进到对应的生命周期阶段 | Home 首帧或应用广播已经完成 |
sys.boot_completed=1 | AMS 已经走过设备级 finishBooting() 的关键点 | 某个用户已解锁,某个应用已收到广播 |
用户为 RUNNING_LOCKED / RUNNING_UNLOCKED | 该用户 DE/CE 数据可用性推进到哪一步 | 目标 Receiver 已经执行完毕 |
| resumed Activity 与 focused window | Activity 和窗口焦点已经推进 | 首帧一定已经被用户看到 |
然后按“最后一个成功的节点”来切分问题:
- Zygote 没有起来:查看
zygote-start的前置属性、rc 服务状态,以及 Zygote 的第一条 fatal; system_server的 PID 或start_count持续变化:找每次重启之前最早出现的Failure starting system services、fatal exception 或 watchdog 证据,不要被重启后的连锁日志带偏;system_server稳定,但sys.boot_completed仍为空:沿着 AMS 的 booted 条件、WMS 等待窗口和 bootanimation 退出的路径排查;sys.boot_completed=1,桌面仍然异常:转向 Home 解析、Activity 启动、窗口焦点和显示合成;- 桌面正常,应用没有收到广播:检查当前用户、CE 解锁状态、接收者声明和包自身的状态,不必再回头怀疑 Bootloader。
命令输出只是某一时刻的快照,logcat 也可能轮转。可靠的做法是把属性、PID、窗口状态和同一时间段的日志对齐在一起看。
沿源码复走一遍
如果要在 Android 14 源码里自己复走一遍,建议按下面的顺序下断点或做静态阅读:
| 链路节点 | Android 14 r75 源码 |
|---|---|
| init second stage 与事件队列 | SecondStageMain()、ActionManager::ExecuteOneCommand() |
| Zygote trigger 与 service 定义 | init.rc 中的 on zygote-start、init.zygote64.rc |
| init fork/exec service | Service::Start() |
app_process 进入 Java | app_main.cpp 中的 runtime.start() |
主 Zygote fork system_server | ZygoteInit.main() 与 forkSystemServer() |
| SystemServer 服务编排 | SystemServer.run() |
| boot phase | SystemService、SystemServiceManager.startBootPhase() |
| AMS ready 与设备启动收尾 | ActivityManagerService.systemReady()、finishBooting() |
| Home 解析与启动 | RootWindowContainer.startHomeOnTaskDisplayArea() |
| Activity idle 触发收尾 | ActivityTaskSupervisor.checkFinishBootingLocked() |
| WMS 停动画并打开显示 | WindowManagerService.performEnableScreen() |
| SurfaceFlinger 启动动画 | StartPropertySetThread::threadLoop() |
| 按用户发送开机广播 | UserController.onBootComplete()、finishUserUnlockedCompleted() |
Boot ROM 和 Bootloader 的实现需要回到具体芯片与设备的仓库去看。AOSP 能稳定展开的主线,是从 Kernel 交给 /init 之后的部分。
后续专题会分别展开 Bootloader、Kernel、init、Zygote 和 SystemServer 的细节。
下次遇到“卡在开机动画”,先别急着把问题归给动画。写下最后一个有证据成功的节点:zygote 还在不在,system_server 稳不稳定,Home 有没有发起,WMS 在等什么,用户又解锁到哪一步。问题未必会立刻消失,但搜索范围会从“整套 Android”缩小到一小段真正值得读的源码。
下一篇将讨论:一个应用进程里有多少线程,谁决定进程生死。