3. WindowManagerService 消息队列深度排查
窗口动画卡顿、触摸事件响应延迟、界面冻结——这些问题往往与 WindowManagerService 内部消息队列的健康状态直接相关。WMS 依赖其主线程的 Handler 队列处理窗口布局、动画更新、输入事件派发等核心任务。一旦队列出现积压、任务死锁或线程阻塞,便会引发连锁的用户感知异常。
3.1 现象识别
以下是窗口动画卡顿与输入事件队列异常的典型表现,有助于工程师快速建立排查方向。
| 现象分类 | 用户感知 | 系统信号 |
|---|---|---|
| 窗口动画卡顿 | 启动/退出动画掉帧,过渡动画不连贯,App 窗口突然“卡住”后跳帧 | dumpsys window traces 显示动画帧率低于 60fps;ANR 日志中出现 Executing service com.android.systemui/.SystemUIService |
| 输入事件队列异常 | 触摸/按键无响应,点击后经过数秒才生效,或触控轨迹明显滞后 | dumpsys input 显示 Dispatch time too far in the future 或 Window input channel BUFFERED |
| 死锁判断 | 整个 SystemUI 或 Launcher 僵死,logcat 中无输入事件派发日志,dumpsys window 命令超时无响应 | dumpsys -t 5 window 卡住;cat /proc/<pid>/task/*/stack 显示多个线程相互等待锁 |
3.2 数据采集
使用 dumpsys window 命令获取队列状态时,建议优先执行以下三条命令,分别对应不同维度。
# 获取 WMS 消息队列整体信息(包含 pending 和 delayed 任务数)
adb shell dumpsys window | grep -A 20 "Looper"
# 获取详细的动画帧队列状态
adb shell dumpsys window animations
# 获取输入通道绑定窗口的队列深度
adb shell dumpsys input --windows
数据采集关键点:在故障复现期间连续采集 3~5 次(间隔 1 秒),对比队列深度变化趋势。单次快照只能判断是否阻塞,无法识别队列是否在持续增长。
3.3 原因定位
以下故障树展示了从“窗口动画卡顿”根节点出发的排查路径。每个叶子节点对应一个可执行的检查点。
故障树:WMS 消息队列卡顿原因定位
3.4 修复动作
根据故障树定位到的叶子节点,执行以下针对性修复动作。
| 定位结果 | 修复动作 | 验证方式 |
|---|---|---|
| Looper 队列深度 > 2000,且持续增长 | 检查 WMS Handler 是否在 performLayoutAndPlaceSurfacesLocked 中频繁调用同步 Binder 请求(如 reloadWindowResources),将其改为异步或移除冗余触发。 | 复测后队列深度降至 200 以下 |
| AnimationHandler 的 runNextFrame 超时 | 减少单帧内的 SurfaceTransaction 数量,避免多个动画同时触发 setAlpha/setMatrix 导致 VSYNC 延迟。 | dumpsys window animations 显示帧间隔 < 16ms |
| Input 通道队列满 | 优化对应窗口的 UI 线程消息处理,确保 onTouchEvent 不在主线程执行耗时 I/O 或复杂布局。 | dumpsys input --windows 显示队列占用 < 20% |
| 死锁判断为真 | 在 WMS 锁 mWindowMap 与 mStackId 之间引入锁排序,或使用 tryLock 避免相互等待。 | 多次复现场景下不再出现线程栈死锁 |
3.5 复测
修复后必须执行以下三步复测流程,确保队列状态恢复正常。每一步都应采集 dumpsys window 数据做前后对比。
- 基础功能测试:快速打开和关闭 10 个不同应用,观察窗口动画是否平滑,无掉帧或冻结。
- 压力测试:使用
adb shell input swipe模拟高速连续滑动,同时触发多窗口切换,运行 2 分钟后采集队列深度。 - 死锁稳定性测试:在
dumpsys -t 5 window连续 5 次执行中,保证命令均在超时前返回,且输出中不出现Timeout waiting for reply。
3.6 预防
长期预防措施:在 CI 流程中集成
dumpsys window | grep "Looper" 基线检查,设定队列深度上限为 300。若超出则自动阻断提交。同时为所有 WMS Handler 的 Message 添加 setMessageName(),确保 dumpsys window 输出的队列日志可追溯耗时消息来源。
此外,建议在系统服务侧开启 Looper.setMessageLogging() 日志,将执行时间超过 50ms 的消息记录到 EventLog 中,便于在生产环境中提前发现队列繁忙趋势。