Android 工程师应该都遇到过应用无响应(ANR,Application Not Responding)问题,当应用程序一段时间无法及时响应,则会弹出ANR对话框,让用户选择继续等待,还是强制关闭。
前段时间做预装厂商反馈了好些ANR,借此机会再次复习下ANR 相关的知识。App 稳定性监控ANR 也占了大头,对ANR 流程的深刻理解也有助于做监控。希望这些Input ANR 知识对你能有所帮助。
ANR 信息
发生ANR 以后会有3个主要的信息帮助开发者修复问题:Log 日志可以查看ANR 的原因,Trace 日志可以找到具体耗时Trace 路径,弹窗则提示用户发生了ANR。
Log 日志
|
|
Trace 日志
|
|
弹窗
Input 机制相关流程
主要代码:
|
|
InputDispatcher(InputDispatcherThread)线程负责将输入事件分发到目标窗口,其中用到了几个重要的事件队列:
- mInBoundQueue 用于记录InputReader 发送过来的输入事件
- outBoundQueue 用于记录即将分发给目标应用窗口的输入事件
- waitQueue 用于记录已分发给目标应用,且应用尚未处理完成的输入事件
- mCommandQueue 用于记录一些特殊事件(比如这里面要讲解的ANR)
下面是mInBoundQueue、outBoundQueue、waitQueue、mCommandQueue 对应的关系。
这里要讲的ANR 和事件分发流程起始点都是InputDispatcherThread::threadLoop
。
Input 事件分发流程
代码:
|
|
这里是一个完整的按键事件流程(简易版本),主要涉及到的流程:
- mInBoundQueue 队列中获取输入事件(dispatchOnceInnerLocked)
- 按键事件分发(dispatchKeyLocked)
- 查找事件对应的目标窗口(findFocusedWindowTargetsLocked)
- enqueueDispatchEntriesLocked 将事件加入到outBoundQueue 队列中
- startDispatchCycleLocked 分发事件,如果分发失败会将事件从outBoundQueue 中移除然后加入到waitQueue 队列
- InputChannel 部分后面以后可以专门详细分析
- 事件分发成功以后会重置ANR 以及mPendingEvent 等(releasePendingEventLocked)
Java 层的流程:
Input ANR 流程
Input ANR 产生的流程如果不仔细研究下,可能会存在一些误解。分析下来可将ANR 分成3个步骤:
- 步骤1(上图2~8),
checkWindowReadyForMoreInputLocked
检查可能出现ANR(具体原因可以查看下方:ANR 原因),handleTargetsNotReadyLocked
计算出发生ANR 的超时时间 - 步骤2(上图9~16),
handleTargetsNotReadyLocked
判断是否发生了ANR(当前事件时间距离上次事件时间超过5s,不同的ROM 可能修改这个时间的,具体时间间隔是下一次dispatchOnceInnerLocked
距离上一次resetANRTimeoutsLocked
时间间隔超过5s),最后调用onANRLocked
记录ANR 事件 - 步骤3(上图17~20),
runCommandsLockedInterruptible
最后执行ANR 相关事件
具体3个步骤开始入口可以查看下方代码InputDispatcher::dispatchOnce()
:
|
|
ANR 计算关键代码:
|
|
currentTime
表示当前事件开始的时间- 如果当前还有未完成的事件(
mPendingEvent
不为null)则会通过resetANRTimeoutsLocked
重置ANR
|
|
mInputTargetWaitTimeoutTime
是计算出发生ANR 的时间。具体计算的值是步骤1(上图2~8)分发事件时间 +timeout
(此处timeout
为5s)。由于是第1次执行handleTargetsNotReadyLocked
所以currentTime < mInputTargetWaitTimeoutTime
会直接退出该方法- 步骤2(上图9~16)也会执行到
handleTargetsNotReadyLocked
,currentTime >= mInputTargetWaitTimeoutTime
成立会继续执行onANRLocked
记录ANR 信息
Input ANR 处理流程
system_process
进程处理ANR,流程中也有一些关键需要注意:
- 步骤2,
InputMonitor#notifyANR
会计算发生ANR 的进程 - 步骤5,
AppErrors#appNotResponding
会打印ANR 的Log 日志 - 步骤6,
ActivityManagerService#dumpStackTraces
会收集ANR 的Trace 日志 - 步骤8,
AppErrors#handleShowAnrUi
会展示给用户ANR 弹窗
Input ANR 收集信息流程
ANR Trace 日志是解决ANR 问题的关键。system_process
进程通过sendSignal
(SIGNAL_QUIT 信号)发送信号给发生ANR 的进程,最后发生ANR 的进程在Signal Catcher
线程收集所有线程的信息。
代码:
|
|
具体生成的ANR Trace 日志可以查看上面Trace 日志信息。里面包含了大量的信息:Classloader Info、Intern Table、JavaVM、Heap、oat、JIT、Deoptimizations、ThreadList 等等。最主要的信息是线程的堆栈信息,ThreadList 收集的线程有两种attached 和not attached,详细代码可以查看Thread::DumpState
和Thread::DumpStack
,分别打印线程状态和堆栈信息。
ANR 原因
Input ANR 流程中分析checkWindowReadyForMoreInputLocked
有分析产生ANR 的原因。
- 无窗口, 有应用:Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.
- 窗口暂停:Waiting because the 【targetType】 window is paused.
- 窗口未连接:Waiting because the 【targetType】 window’s input channel is not registered with the input dispatcher. The window may be in the process of being removed.
- 窗口连接已死亡:Waiting because the 【targetType】 window’s input connection is 【StatusLabel】.The window may be in the process of being removed.
- 窗口连接已满:Waiting because the 【targetType】 window’s input channel is full. Outbound queue length: 【outboundQueue.count】. Wait queue length: 【waitQueue.count】.
- 按键事件,输出队列或事件等待队列不为空:Waiting to send key event because the 【targetType】 window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 【outboundQueue.count】. Wait queue length: 【waitQueue.count】.
- 非按键事件,事件等待队列不为空且头事件分发超时500ms:Waiting to send non-key event because the 【targetType】 window has not finished processing certain input events that were delivered to it over 【STREAM_AHEAD_EVENT_TIMEOUT】 ago. Wait queue length: 【waitQueue.count】. Wait queue head age: 【等待时长】.
ANR 可能存在的误解
大家可能会误解只要主线程执行时间超过5s 就会发生Input ANR,从上面分析中可以得出实际上不是。实际上发生一个Input ANR 至少需要执行两次InputDispatcher::dispatchOnce
,第一次是检查可能出现了ANR 并且计算出发生ANR 的时间,第二次InputDispatcher::onANRLocked
才是处理ANR 相关的逻辑。
处理ANR 小秘诀
- 可以全局搜索
ANR in
用来定位ANR 原因 - 最后收集ANR 日志是通过
sendSignal(SIGNAL_QUIT)
,可以参考实现ANR 监控
参考
彻底理解安卓应用无响应机制
Input系统—ANR原理分析
Input系统—InputDispatcher线程
Input系统—事件处理全过程
Input系统—启动篇
Android trace文件抓取原理