3. SystemServer 初始化中的性能监控需求
说到 SystemServer 的初始化,很多人第一反应是“把服务都 start 起来就行”。但我在项目中踩过太多次坑了——服务启动顺序、锁竞争、IPC 超时,任何一个环节出问题,手机开机就要多等好几秒。所以这一章,咱们专门聊聊性能监控到底要盯哪些东西。
3.1 为什么要做性能监控?
说白了,系统启动是用户第一眼看到的体验。如果冷启动花了 30 秒,用户早就把手机摔了。我见过一个案例:某款设备升级后,开机动画播了 15 秒还没进入桌面,最后定位到是 PMS 扫描包时频繁触发 GC。
性能监控的好处很明显:
- 快速定位瓶颈 – 哪个服务耗时异常,一眼就能看到
- 量化优化效果 – 改之前 5.2s,改之后 4.7s,有数据才靠谱
- 防止回归 – 版本迭代时自动告警,避免新改动拖慢启动
我个人习惯在每一版固件发布前,跑一遍全链路监控,记录所有关键节点的时间戳。哪怕这次没改 SystemServer,也能知道有没有意外退化。
3.2 核心监控维度
不要什么都埋点,那样只会制造垃圾数据。我把常用的维度整理成一个表格,你照着清单做就行。
| 维度 | 指标 | 说明 |
|---|---|---|
| 时间 | 阶段耗时、总耗时 | 每个 Service.onStart 到 handleMessage 的时间 |
| CPU | 占用率、掉帧数 | 启动阶段 CPU 是否飙到 100%?有没有明显掉帧 |
| 内存 | PSS、GC 次数 | SystemServer 进程内存增长曲线,GC 是否频繁 |
| 锁竞争 | 锁等待时间、死锁 | 主线程 wait 太久会导致 ANR |
| IPC | Binder 调用耗时 | 跨进程调用是否超时,尤其 service 之间的依赖 |
| I/O | 读写耗时、路径 | 解析 manifest、扫描 data 目录很耗 I/O |
这里我建议优先关注「时间」和「CPU」,因为它们是用户体验最直接的映射。等优化到一定程度,再深入看锁和 IPC。
3.3 关键埋点阶段与分类
SystemServer 初始化不是一根直线,而是分阶段并行处理。我们需要把埋点分散到每个阶段入口和出口。下图是我常用的阶段划分:
从图上你能看到,我习惯把埋点分为三个阶段:预加载期(Zygote)、服务启动期(SystemServer 主线程 + 各服务)、广播通知期。每个阶段的监控指标侧重点不同。比如预加载期重点关注类加载数量和耗时,服务启动期要看锁竞争和 IPC 延迟,广播期则要看接收者处理时长。
3.4 常见埋点误区与避坑
做埋点容易犯几个错误,我列出来希望你能避开:
- 埋点本身影响性能 – 比如每个函数都加 log,启动时间直接翻倍。建议用异步写日志,或者只在 debug 版本开启。
- 时间戳精度不够 – 用 SystemClock.elapsedRealtime() 而不是 currentTimeMillis(),后者会被 NTP 修改。
- 忽略并发场景 – SystemServer 里多个线程同时跑,如果只统计主线程,可能漏掉 work thread 的瓶颈。
- 依赖顺序假设 – 我曾经以为 Service A 一定在 Service B 之前启动,结果某次改动了启动顺序,埋点数据全乱了。
特别注意:不要在 Service.onStart() 里做耗时操作,否则直接卡住主线程。如果真的要统计,用 Handler.post 抛到空闲时再记录。
另外,我建议你在正式埋点前先跑一个空监控版本(只加时间戳,不做任何额外操作),测量 overhead。如果埋点使启动耗时增加超过 1%,就需要优化埋点方式。