Android 系统启动机制(九):system_server 已经运行,为什么还不能说 Android 启动完成?

开机诊断中,下面几条证据经常被当成同一件事:

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/base commit 45034f0663f960d9ee5fb0a101a4732b71f6e2f4。

一、至少有七个"启动完成"的观察层

观察层 常见证据 它最多证明什么
进程层 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 实例;没有观察到后续证据时,应先区分"明确失败"与"暂时不可观测"。

源码定位

相关推荐
mmsx1 小时前
Android 上的 AI 对话链路:流式响应、SSE 解析与三级降级
android·kotlin
阿俊-全栈开发2 小时前
LikeShop单商户高级版定时任务实现:订单超时关闭与自动确认收货
android·likeshop·likeshop开源商城
倔强的石头1063 小时前
【Transformer】Tokenization详解_BPE_WordPiece_SentencePiece
android·深度学习·transformer
haerapi3 小时前
仓库拣货路线不只靠最短边:S 形启发式的反例记录
android·开发语言·javascript
其实防守也摸鱼12 小时前
Fastjson 反序列化漏洞(JNDI注入)
android·数据库·安全·oracle·自动化·fastjson·反序列化
狗凯之家源码网14 小时前
微博图床 PHP 系统搭建与实战应用指南
android·开发语言·php
消失的旧时光-194315 小时前
(第一篇)JNI 多线程到底难在哪:从 Linux Thread 到 JNIEnv
android·jni·ndk
逗脑IDE18 小时前
零基础学ESP32:蓝牙控制舵机——手机一点,舵机就转!
android·智能手机
清霜之辰18 小时前
骁龙 8397 舱驾融合平台端侧大模型部署:7B 模型本地推理与 64GB 内存账本
android