ANR的类型
ANR 在 Android 手机中很常见,按其相应类型可以分为以下 常见 三种类型。
1. 输入事件/按键响应分发超时(Key Dispatch Timeout) 默认 5 s,超过则会出现ANR。
1.1 输入事件处理无响应
当应用程序的窗口处于活动状态并且能够接收输入事件(例如按键事件、触摸事件等)时,系统底层上报的事件就会被 InputDispatcher
分发给该应用程序。
对大多数窗口而言"处于活动状态"可以理解为"能够获得焦点且已经获取焦点",但是一些具有 FLAG_NOT_FOCUSABLE
属性的窗口(设置之后 window
永远不会获取焦点,所以用户不能给此 window
发送点击事件焦点会传递给在其下面的可获取焦点的 window)除外。
应用程序的主线程通过 InputChannel
读取输入事件并交给界面视图处理,界面视图是一个树状结构,DecorView
是视图树的根,事件从树根开始一层一层向焦点控件(例如一个 Button)传递。
开发者通常需要注册监听器来接收并处理事件,或者创建自定义的视图控件来处理事件。
InputDispatcher
运行在 system_server
进程的一个子线程中,每当接收到一个新的输入事件,InputDispatcher
就会检测前一个已经发给应用程序的输入时间是否已经处理完毕,如果超时,会通过一系列的回调通知 WMS
的 notifyANR
函数报告ANR发生。
需要注意的是,产生这种 ANR 的前提是要有 输入事件
,如果没有输入事件,即使主线程阻塞了也不会报告ANR。
从设计的角度看,此时系统会推测用户没有关注手机,寄希望于一段时间后阻塞会自行消失,因此会暂时"隐瞒不报"。
从实现的角度看,InputDispatcher
没有分发事件给应用程序,当然也不会检测处理超时和报告ANR了。 此类ANR发生时的提示语是:Reason: Input dispatching timed out (Waiting because the focused window has not finished processing the input events that were previously delivered to it.)
。
需要注意区分同为
Input dispatching timed out
大类的窗口获取焦点超时,这两类超时括号内的提示语是不同的。 此类ANR的超时时间在ActivityManagerService.java
中定义,默认为5秒
。如果有需要可以修改代码将小内存设备上的超时时间改为大于5秒。或者在某一段时间内将此参数值设置为相应合理值。
1.2 get focus timeout 窗口获取焦点超时
窗口获取焦点超时是 用户输入事件处理超时
的一种 子类型
,它们都由 InputDispatcher
向 AMS
上报。
当应用程序的窗口处于 活动状态并且能够接收输入事件
时,系统底层上报的事件就会被 InputDispatcher
分发给该应用程序。
如果由于某种原因,窗口迟迟 不能达到"活动状态",不能接收输入事件
,此时 InputDispatcher
就会报出 "窗口获取焦点超时"
。 此类ANR发生时的提示语是:Reason: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)
。
需要注意区分同为
Input dispatching timed out
大类的用户输入事件处理超时,这两类超时括号内的提示语是不同的。
为了研究窗口为什么会获取焦点超时
,我们需要简单了解在窗口切换过程中焦点应用和焦点窗口的切换逻辑。
假设当前正处于应用A中,将要启动应用B。
启动过程中焦点应用和焦点窗口转换如下:
流程开始,焦点应用是A,焦点窗口是A(的某一个窗口) ====》 当A开始OnPause流程后,焦点应用是A,焦点窗口是null ====》 在zygote创建B的进程完毕后,焦点应用是B,焦点窗口是null ====》 应用B的OnResume流程完成后,焦点应用是B,焦点窗口是B(的某一个窗口)。
在这个过程当中有 两个阶段的焦点窗口是null
,那么如果焦点窗口为 null 阶段的时间超过了5秒,应用就会被报告为窗口获取焦点超时类的ANR。
另外这个过程当中有两个阶段的焦点窗口是null,系统报告的ANR应用不一定是真实产生ANR的应用。
因此在分析窗口获取焦点超时的ANR时,一定要注意分析当前焦点应用和焦点窗口是否一致,首先要明确ANR的真正应用是哪一个,再进行进一步分析才会更有意义。
那么 "焦点窗口为 null 阶段的时间超过了5秒"
这种情况又是为什么会出现呢?
一般由下面几个原因导致:
-
应用程序创建慢
:程序的OnCreate/OnStart/OnResume方法执行速度慢/存在死锁/死循环导致OnResume迟迟不能执行完毕,超时造成ANR。 -
应用程序'OnPause'慢
:对同一个应用而言,前一次OnPause执行完毕之前后一次OnResume不会执行。但不同应用之间不会互相影响。 -
系统整体性能慢
:由于系统性能原因,如CPU占用率高/平均等待队列长/内存碎片化/页错误高/GC慢/用户空间冻结/进程陷入不可打断的睡眠,会造成整体运行慢使ANR频繁发生。
2. 广播超时(Broadcast Timeout) 默认 10 s,超过则会出现ANR。
该部分内容比较好理解,不进行详细的展开。
3. 服务超时(Service Timeout) 默认 20 s,超过则会出现ANR。
该部分内容比较好理解,不进行详细的展开。
如何分析ANR产生的原因
知道了 ANR 的分类后,那么如何进行 ANR 的分析呢。
在分析ANR时有一些常见的模式可供选择:
- APP正在主线程上进行缓慢的I/O操作;
- APP正在主线程中进行很复杂的计算操作;
- 主线程正在对另一个进程执行同步Binder程序调用,但另一个进程需要很长时间才能返回结果;
- 主线程在等待另一个正在长时间执行块操作的子线程时被阻塞;
- 主线程因为另一个线程死锁,无论是Bind调用还是主线程调用,都不能让主线程等待很久,更不能在主线程中进行复杂的计算。
常用的排查手段
1. 开发环境中开启 StrictMode ,开发阶段严格控制UI线程上的 I/O操作
耗时操作
使用 StrictMode
可以帮助您在开发应用程序时在主线程上发现意外的 I/O操作
。 您可以在application或activity使用StrictMode。
2. 拉取ANR日志文件进行分析
Traceview 获取正在运行的应用程序的跟踪信息,分析此 traces.txt 文件 可以推测出主线程在忙于某些事情。 traces文件通常保存在/data/anr/traces.txt下,你可以直接用 adb cat 查看,或者 adb pull 出来都可以。
建议使用 adb pull 到电脑后在查看。