开机诊断中,下面几条证据经常被当成同一件事:
ini
system_server 进程存在;
activity 服务能查到;
sys.boot_completed=1;
开机动画消失;
Launcher 已显示;
App 收到 BOOT_COMPLETED。
它们分别属于进程、服务发现、全局 Framework phase、显示、用户界面和广播处理层,时间上有关联,却不是一个原子事件;若 system_server 发生 runtime restart,它们甚至可能来自不同 Framework generation。
先建立判断底线:
system_server 出生只表示 Framework 容器开始运行。之后还要创建/发布服务,推进 Boot Phase,允许第三方进程启动,等待窗口与开机动画满足退出条件,进入
PHASE_BOOT_COMPLETED,设置全局属性,再按用户状态发送LOCKED_BOOT_COMPLETED/BOOT_COMPLETED。任何单点证据都只能证明对应里程碑,不能替代端到端功能健康检查。
本文按 Android 16 固定源码还原"完成"的多个语义,不展开 Launcher 首帧和完整多用户状态机。
源码基线: Android 16 QPR2
android-16.0.0_r4,frameworks/basecommit45034f0663f960d9ee5fb0a101a4732b71f6e2f4。
一、至少有七个"启动完成"的观察层

| 观察层 | 常见证据 | 它最多证明什么 |
|---|---|---|
| 进程层 | ps 中有 system_server |
Zygote 已 fork 子进程,且采样时进程仍存在 |
| 服务层 | service check activity |
activity Binder 名称可发现 |
| Framework phase | SystemServiceManager Current phase | Manager 已进入对应 phase;是否仍在回调要看后续里程碑 |
| 显示层 | bootanimation 退出、display enabled | WMS 满足显示启用路径的一组条件 |
| 全局属性层 | sys.boot_completed=1 |
property service 当前保存这个全局值 |
| 用户层 | 某用户 BOOT_COMPLETED 处理完成 | 该用户对应广播链到达结果回调 |
| 体验层 | Launcher 可操作、关键功能正常 | 需要针对 UI/功能做端到端验证 |
"完成"随观察者而变。工程上不应争论唯一时刻,而应先说明自己要判断哪一个观察层。
观察"完成"之前,还要先回答:这些证据来自哪一次 Kernel boot、哪一个 system_server 实例?本文用 KernelBoot 标识内核启动,用 PID 与 /proc/<pid>/stat start_ticks 区分 system_server 实例;PPID 只描述采样时的父子关系,不参与实例身份。
当前值不等于当前实例刚刚写入;要把 phase、property 和广播归到同一次 Framework 启动,必须观察同代跃迁,或取得带当前 PID/trace 时间关联的事件。
Current phase 的更新与回调顺序以 第八篇为唯一机制说明;本篇只沿用它的证据边界:Current phase=N 表示已经进入 N,不单独证明该阶段全部工作完成。
二、system_server 存在,后面还差多远?
system_server 出现在 ps 中时,最少只能推断:
perl
主 Zygote 曾经 fork 成功;
目标 PID 在采样瞬间未退出;
进程名已设置为 system_server。
它不能直接证明:
scss
SystemServer.main() 已执行到哪一行;
System Context 已建立;
AMS/PMS/WMS 构造成功;
Binder 名称已发布;
主线程未死锁;
Boot Phase 已到 600/1000;
用户界面可交互。
甚至"PID 一直存在"也可能是卡死,而不是健康。
三、服务可发现,也不等于服务 ready
ServiceManager.addService("activity", binder) 成功后,service check activity 可以显示 found。此时 Binder 驱动里有了可查询句柄,但 AMS 仍可能在:
scss
等待 PMS/用户/进程列表初始化;
尚未进入 systemReady();
尚未允许第三方 App 启动;
主线程或依赖服务阻塞;
只允许部分早期接口工作。
所以诊断时应把"查得到"升级为"最小调用成功",再升级为"目标业务链成功"。例如 activity service 存在,不足以证明 Home Activity 已完成绘制。
四、Phase 550 与 600:Framework 开始面向应用,但还不是 1000
SystemServer 在 mActivityManagerService.systemReady() 回调中推进:
ini
PHASE_ACTIVITY_MANAGER_READY = 550
→ 可以广播 Intent 等 AMS ready 能力
PHASE_THIRD_PARTY_APPS_CAN_START = 600
→ 服务可以启动/绑定第三方 App
固定源码位置见 SystemServer.startOtherServices()。这条链的含义是:system_server 早已存在,核心服务继续 ready,AMS 推进 phase 550,后续准备完成后再进入 phase 600。到 600 后,第三方进程具备开始运行的系统条件;但开机动画、显示窗口、用户解锁与 boot-completed 广播仍可能在后面。
"App 可以开始"与"所有 App 已启动"显然也不是一个意思。
五、开机动画退出是谁决定的?
WindowManagerService 的 performEnableScreen() 体现了显示路径的一组门槛:
css
系统已 booted 或需要显示 boot message;
policy 允许 dismiss boot animation;
需要等待的 system decor windows 已绘制;
向 bootanimation 发退出请求;
等待 bootanimation 实际完成;
通知 SurfaceFlinger bootFinished;
标记 display enabled;
启用 input dispatch;
回调 AMS.bootAnimationComplete()。
events buffer 中的 boot_progress_enable_screen 容易被名字误导。AOSP 对它的定义是"ActivityManagerService 调用 enableScreenAfterBoot()",只说明 AMS 已经发起启屏流程;WMS 后面仍可能等待窗口、动画或显示条件,因此它不等于 mDisplayEnabled=true。
1. service.bootanim.exit=1 只是退出请求
WMS 设置:
ini
service.bootanim.exit=1
源码注释明确说,不再直接杀掉进程,而是请求 bootanimation 自己选择停止位置。紧接着 WMS 还会检查动画是否真的完成;没完成就 return,稍后再检查。
因此:
ini
service.bootanim.exit=1
≠
bootanimation 进程已经退出
≠
屏幕已经启用
因此实战里的 M8 保留源码强条件,但现场证据分级:L1 用同一 generation 的 trace/dump/log 直接覆盖 animation done、display enabled、input enable/dispatch;L2 用 wm_boot_animation_done、display 状态和实际交互/输入组成代理;L3 只有退出请求;L4 为不可观测。只有 L1 可称直接证明,五分钟速查允许明确标注 L2;单独观察到退出请求不能把 M8 写成已成立。
2. 动画结束也不等于 Launcher 每项功能健康
WMS 满足的是显示切换与输入启用路径。Launcher 可能已绘制基本窗口但某个 provider、桌面数据、SystemUI 模块或硬件能力仍异常。视觉上"离开开机动画"是重要里程碑,却不是全系统健康证明;目标验收必须另写成 A[Home]、A[SystemUI] 或其他预先定义的目标,而不是把它接成 M9 的下一阶段,更不能笼统写"整机健康"。
六、ActivityManagerService.finishBooting() 为什么还要等动画?
ActivityManagerService.finishBooting() 开头检查 mBootAnimationComplete:
scss
若动画未完成:
mCallFinishBooting = true
return
动画完成回调到来:
bootAnimationComplete()
再次调用 finishBooting()
这把 Framework 全局 boot-completed 阶段与 WMS 的显示/动画完成里程碑连接起来。
但要注意方向:这不是说"所有 App 收到 BOOT_COMPLETED 后动画才消失",而是当前固定实现先等待 WMS 报告动画完成,之后才继续 phase 1000 与用户 boot-completed 流程。
七、Android 16 中 phase 1000、属性和用户广播的精确顺序
动画门槛通过后,finishBooting() 的关键顺序是:
ini
1. ZYGOTE_PROCESS.bootCompleted()
2. VMRuntime.bootCompleted()
3. 提交存储 checkpoint 成功状态
4. SystemServiceManager.startBootPhase(PHASE_BOOT_COMPLETED = 1000)
5. 启动此前 hold 的进程等收尾动作
6. set sys.boot_completed = 1
7. set dev.bootcomplete = 1
8. UserController.onBootComplete(...)
9. 各已启动用户继续解锁与 BOOT_COMPLETED 广播流程
这是理解几个观测点的关键:
yaml
phase 1000 先于属性设置;
属性设置先于 UserController 启动用户级完成流程;
用户广播又可能异步排队、逐接收者执行。
所以读取到 sys.boot_completed=1 时,不能宣称"所有 BOOT_COMPLETED Receiver 都执行完了"。固定源码反而清楚显示,调用用户完成流程发生在属性设置之后。
实机对照:把关键事件绑定同一 FrameworkGeneration
某 Android 16 userdebug 设备在主动重启后的连续采样中,KernelBoot 与 FrameworkGeneration 始终不变。只保留与本节结论直接相关的顺序,可以归纳为:
yaml
wm_boot_animation_done(system_server 的非主线程 T_worker)
→ Starting phase 1000(同一个 T_worker)
→ sys.boot_completed 从非 1 进入 1
→ 稍后才开始用户级 BOOT_COMPLETED 广播
这段证据既验证了 phase 1000 先于属性跃迁、属性跃迁先于用户级完成广播,也暴露了一个容易被文档契约掩盖的线程边界:这一轮 phase 1000 的 T_worker != PID,并非 system_server 主线程。原因不是 Boot Phase 自带异步调度,而是 WMS 显示完成到 AMS finishBooting() 的同步调用链没有强制切回主 Looper;第八篇的线程边界专门解释了"文档契约、Manager 实现、实际调用点"三者的区别。
八、sys.boot_completed=1 究竟能证明什么?
在一次正常的首次启动路径中,从属性由非 1 变为 1 可以强力说明:
css
AMS 已进入 finishBooting;
boot animation completion gate 已通过;
PHASE_BOOT_COMPLETED 的同步分发已经返回;
Framework 到达全局 boot-completed 属性里程碑。
这里的关键不是"同时读到两个当前值",而是观察到跃迁并把它绑定当前实例。严格判断至少需要:
bash
当前 FrameworkGeneration 的 M7@1000
AND
同一 FrameworkGeneration 内 sys.boot_completed 从非 1 进入 1
或带当前 PID/trace 时间范围的等价关联证据
原因在于两条证据来自不同所有者:Current phase 是 system_server 进程内对象的 dump,sys.boot_completed 当前值来自独立的 property service。一次读取到 phase >= 1000 和 property = 1,只能得到一致快照;若要证明先后与写入归属,还需要同一实例内的跃迁或等价时序证据。
但事后读取当前值仍存在边界:
1. 它不是健康探针
属性不会持续轮询 AMS、WMS、Launcher、SystemUI、网络和硬件。设置之后,某个服务可以崩溃、死锁或功能退化,值仍可能保持 1。
2. 它不是"当前 system_server 实例的时间戳"
Android runtime restart 时,内核和 property service 未必重启;旧值可能继续存在。只读取 1 无法证明当前 system_server 刚刚完整走过所有阶段,也无法区分冷启动和 Framework 重启。
例如 generation A 到过 phase 1000 并设置属性,随后 generation B 只到 phase 520。A 的旧日志、B 的当前 dump 和遗留属性 1 不能合取;对 B 而言,M9 没有成立。关键是先区分实例,再组合证据;继续增加"完成"的别名解决不了归属问题。
3. 它不是多用户完成状态
一个全局属性无法表达:用户 0 是否解锁、工作资料是否启动、次用户是否完成 PRE_BOOT、每个包的广播是否处理完。
4. 它可以触发新的工作
init rc 可以监听 property:sys.boot_completed=1 再启动或调整其他服务。换句话说,这个值有时是"下一阶段开始"的触发器,而不是所有后续工作的终点。
关于
dev.bootcomplete: Android 16 的finishBooting()在设置sys.boot_completed=1后紧邻设置它。两者在固定实现中是相邻里程碑,但不同脚本和厂商组件可能监听不同属性;若观测到分叉,应检查属性写入日志、runtime restart 与厂商修改,不能只凭名称推断等价契约。
九、用户级完成为什么更复杂?
UserController 管理每个用户独立的启动、解锁和广播状态。
1. LOCKED_BOOT_COMPLETED
支持 Direct Boot 的组件可以在 credential-encrypted 存储尚未解锁时接收锁定启动完成广播。这是"设备保护存储可用"的用户里程碑,不等于用户凭据存储已解锁。
2. PRE_BOOT_COMPLETED
系统升级或 fingerprint 变化时,用户启动流程可能先发送 PRE_BOOT 广播,并明确阻止 BOOT_COMPLETED 越过尚未完成的 PRE_BOOT receivers,避免并发造成问题。
3. BOOT_COMPLETED
UserController 创建用户级 ACTION_BOOT_COMPLETED,把发送工作提交到线程/广播系统。最终结果回调记录"Finished processing BOOT_COMPLETED for uX"。
因此要区分:
准备发送;
广播已入队;
某个 receiver 已收到;
该用户整条 ordered broadcast 已处理完。
另外,stopped package、后台限制、私有空间、用户尚未启动等规则会改变具体接收时机;"全局开机完成"不保证每个安装包立刻执行 Receiver。
十、把时间线按"能开始什么"重写
| 里程碑 | 系统新增能力 | 仍可能未完成 |
|---|---|---|
| system_server PID 出现 | Framework Java 容器开始执行 | Context、服务、phase |
| 核心 Binder 服务发布 | 客户端可发现部分能力 | 服务内部 ready |
| Phase 500 | 服务可依赖核心 Power/PMS 等 | AMS/三方 App/显示 |
| Phase 550 | AMS ready、可广播 | 三方 App全面启动条件 |
| Phase 600 | 可启动/绑定第三方 App | 动画、用户广播 |
| display enabled / animation complete | 屏幕和输入进入用户显示路径 | phase 1000、用户广播处理 |
| Phase 1000 | SystemService 获得全局完成回调 | 属性与用户流程的后续步骤 |
| sys.boot_completed=1 | 全局 property milestone | 每用户/每 Receiver、持续健康 |
| 用户广播完成 | 该用户启动广播链结束 | 所有业务和硬件功能健康 |
这比一句"Android 启动完成了"更适合定位问题。
十一、两个典型反例
反例 1:system_server 存在,卡在 PMS 初始化
进程证据成立,服务与 phase 证据不成立。此时 sys.boot_completed 不会按正常首次启动路径切到 1。
反例 2:sys.boot_completed=1,SystemUI 随后崩溃循环
全局属性仍为 1,用户看到的状态却明显异常。这正说明属性不是持续健康探针。
其余"服务已发布但尚未 ready""主用户完成但 profile 未解锁"等现场组合,统一放到实战手册,避免机制正文重复展开诊断表。
十二、源码证据边界
| 结论 | 固定源码能否证明 |
|---|---|
| phase 1000 发生在设置两个 boot property 之前 | 能 |
| 用户完成流程从属性设置后开始 | 能 |
| 每个 Receiver 都在 property 变 1 前执行完 | 不能,而且顺序反证 |
service.bootanim.exit=1 就是动画已退出 |
不能,而且 WMS 后续仍显式等待 |
sys.boot_completed=1 代表此刻所有功能健康 |
不能 |
| Phase 600 后允许启动/绑定第三方 App | 能,SystemService phase 契约直接说明 |
| 所有厂商设备的 UI 首帧时序完全一致 | 不能,产品、窗口与模块行为会变化 |
十三、启动链结束了,健康检查才刚开始
Android 的启动观察由有关联但非原子的多条轨迹组成:全局脊柱包含 system_server 出生、phase 500/550/600、显示与动画完成、phase 1000 和全局属性;具体服务各自在不同时点发布并 ready;每用户广播和目标体验又有自己的进度。诊断时必须说清"哪一层、哪条轴完成",不能拿一个 PID、一个 Binder 名称或一个 property 代替整机健康。
至此,九篇源码主线已经从 init PID 1 走到用户级完成语义。工程诊断会把这些结论转成三类检查:沿全局启动脊柱确认进度,按具体服务检查发布与 ready,再为 Home、SystemUI 或网络等目标单独验收。所有观察都必须先确认来自同一次 Kernel boot 和同一个 system_server 实例;没有观察到后续证据时,应先区分"明确失败"与"暂时不可观测"。
源码定位
SystemServer.startOtherServices():phase 550 与 600。WindowManagerService.performEnableScreen():窗口、动画、显示和输入门槛。ActivityManagerService.finishBooting():动画 gate、phase 1000、属性与用户流程顺序。UserController:PRE_BOOT 与用户 BOOT_COMPLETED。- AOSP 启动时间官方说明:boot progress 事件与启动性能观测概览。