4. CPU竞争分析:cpuinfo、systrace、perfetto中的线程状态
现象识别 —— 当ANR发生时,往往伴随着CPU竞争激烈,表现为:主线程长时间处于Runnable状态却得不到调度、系统负载居高不下、关键线程频繁被抢占。最终用户感知到界面卡死或“应用无响应”对话框。
4.1 现象识别:CPU竞争导致ANR的典型信号
CPU竞争型ANR的典型特征如下表所示,可用于初步定界:
| 观察维度 | 典型信号 | 怀疑程度 |
|---|---|---|
| /proc/stat 中的 CPU 使用率 | user+system 持续 > 90%,iowait 低 | 高 |
| top 或 cpuinfo 负载 | load average 接近或超过 CPU 核心数 | 高 |
| systrace 主线程状态 | 主线程长时间处于 Running(蓝色),但无进展 | 极高 |
| perfetto 调度记录 | 同一时间多个高优先级线程争抢核心 | 极高 |
| logcat “ActivityManager: ANR” 前后 | CPU 占用突增,其他进程频繁唤醒 | 中 |
注意:不要将 iowait 高(磁盘I/O瓶颈)误判为CPU竞争。iowait高时,CPU空闲等待I/O,而非被其他线程抢占。
4.2 数据采集:三工具并行取证
三种工具各有侧重,必须联合使用才能完整还原CPU竞争全景。
4.2.1 cpuinfo 快速快照
在ANR发生瞬间,通过 adb shell cat /proc/stat 和 top 获取全局负载:
adb shell cat /proc/stat | head -3
adb shell top -n 1 -d 1 | head -20
关键指标:
– cpuN 行:user/nice/system/idle/iowait/irq/softirq 比例。
– load average:1分钟、5分钟、15分钟。若1分钟 > 核心数 × 0.7,CPU竞争风险高。
4.2.2 systrace 线程状态染色
使用 atrace --async_start -a <pkg> sched freq idle 捕获,然后通过 adb shell atrace --async_stop -z > trace.ctrace 导出。打开后关注:
- 主线程(UI Thread):颜色应为绿色(Sleeping)或蓝色(Running)?若长时间蓝色且无任何系统调用(syscall计数停顿),说明被更高优先级线程抢占。
- 其他线程:查看是否有后台线程(如渲染线程、Binder线程、自定义线程池)频繁运行,导致核心满载。
- 调度片段(Sched Fragment):查看每个线程的CPU占用率,识别“吃CPU大户”。
4.2.3 Perfetto 深度调度分析
Perfetto 提供了比 systrace 更细的调度视图。使用以下配置捕获:
adb shell perfetto \
-c - -o /data/misc/perfetto-traces/trace.perfetto \
<
Perfetto 界面中可看到:
- 线程状态时间线:每个线程的 Running(绿色)、Runnable(可运行但未调度,蓝色)、Sleeping(灰色)、Uninterruptible Sleep(红色,I/O阻塞)。
- CPU片分配:哪些线程在哪段时间内分配了哪个核心。
- 全局调度延迟:Runnable 至 Running 的等待时间,如果多次超过 10ms 即为异常。
4.3 原因定位:故障树分析法
使用故障树从现象逐层深入,如下SVG所示:
4.4 修复动作:针对根因的精准干预
根据故障树分析结果,执行对应修复方案:
| 根因 | 修复动作 | 参考代码/配置示例 |
|---|---|---|
| 频繁上下文切换(大量Binder线程) | 减少不必要的Binder调用;合并跨进程请求;使用异步回调 | |
| 高优先级抢占(后台线程优先级过高) | 将后台线程优先级降至 THREAD_PRIORITY_BACKGROUND 或更低 | |
| 内核态死循环 | 检查驱动、HAL层、JNI实现;避免在持有锁的情况下执行耗时syscall | |
| 多个线程争抢同一个锁(非明确分支但常见) | 降低锁粒度;改用读写锁或无锁数据结构 | |
提示:修复后不要仅依赖标准测试,需构造CPU高负载场景(比如同时执行动画、网络请求、数据库写入)复现,确认主线程延迟不再出现。
4.5 复测:验证修复有效性
复测用例至少包含以下场景:
- 基线测试:修复前回放ANR现场(需保存原始trace),确认症状一致。
- 修复后测试:使用相同负载脚本运行,触发主线程逻辑(如点击、滑动、刷新)。
- 压力测试:使用
stressapptest或cyclictest提高系统CPU负载至90%以上,再次检查主线程调度延迟。 - 工具验证:用
systrace或perfetto捕获修复后trace,确认主线程不再长时间处于Running但无进展,后台线程的CPU占用率下降。
复测判定标准:
- 主线程Runnable等待调度时间 < 2ms(原 > 50ms)。
- 系统load average 在高峰时不超过核心数 × 0.5。
- 无ANR日志生成。
4.6 预防:构建CPU竞争监控体系
将排查经验转化为预防机制:
| 预防措施 | 实现方式 | 监控指标 |
|---|---|---|
| 运行时线程优先级审计 | 在WebView、三方SDK初始化后遍历ThreadGroup,检查优先级 | 非系统线程priority < -2 则记录警告 |
| Binder调用频次限制 | 封装Binder助手,内置节流阀(每100ms最多5次) | 调用次数/秒 > 阈值时降级 |
| 后台任务CPU限额 | 使用WorkManager设置setExpedited(false),限制CPU密集型任务 | CPU使用率 > 30%持续10秒时暂停 |
| 线上perfetto自动捕获 | 集成Perfetto SDK,当主线程调度延迟>100ms时自动dump trace | trace上报率 > 95% |
总结:CPU竞争型ANR的排查核心在于“看调度”——通过cpuinfo快速感知整体负载,通过systrace捕获线程状态变化,通过perfetto精确计算调度延误。结合故障树剪枝,可高效定位到频繁上下文切换、高优先级抢占或内核态异常。预防阶段需将检查和节流机制内建到应用运行时,防患于未然。