常见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(…) |
典型场景举例
- 列表滑动时点击按钮:RecyclerView 的 Adapter 中
onBindViewHolder或onClick里执行了网络请求+Gson 解析,主线程被阻塞超过 5s,InputDispatcher 等待上一个 UP 事件的完成而超时。 - WebView 与 JS 互调:主线程通过
evaluateJavascript等待 JS 执行结果,JS 侧死循环或 JS 桥接同步锁阻塞。 - 启动页过度绘制:Activity 启动时
onCreate/onResume中加载大图、解析 JSON 配置,导致首个触摸事件来不及处理。 - 后台任务偷跑:子线程持有锁,主线程在
onClick中尝试获取同一把锁(如数据库写入锁),子线程写入大数据量(超过 5 秒)导致主线程等待。
排查口诀:找到堆栈中 at android.os.MessageQueue.next 之前的最后一条主线程调用。如果卡在 View.draw 或 measure 链上,优先检查布局复杂度;如果出现 Binder 调用,检查远端服务耗时;如果出现 Lock 等待,检查线程锁图。
Input dispatch 超时发生流程
下图展示了从输入事件到达、分发到主线程、超时检测直至 ANR 的完整路径:
主线程阻塞点分类
| 阻塞类型 | 常见原因 | 调试手段 |
|---|---|---|
| 锁竞争 | 主线程等待子线程持有的锁 | 使用 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 综合分析。
快速定位步骤
- 从
/data/anr/traces.txt中提取主线程堆栈,找到距离at android.os.MessageQueue.next最近的业务方法。 - 判断堆栈是否出现
Lock、Binder、View相关调用,参照上表分类。 - 使用
systrace或Perfetto捕获事件周期,查看以下关键区间:# 分析命令示例 systrace -t 10 -a com.example.app input view am wm - 重点查看
InputDispatcher::dispatchOnce与Choreographer::doFrame之间的时间差。 - 如果堆栈显示主线程处于
Runnable状态但sleep在epollWait,说明事件尚未到达主线程,可能是系统侧延迟(低概率),此时需要检查 SystemServer 的InputDispatcher线程。
实战建议:一旦收到用户反馈“点击无响应”但 ANR 未弹出,可主动通过 adb shell dumpsys activity processes | grep anr 查看是否有 pending 的 ANR。提前拿到 traces 可避免问题复现困难。