第3章:线程基本信息采集

SystemUI 无障碍线程的异常行为(如界面卡顿、输入无响应)往往源于线程数量膨胀、优先级错乱或关键线程阻塞。采集线程基本信息是定位问题的第一步,本章提供两种标准化采集方法:adb shell ps -T 快速扫描与 /proc/pid/task/ 深度解析。

线程基本信息采集流程(两种方法对比) 方法一:adb shell ps -T 快速获取进程下所有线程 输出特点 线程名 / TID / CPU使用率 / 优先级 方法二:/proc/[pid]/task/ 解析 逐线程解析状态/栈/调度信息 关键文件 status(状态)/ sched(调度)/ stack(调用栈) 综合异常线程识别

3.1 现象识别

异常现象线程层面征兆判定规则
下拉通知栏卡顿主线程(binder:xxx)在线程列表中长时间处于 R 状态同一 TID 连续 3 次采样均为 R 状态且 CPU 占用 >30%
无障碍焦点切换延迟 >200msAccessibilityInteractionClient 相关线程数量超过 5 个ps -T 输出中 grep “access” 线程数 >5
SystemUI 全局 ANR某个子线程(如 AsyncTask #)处于 D 状态超过 5s/proc/pid/task/*/stat 状态字段为 D 且累计时间 >5s

3.2 数据采集

3.2.1 方法一:adb shell ps -T 快速扫描

使用 -T 选项列出指定进程下所有线程的 PID、TID、CPU 使用率、优先级及线程名。适用于现场初步判断线程规模与活跃度。

# 获取 SystemUI 进程 PID(假设为 1234)
adb shell ps -A | grep systemui | awk '{print $2}'
# 输出: 1234

# 获取线程列表
adb shell ps -T -p 1234
# 示例输出片段(简化):
# USER    PID    TID    PPID    VSZ    RSS    WCHAN    ADDR    S    NAME
# u0_aXX  1234   1234   1       123456 45789  SyS_epo 0       S    systemui
# u0_aXX  1234   1235   1       123456 45789  futex   0       S    binder_io
# u0_aXX  1234   1240   1       123456 45789  do_sel  0       R    Accessibility
# u0_aXX  1234   1245   1       123456 45789  pipe_w  0       D    AsyncTask #1
经验规则:如果某线程 NAME 中包含 "Accessibility" 且状态为 R(运行)且 CPU% 超过 20%,极有可能正在执行高耗时无障碍回调。

3.2.2 方法二:/proc/[pid]/task/ 深度解析

进入进程目录,遍历每个 TID 子目录,读取 status、sched、stat、stack 等文件,获得线程精确状态、调度优先级、内核栈等底层信息。

# 遍历 /proc/1234/task/ 下的所有线程目录
adb shell "for d in /proc/1234/task/*/; do
    echo '=== TID: '$(basename $d)' ==='
    cat $d/status | grep -E '^(Name|State|Pid|Tgid|Ppid|Uid)'
    cat $d/sched | head -3
    echo '---'
done"
注意:/proc/pid/task/ 中的 stack 文件只能在 root 权限下读取。若设备未解锁,可使用 adb shell cat /proc/1234/task/1245/stack 尝试,失败时返回空或 "permission denied"。

3.3 原因定位

使用故障树方法缩小嫌疑线程范围:

  • 故障根:SystemUI 响应慢
  • ├─ 主线程阻塞? → 检查 ps -T 中 NAME 为 systemui 的 TID 状态是否为 D 或长时间 R
  • │ └─ 是 → 进一步读取 /proc/[pid]/task/[TID]/wchan 查看等待链
  • ├─ 无障碍线程过载? → 统计 NAME 含 "Accessibility" 或 "assist" 的线程数量及 CPU%
  • │ └─ 数量 >5 且总 CPU% >80% → 无障碍服务逻辑死循环或频繁反射
  • └─ 子线程死锁? → grep 状态为 D 的线程,比对 stack 文件中的锁等待符号

3.4 修复动作

定位结论修复动作代码/命令参考
主线程因 binder 调用等待而阻塞 将 binder 同步调用改为异步,或使用 HandlerThread 解耦
// 改为异步回调
IBinder.transact(code, data, reply, IBinder.FLAG_ONEWAY);
无障碍线程数量异常(>10) 限制无障碍服务线程池大小,复用已有线程
// 在无障碍服务中配置
ExecutorService pool = Executors.newFixedThreadPool(4);
某线程处于 D 状态超过 10s 检查是否持有锁而未被释放,或内核驱动挂起
# 修复后 kill -9 该 TID 并重启服务(应急)

3.5 复测验证

修复后重复执行数据采集命令,验证以下指标:

  • 线程总数减少 ≤30%(与正常基线对比)
  • 无障碍相关线程 CPU% 总和 <30%
  • 无持续 D 状态线程存在
# 复测命令
adb shell ps -T -p $(adb shell ps -A | grep systemui | awk '{print $2}') | grep -E 'Accessibility|binder|AsyncTask'
# 期望输出:线程名清晰,状态以 S 为主,无 D 状态,CPU% 总和 < 30

3.6 预防方案

预防措施具体实现
建立线程数量基线 在 CI/CD 流程中加入 adb shell ps -T 采集,若线程数超过历史平均值的 1.5 倍则报警
线程命名规范 所有无障碍相关 Thread 必须以 "Accessibility-" 前缀命名,便于筛选与归因
定期检查 /proc/pid/task/ 调度时间
# 每日定时任务
adb shell cat /proc/1234/task/*/sched | grep -E 'se.sum_exec_runtime' | awk '{sum+=$NF} END{print sum}'
章节小结:通过 ps -T 与 /proc/pid/task/ 组合采集,可快速获取 SystemUI 无障碍线程的完整快照。结合状态、CPU% 和堆栈信息,能在 5 分钟内将问题线程范围缩小到 1-2 个嫌疑 TID。建议将本章命令封装为 sysui_thread_snapshot.sh 脚本,集成到日常性能巡检工具链中。