ANR分析(一)

106 阅读5分钟

一、ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。一般地,这时往往会弹出一个提示框,告知用户当前xxx未响应,用户可选择继续等待或者Force Close。

主要场景: Service Timeout:前台服务在20s内未执行完成;

BroadcastQueue Timeout:前台广播在10s内未执行完成

ContentProvider Timeout:内容提供者在publish过超时10s;

InputDispatching Timeout:通常是activity 页面中输入事件分发超时5s,包括按键和触摸事件。

二、ANR的弹出提示

   发生ANR时会调用AppNotRespondingDialog.show()方法弹出对话框提示用户,该对话框的依次调用关系:

AppErrors.appNotResponding(); //ANR对话框的唯一入口

AMS.UiHandler.sendMessage(ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG);

AppErrors.handleShowAnrUi();

AppNotRespondingDialog.show();

根据造成ANR的场景,产生ANR的来源可以总结为两大类:组件类ANR和Input ANR

(一)组件超时机制 对于​Service、Broadcast、Provider​组件类的​ANR​而言,如果把发生ANR比作是引爆炸弹,那么整个流程包含三部分组成:

埋炸弹:中控系统(system_server进程)启动倒计时,在规定时间内如果目标(应用进程)没有干完所有的活,则中控系统会定向炸毁(杀进程)目标。 拆炸弹:在规定的时间内干完工地的所有活,并及时向中控系统报告完成,请求解除定时炸弹,则幸免于难。 引爆炸弹:中控系统立即封装现场,抓取快照,搜集目标执行慢的罪证(traces),便于后续调试分析,最后是炸毁目标。 bumpServiceExecutingLocked;

scheduleServiceTimeoutLocked; //监听Service运行时间

AMS.post(SERVICE_TIMEOUT_MSG); //记录executeingService并发送超时消息

ActiveServices.serviceTimeout; //判断是否超时

AppErrors.appNotResponding; bumpServiceExecutingLocked 在很多地方被调用:

requestServiceBindingLocked(ServiceRecord, IntentBindRecord, boolean, boolean) realStartServiceLocked(ServiceRecord, ProcessRecord, boolean) sendServiceArgsLocked(ServiceRecord, boolean, boolean) bringDownServiceLocked(ServiceRecord) removeConnectionLocked(ConnectionRecord, ProcessRecord, ActivityRecord) 每个调用的地方都对应着Service的一个生命周期,也就是说Service的每个生命周期开始时都会调用到​scheduleServiceTimeoutLocked​用于监听Service运行的时间。

(二)输入事件超时机制

    Input​类型的ANR在日常开发中更为常见且更复杂,比如用户或者测试反馈,点击屏幕中的UI元素导致卡死。

    与组件类ANR不同的是,Input类型的超时机制并非时间到了一定就会爆炸,而是处理后续上报事件的过程才会去检测是否该爆炸,所以更像是扫雷过程。

    什么叫做扫雷呢,对于输入系统而言,即使某次事件执行时间超过预期的时长,只要用户后续没有再生成输入事件,那么也不需要ANR。而只有当新一轮的输入事件到来,此时正在分发事件的窗口(即App应用本身)迟迟无法释放资源给新的事件去分发,这时​InputDispatcher​才会根据超时时间,动态的判断是否需要向对应的窗口提示ANR信息。

    InputDispatcher​的源码实现中,整体的事件分发流程共使用到3个事件队列:

inBoundQueue:用于记录​InputReader​发送过来的输入事件; outBoundQueue:用于记录即将分发给目标应用窗口的输入事件; waitQueue:用于记录已分发给目标应用,且应用尚未处理完成的输入事件。

   1. 第一轮事件分发

    首先​InputReader​线程 通过​EventHub​监听到底层的输入事件上报,并将其放入了​inBoundQueue​中,同时唤醒了​InputDispatcher​线程。

    然后​InputDispatcher​开始了第一轮的事件分发,此时并没有正在处理的事件,因此​InputDispatcher​从​inBoundQueue​队列头部取出事件,并重置ANR的计时,并检查窗口是否就绪,此时窗口准备就绪,将该事件转移到了​outBoundQueue​队列中,因为应用管道对端连接正常,因此事件从​outBoundQueue​取出,然后放入了​waitQueue​队列,因为​Socket​双向通信已经建立,接下来就是 应用进程 接收到新的事件,然后对其进行分发。

    如果应用进程事件分发正常,那么会通过​Socket​向​system_server​通知完成,则对应的事件最终会从​waitQueue​队列中移除。

   2. 第二轮事件分发

    如果第一轮事件分发尚未接收到回调通知,第二轮事件分发抵达又是如何处理的呢?

    第二轮事件到达​InputDispatcher​时,此时​InputDispatcher​发现有事件正在处理,因此不会从​inBoundQueue​取出新的事件,而是直接检查窗口是否就绪,若未就绪,则进入ANR检测状态。

    至此,输入系统检测到了ANR的发生,并向上层抛出了本次ANR的相关信息。

    这里我们来总结一下,有哪些路径会引发ANR? 



    从埋下定时炸弹到拆炸弹之间的任何一个或多个路径执行慢都会导致ANR(以service为例),可以是service的生命周期的回调方法(比如onStartCommand)执行慢,可以是主线程的消息队列存在其他耗时消息让service回调方法迟迟得不到执行,可以是SP操作执行慢,可以是system_server进程的binder线程繁忙而导致没有及时收到拆炸弹的指令。