常见ANR模式之Input dispatch超时:堆栈特征与典型场景

Input dispatch 超时是 Android 中最频繁的 ANR 类型之一。当系统向应用主线程分发按键、触摸等输入事件时,若主线程在 5 秒内未能完成上一个事件的消费并进入下一个事件循环,InputDispatcher 便会判定超时并触发 ANR。该模式的核心矛盾在于:主线程 Looper 被长期阻塞,无法及时拉取并处理输入事件。

堆栈特征

从 traces.txt 中截取的主线程堆栈,通常呈现以下共性(以 Android 12+ 为例):

// 典型 Input dispatch 超时主线程堆栈片段
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e3a7e0 self=0x7b9f6a4800
  | sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7ba3e5c540
  | state=S schedstat=( 2987123456 12345678 4567 ) utm=289 stm=97 core=4 HZ=100
  | stack=0x7fc6a6f000-0x7fc6a71000 stackSize=8192KB
  | held mutexes=
  at com.example.app.MainActivity.onClick(MainActivity.java:42)
  - waiting to lock <0x0a1b2c3d> (a java.lang.Object) held by thread 22
  at android.view.View.performClick(View.java:7448)
  at android.view.View.onTouchEvent(View.java:13890)
  at android.widget.Button.onTouchEvent(Button.java:246)
  at android.view.View.dispatchTouchEvent(View.java:12789)
  at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3087)
  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2786)
  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2786)
  at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2786)
  at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:484)
  at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1886)
  at android.app.Activity.dispatchTouchEvent(Activity.java:4122)
  at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:470)
  at android.view.ViewRootImpl.callActivityInputStage(ViewRootImpl.java:750)
  at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:1098)
  at android.os.Handler.dispatchMessage(Handler.java:106)
  at android.os.Looper.loopOnce(Looper.java:201)
  at android.os.Looper.loop(Looper.java:288)
  at android.app.ActivityThread.main(ActivityThread.java:7898)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
堆栈特征关键词 可能根因 典型调用链
waiting to lock、Blocked 主线程等待子线程释放锁,锁竞争导致死等 onClick → 业务方法 → synchronized / ReentrantLock
performTraversals、draw View 树绘制/布局/测量超过 5s Choreographer.doFrame → performTraversals → measure/layout/draw
IdleHandler.queueIdle IdleHandler 中执行长时间任务,拖慢下一轮事件分发 Looper.loop → MessageQueue.next → IdleHandler.queueIdle
BinderProxy.transact、IPCThreadState.waitForResponse 主线程发起 Binder 同步调用,对方进程耗时 ContentResolver.query / ServiceConnection
Thread.sleep、Object.wait 主线程被强制挂起(通常为不负责任的同步操作) Thread.sleep(6000) 或 CountDownLatch.await(…)

典型场景举例

  1. 列表滑动时点击按钮:RecyclerView 的 Adapter 中 onBindViewHolder 或 onClick 里执行了网络请求+Gson 解析,主线程被阻塞超过 5s,InputDispatcher 等待上一个 UP 事件的完成而超时。
  2. WebView 与 JS 互调:主线程通过 evaluateJavascript 等待 JS 执行结果,JS 侧死循环或 JS 桥接同步锁阻塞。
  3. 启动页过度绘制:Activity 启动时 onCreate / onResume 中加载大图、解析 JSON 配置,导致首个触摸事件来不及处理。
  4. 后台任务偷跑:子线程持有锁,主线程在 onClick 中尝试获取同一把锁(如数据库写入锁),子线程写入大数据量(超过 5 秒)导致主线程等待。

排查口诀:找到堆栈中 at android.os.MessageQueue.next 之前的最后一条主线程调用。如果卡在 View.draw 或 measure 链上,优先检查布局复杂度;如果出现 Binder 调用,检查远端服务耗时;如果出现 Lock 等待,检查线程锁图。

Input dispatch 超时发生流程

下图展示了从输入事件到达、分发到主线程、超时检测直至 ANR 的完整路径:

Input dispatch 超时触发 ANR 流程 InputReader InputDispatcher 主线程 MessageQueue ANR 检测器 AppNotResponding 5s 超时触发 说明:InputDispatcher 在派发事件到主线程前会启动一个 5s 定时器。 若定时器到期时主线程尚未消费完毕,则通过 ANR 检测器弹窗并生成 traces.txt。

主线程阻塞点分类

阻塞类型 常见原因 调试手段
锁竞争 主线程等待子线程持有的锁 使用 Thread dump 查看锁持有者;配合 StrictMode 检测锁策略
IPC/Binder 耗时 ContentProvider 查询、Service 绑定、远程回调 检查 Binder 调用耗时;使用 systrace -b 20000 --trace=aidl
绘制/布局过重 复杂 View 层级、超大 Bitmap、自定义 View 中循环绘制 Profile GPU Rendering 或 Layout Inspector
IdleHandler 滥用 在 IdleHandler 中执行同步 I/O、频繁 gc 查看 MessageQueue.next 附近的内部调用
Handler 消息处理 单条消息处理超过 5 秒 Looper.setMessageLogging 打印每条消息耗时

注意: Input dispatch 超时不一定意味着主线程真的在“做坏事”。有时是因为前一个事件的处理尚未完成(例如 onTouchEvent 返回 false,但事件队列中还有未处理的 MotionEvent),系统却因超时机制提前触发 ANR。务必结合 all events 的 timeline 综合分析。

快速定位步骤

  1. 从 /data/anr/traces.txt 中提取主线程堆栈,找到距离 at android.os.MessageQueue.next 最近的业务方法。
  2. 判断堆栈是否出现 Lock、Binder、View 相关调用,参照上表分类。
  3. 使用 systrace 或 Perfetto 捕获事件周期,查看以下关键区间:
    # 分析命令示例
    systrace -t 10 -a com.example.app input view am wm
  4. 重点查看 InputDispatcher::dispatchOnce 与 Choreographer::doFrame 之间的时间差。
  5. 如果堆栈显示主线程处于 Runnable 状态但 sleep 在 epollWait,说明事件尚未到达主线程,可能是系统侧延迟(低概率),此时需要检查 SystemServer 的 InputDispatcher 线程。

实战建议:一旦收到用户反馈“点击无响应”但 ANR 未弹出,可主动通过 adb shell dumpsys activity processes | grep anr 查看是否有 pending 的 ANR。提前拿到 traces 可避免问题复现困难。