SystemServer 初始化阶段划分

我常常跟团队里的新人说,别把 SystemServer 想成一个黑盒。它从 Zygote 里 fork 出来,再到跑起上百个服务,每个阶段都有明确的边界。你只有把边界划清楚了,埋点才能精准——不然你根本不知道这个耗时到底花在哪儿了。

核心观点:SystemServer 初始化 = Zygote 孵化 → 进程自检 → 服务分批次启动。三个阶段,三条埋点主线。

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 以上。

子阶段关键代码入口埋点时机注意
监听 socketZygoteInit.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() 方法里会依次调用三个批量启动函数:

  1. startBootstrapServices():引导服务,比如 PackageManagerService、ActivityManagerService、PowerManagerService。这些是其他服务的依赖基础。
  2. startCoreServices():核心服务,比如 BatteryService、UsageStatsService、WebViewUpdateService。
  3. 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. 整体流程图

把三个阶段串起来,就得到下面这张图。注意节点之间的顺序和箭头指向,每个方框都代表了你可以埋点的位置。

SystemServer 初始化三个阶段流程图 ZygoteInit.main() forkSystemServer() RuntimeInit.commonInit() SystemServer.main() SystemServer.run() 引导服务 核心服务 其他服务 每个矩形节点都可以埋一个“开始”和“结束”标记,从而拆分出每个阶段的耗时。 连线上也有时间消耗,例如 fork 系统调用本身、JNI 注册、库加载等。

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()
总结一下:把三个阶段埋好,你就能精确知道开机慢到底慢在哪个环节。是 Zygote 预加载太慢?还是引导服务里某个 Service 的 onStart 卡住了?数据一目了然。我在两个量产项目里用这套方案追踪过,最后分别优化掉了 34% 和 41% 的开机时间——说白了,埋点就是给性能优化装上了一双眼睛。