SystemServer 初始化阶段划分
我常常跟团队里的新人说,别把 SystemServer 想成一个黑盒。它从 Zygote 里 fork 出来,再到跑起上百个服务,每个阶段都有明确的边界。你只有把边界划清楚了,埋点才能精准——不然你根本不知道这个耗时到底花在哪儿了。
1. 第一阶段:Zygote 孵化 SystemServer
Zygote 是 Android 的“细胞分裂器”。所有应用进程包括 SystemServer,都是从它 fork 出来的。但我观察过很多项目,大家往往只盯着 SystemServer 的 main(),却忽略了 fork 之前的准备工作。
具体流程是这样的:Zygote 在 ZygoteInit.main() 里完成预加载资源,然后监听 socket。当它收到“启动 SystemServer”的指令时,就会调用 forkSystemServer()。这个 fork 本身是一个系统调用,耗时不长,但之后会立即执行 RuntimeInit.commonInit() 和 nativeZygoteInit()。
埋点建议:我在项目里习惯在 ZygoteInit.main() 末尾打一个标记,再在 RuntimeInit.commonInit() 之后打第二个标记。两者之差就是“fork + 预初始化”的耗时。别小看这几毫秒,有些定制 ROM 里它会膨胀到 20ms 以上。
| 子阶段 | 关键代码入口 | 埋点时机 | 注意 |
|---|---|---|---|
| 监听 socket | ZygoteInit.runSelectLoop() | 收到 "start-system-server" 消息 | 要排除其他应用 fork 的干扰 |
| fork 进程 | Zygote.forkSystemServer() | fork() 前后各打一个点 | 子进程和父进程都会执行后面的代码,要区分 pid |
| 预初始化 | RuntimeInit.commonInit() | 该函数入口 | 这里会设置 log、时区、默认异常处理器 |
| native 初始化 | nativeZygoteInit() | 该函数内部 | 会触发 AndroidRuntime::startReg,注册 JNI 函数 |
forkSystemServer() 之后打点,结果发现父进程和子进程同时记录,导致数据翻倍。后来加了个 if (pid == 0) 的判断——只有在子进程里才打点,才得到干净的数据。
2. 第二阶段:SystemServer 进程启动
子进程诞生后,它立刻执行的是 SystemServer.main()。嗯,这里有不少开发者以为 main() 就是入口,其实真正的主循环在 SystemServer.run() 里。main() 只做了三件事:创建主线程 Looper、加载 libandroid_servers.so、调用 run()。
我个人习惯在 SystemServer.main() 的第一行打一个“进程入口”标记,然后在 run() 内部再打一个“准备启动服务”标记。这样就能分离出“进程创建开销”和“服务启动前的准备工作”。
run() 方法内部会干几件大事:
- 设置语言和时区:通过
ActivityThread.systemMain()初始化主线程。 - 加载系统库:比如
System.loadLibrary("android_servers")。 - 创建系统上下文:构建
SystemServiceManager和PackageManager的早期服务。 - 启动引导服务:这是第三阶段的内容。
埋点最容易被忽略的是 Looper.prepareMainLooper() 之后的那个瞬间。我建议在 Looper.loop() 之前再打一个点——这样可以确认整个初始化流程的完成时间,因为 loop() 一旦开始就进入事件循环,不会返回。
android.os.SystemClock.elapsedRealtime() 来打时间戳,它不受系统时间调整影响,适合做性能对比。我在项目中一直用这个 API。
3. 第三阶段:服务批量启动
这是 SystemServer 最“热闹”的阶段。run() 方法里会依次调用三个批量启动函数:
- startBootstrapServices():引导服务,比如
PackageManagerService、ActivityManagerService、PowerManagerService。这些是其他服务的依赖基础。 - startCoreServices():核心服务,比如
BatteryService、UsageStatsService、WebViewUpdateService。 - startOtherServices():其他服务,这里面有上百个,包括
WifiService、BluetoothService、CameraService等等。
你看,Android 团队把服务分了三批,每批内部还有顺序。我在做埋点的时候,会在每个批次的入口和出口各打一个标记,然后在内核日志里看到类似这样的输出:
2025-04-01 08:15:30.123 [SystemServer] startBootstrapServices 开始
2025-04-01 08:15:30.124 [SystemServer] Installer 启动完成
2025-04-01 08:15:30.456 [SystemServer] PackageManager 启动完成
2025-04-01 08:15:30.678 [SystemServer] ActivityManager 启动完成
2025-04-01 08:15:30.680 [SystemServer] startBootstrapServices 结束,耗时 557ms
为什么要分批次? 说白了就是依赖关系。比如 PackageManager 必须在 Installer 之后启动,否则即使起了也没法安装应用。我见过一些第三方 ROM 因为调换了顺序,导致开机循环重启——这就是不尊重服务依赖的后果。
4. 整体流程图
把三个阶段串起来,就得到下面这张图。注意节点之间的顺序和箭头指向,每个方框都代表了你可以埋点的位置。
5. 关键时间点汇总
最后我列一个对照表,方便你在代码里直接定位埋点位置。这些是我反复验证过的“黄金埋点”。
| 时间点名称 | 代码位置(Android 14 AOSP 参考) | 说明 |
|---|---|---|
| Zygote 收到指令 | ZygoteServer.runSelectLoop() 中 case "start-system-server" | 可以拿到最早的时间 |
| fork 完成 | Zygote.forkSystemServer() 返回后,pid == 0 分支 | 子进程开始独立运行 |
| main() 开始 | SystemServer.main() 第一行 | 进程的 Java 入口 |
| run() 开始 | SystemServer.run() 第一行 | 准备启动服务 |
| 引导服务开始 | startBootstrapServices() 入口 | 第一批服务 |
| 引导服务结束 | startBootstrapServices() 最后一行 | 耗时=引导服务总时间 |
| 核心服务开始 | startCoreServices() 入口 | |
| 核心服务结束 | startCoreServices() 最后一行 | |
| 其他服务开始 | startOtherServices() 入口 | 这个批次最重 |
| 其他服务结束 | startOtherServices() 最后一行 | 之后马上 Looper.loop() |