ANR决策树一解决ANR问题思路小结

Wiki 和飞书中已经有很多 ANR 文章,此文章主要分享自己遇到 ANR 问题时的解决思路,会避免一些和其他人重复的内容。

一、怎么会引起 ANR 问题?

以 Service 的 onCreate 耗时造成 ANR举例:

  1. AMS 调用 App, 告诉 App 要执行 onCreate 方法了。
  2. App 开始执行 onCreate 方法。

3)App 执行完 onCreate 方法后告诉 AMS。

4)AMS 计算 1)到3) 之间的耗时,是否超过阈值,前台服务阈值 20s, 后台服务阈值 200s。

二、开始分析 ANR 问题的流程了

这里先强调一点,ANR 问题是一个耗时问题。

我们拿到的 trace 是一个时间片,时间片并不会引起 ANR。

什么问题会引起耗时?

等资源:包括 CPU 太高、内存占用太多、IPC(Binder)等待另外进程的回调、锁竞争、磁盘数据库IO、访问多媒体Wifi\Audio等硬件服务。

开始分析 ANR 问题了。

下图展示了 ANR 流程的决策树流程

2.1 蓝色快区域,focus window。

针对 input ANR,如果有 focus window 超过 5s 的 ANR。

Input 事件分发的三个队列:

InputDispatcher的实现主要涉及3个Queue:

(1). InboundQueue: 这个队列里面存储的是从InputReader送来到输入事件

(2). OutboundQueue:这个队列里面存储的是即将要发送给应用的输入事件

(3). WaitQueue:这个队列里面存储的是已经发给应用的事件,但应用还未处理完成。

Input 发生ANR的条件是:WaitQueue有元素,且mPendingEvent等待(加入OutboundQueue)超时。

(A)如果在dispatchTouchEvent, onTouchEvent等处于consumeEvents调用链中,执行耗时操作,即使未达到5s,也可能因为事件累计导致ANR。导致ANR的条件是在有PendingEvent时,WaitQueue中累积事件的总处理时长大于5s。

(B)如果在onButtonClicked等非consumeEvents调用链中,执行耗时操作,一定是大于5s(且有PendingEvent)才会触发ANR。因为onButtonClick是在dispatchTouchEvent执行完成后转抛到主线程的一条消息。也就是当进入onButtonClick函数时,点击的down/up事件已经执行完成,并从WaitQueue移除。(这样理解简单:consumeEvents调用链中有三个队列,onButtonClicked非consumeEvents调用链中四个队列。)参考:https://zhuanlan.zhihu.com/p/53331495

假如只有第一个事件耗时,即使耗时20s 或者耗时 30s 都不会引起 ANR,这是因为没有后续时间假加入的 PendingEvent,谷歌设计如此。大家可以写个demo验证一下。

2.2 绿色块区域,说明上图中的【6】DeadSystemException 问题:

binder 分为 oneway 调用和非 oneway 调用,

oneway 调用 buffer 为0.5M, 没有返回值。

非oneway 调用 buffer 为1M,有返回值。

上图 serviceDoneExecuting 是 oneway 接口,就是当 server 进程中的 binder buffer 超过 0.5M 的时候,App端执行Service 的 onCreate 方法后无法再通过 serviceDoneExecuting 告知 AMS,AMS 等待超过时间阈值后会抛出 ANR。

这之后会打印 DeadSystemException 的异常日志。

DeadSystemException 的相关 Wiki 可访问 DeadSystemException\DeadObjectException 问题案例汇总

2.3 红色块 trace 日志

一般来说 trace 是一个时间片,无法确认是否在一个时间段内耗时。但是如果是在等锁,且有多处等锁。比如下面这种:

- waiting to lock <``0x0420f9fc``> (a com.android.server.wm.WindowManagerGlobalLock)``- waiting to lock <``0x0420f9fc``> (a com.android.server.wm.WindowManagerGlobalLock)``- waiting to lock <``0x0420f9fc``> (a com.android.server.wm.WindowManagerGlobalLock)``- waiting to lock <``0x0420f9fc``> (a com.android.server.wm.WindowManagerGlobalLock)

也可以从 trace 中等锁的线程的 trace 这个时间片推导处耗时,因为这个进程中多个其他线程也在等锁,可以近似认为多个时间片就是一个时间段。

2.4.1 橙色块 【12】

dvm_lock_sample,binder_sample,looper_sample… 这是在关键点插入的耗时统计(有的版本是 slow looper, slow binder 格式不一样,但是目的是一样的)。

只有这些日志,我们才好确定是时间段耗时。

2.4.2 橙色快 【13】

这是MIUI性能功耗定制的一些发热、加速、冻结策略,可能会和 ANR 相关,可以辅助分析。

2.5 什么时候可以放弃?

我自己的经验来说,经过上面 ANR 决策树的 13 个步骤,未得出有效结论,而且也不复现这时候可能需要放弃,标记不可复现,关jira。

但是有两点需要注意

(A)持续跟踪,后面是否还有这个应用 ANR 的情况,如果有就需要想办法解决,避免手机给用户带来不好的用户体验。

(B)如果有trace 和 log 不是很齐全的(决策树中的红色块和橙色块),可以联系功耗或者稳定性、性能同事添加日志。 复现后可以在接着分析。

三、ANR 具体流程

下面是 ContentProvider 的请求流程,ANR 主要监听紫色色块的地方。也是 AMS 通知 App 执行某个操作 App 执行完的时候再告知 AMS,AMS 统计这个时间段是否超过阈值,超过阈值则发生 ANR。

四种类型的 ANR 详细流程各不一样,这里不一一列举,

这里举例 ContentProvider 的 ANR 流程想说明如果对流程的细节了解的越清楚,解bug 时越有把握。

四、ANR 的案例

下面是 AMS 遇到的一些 ANR 案例

AMS 遇到 ANR 案例汇总

当你解过 1000 个 ANR 问题之后,就会发现没有什么 ANR 类型是没有遇到过的。