本文由 htkeepmoving 授权投稿原文链接: https://www.jianshu.com/p/ad1a84b6ec69
ANR 概述
首先, ANR(Application Not responding)是指应用程序未响应, Android 系统对于一些事件需要在一定的时间范围内完成, 如果超过预定时间能未能得到有效响应或者响应时间过长, 都会造成 ANR.ANR 由消息处理机制保证, Android 在系统层实现了一套精密的机制来发现 ANR, 核心原理是消息调度和超时处理.
其次, ANR 机制主体实现在系统层. 所有与 ANR 相关的消息, 都会经过系统进程 (system_server) 调度, 然后派发到应用进程完成对消息的实际处理, 同时, 系统进程设计了不同的超时限制来跟踪消息的处理. 一旦应用程序处理消息不当, 超时限制就起作用了, 它收集一些系统状态, 譬如 CPU/IO 使用情况, 进程函数调用栈, 并且报告用户有进程无响应了(ANR 对话框).
然后, ANR 问题本质是一个性能问题. ANR 机制实际上对应用程序主线程的限制, 要求主线程在限定的时间内处理完一些最常见的操作(启动服务, 处理广播, 处理输入), 如果处理超时, 则认为主线程已经失去了响应其他操作的能力. 主线程中的耗时操作, 譬如密集 CPU 运算, 大量 IO, 复杂界面布局等, 都会降低应用程序的响应能力.
哪些场景会造成 ANR?
1. 发生 ANR 时会调用 AppNotRespondingDialog.show()方法弹出对话框提示用户, 该对话框的依次调用关系如下图所示:
ANR 对话框调用关系. PNG
2. AppErrors.appNotResponding(), 该方法是最终弹出 ANR 对话框的唯一入口, 调用该方法的场景才会有 ANR 提示, 也可以认为在主线程中执行无论再耗时的任务, 只要最终不调用该方法, 都不会有 ANR 提示, 也不会有 ANR 相关日志及报告; 通过调用关系可以看出哪些场景会导致 ANR, 有以下四种场景:(1)Service Timeout:Service 在特定的时间内无法处理完成(2)BroadcastQueue Timeout:BroadcastReceiver 在特定时间内无法处理完成(3)ContentProvider Timeout: 内容提供者执行超时(4)inputDispatching Timeout: 按键或触摸事件在特定时间内无响应.
ANR 机制
ANR 机制可以分为两部分: ANR 监测机制: Android 对于不同的 ANR 类型 (Broadcast, Service, InputEvent) 都有一套监测机制. ANR 报告机制: 在监测到 ANR 以后, 需要显示 ANR 对话框, 输出日志(发生 ANR 时的进程函数调用栈, CPU 使用情况等).
整个 ANR 机制的代码也是横跨了 Android 的几个层: App 层: 应用主线程的处理逻辑; Framework 层: ANR 机制的核心, 主要有 AMS,BroadcastQueue,ActiveServices,InputmanagerService,InputMonitor,InputChannel,ProcessCpuTracker 等; Native 层: InputDispatcher.cpp;
Provider 超时机制遇到的比较少, 暂不做分析; Broadcast 目前主要想说两个知识点:
第一: 无论是普通广播还是有序广播, 最终广播接受者的 onreceive 都是串行执行的, 可以通过 Demo 进行验证;
第二: 通过 Demo 以及框架添加相关日志, 都验证了普通广播也会有 ANR 监测机制, ANR 机制以及问题分析文章认为只有串行广播才有 ANR 监测机制, 后续再会专门讲解 Broadcast 发送及接收流程, 同时也会补充 Broadcast ANR 监测机制; 本文主要以 Servie 处理超时, 输入事件分发超时为例探讨 ANR 监测机制.
Service 超时监测机制
Service 运行在应用程序的主线程, 如果 Service 的执行时间超过 20 秒, 则会引发 ANR.
当发生 Service ANR 时, 一般可以先排查一下在 Service 的生命周期函数中 (onCreate(), onStartCommand() 等)有没有做耗时的操作, 譬如复杂的运算, IO 操作等. 如果应用程序的代码逻辑查不出问题, 就需要深入检查当前系统的状态: CPU 的使用情况, 系统服务的状态等, 判断当时发生 ANR 进程是否受到系统运行异常的影响.
如何检测 Service 超时呢? Android 是通过设置定时消息实现的. 定时消息是由 AMS 的消息队列处理的(system_server 的 ActivityManager 线程). AMS 有 Service 运行的上下文信息, 所以在 AMS 中设置一套超时检测机制也是合情合理的. 我们先抛出两个问题问题一: Service 启动流程? 问题一: 如何监测 Service 超时?
主要通过以上两个问题来说明 Service 监测机制, 在知道 Service 启动流程之后, 通过 Service 启动流程可以更容易分析 Service 超时监测机制.
1. Service 启动流程如下图所示:
Servie 启动流程. PNG
(1)ActiveServices.realStartServiceLocked()在通过 App.thread 的 scheduleCreateService()来创建 Service 对象并调用 Service.onCreate()后, 接着又调用 sendServiceArgsLocked()方法来调用 Service 的其他方法, 如 onStartCommand. 以上两步均是进程间通信, 应用与 AMS 之间跨进程通信可以参考应用进程与系统进程通信 (2) 以上只是列出 Service 启动流程的关键步骤, 具体每个方法主要做哪些工作还需要查看具体的代码, 暂时先忽略这些, 感兴趣的可以参考 Android 开发艺术探索等其他相关资料
2. Service 超时监测机制 Service 超时监测机制可以从 Service 启动流程中找到.(1)ActiveServices.realStartServiceLocked()主要工作有
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord App, boolean execInFg) throws RemoteException { ... // 主要是为了设置 ANR 超时, 可以看出在正式启动 Service 之前开始 ANR 监测; bumpServiceExecutingLocked(r, execInFg, "create"); // 启动过程调用 scheduleCreateService 方法, 最终会调用 Service.onCreate 方法; App.thread.scheduleCreateService(r, r.serviceInfo, // 绑定过程中, 这个方法中会调用 App.thread.scheduleBindService 方法 requestServiceBindingsLocked(r, execInFg); // 调动 Service 的其他方法, 如 onStartCommand, 也是 IPC 通讯 sendServiceArgsLocked(r, execInFg, true); }
(2)bumpServiceExecutingLocked()会调用 scheduleServiceTimeoutLocked()方法
void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; // 在 serviceDoneExecutingLocked 中会 remove 该 SERVICE_TIMEOUT_MSG 消息, // 当超时后仍没有 remove SERVICE_TIMEOUT_MSG 消息, 则执行 ActiveServices. serviceTimeout()方法; mAm.mHandler.sendMessageDelayed(msg, proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); // 前台进程中执行 Service,SERVICE_TIMEOUT=20s; 后台进程中执行 Service,SERVICE_BACKGROUND_TIMEOUT=200s }
(3)如果在指定的时间内还没有 serviceDoneExecutingLocked()方法将消息 remove 掉, 就会调用 ActiveServices. serviceTimeout()方法
void serviceTimeout(ProcessRecord proc) { ... final long maxTime = now - (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); ... // 寻找运行超时的 Service for (int i=proc.executingServices.size()-1; i>=0; i--) { ServiceRecord sr = proc.executingServices.valueAt(i); if (sr.executingStart < maxTime) { timeout = sr; break; } ... } ... // 判断执行 Service 超时的进程是否在最近运行进程列表, 如果不在, 则忽略这个 ANR if (timeout != null && mAm.mLruProcesses.contains(proc)) { anrMessage = "executing service" + timeout.shortName; } ... if (anrMessage != null) { // 当存在 timeout 的 service, 则执行 appNotResponding, 报告 ANR mAm.appNotResponding(proc, null, null, false, anrMessage); }}
(4)Service onCreate 超时监测整体流程如下图
Service 超时监测整体流程. PNG
在 onCreate 生命周期开始执行前, 启动超时监测, 如果在指定的时间 onCreate 没有执行完毕 (该该方法中执行耗时任务), 就会调用 ActiveServices.serviceTimeout() 方法报告 ANR; 如果在指定的时间内 onCreate 执行完毕, 那么就会调用 ActivityManagerService.serviceDoneExecutingLocked()方法移除 SERVICE_TIMEOUT_MSG 消息, 说明 Service.onCreate 方法没有发生 ANR,Service 是由 AMS 调度, 利用 Handler 和 Looper, 设计了一个 TIMEOUT 消息交由 AMS 线程来处理, 整个超时机制的实现都是在 Java 层; 以上就是 Service 超时监测的整体流程.
输入事件超时监测
应用程序可以接收输入事件(按键, 触屏, 轨迹球等), 当 5 秒内没有处理完毕时, 则会引发 ANR.
这里先把问题抛出来了: 输入事件经历了一些什么工序才能被派发到应用的界面? 如何检测到输入时间处理超时?
1. Android 输入系统简介 Android 输入系统总体流程与参与者如下图所示.
输入系统总体流程及参与者. PNG
简单来说, 内核将原始事件写入到设备节点中, InputReader 在其线程循环中不断地从 EventHub 中抽取原始输入事件, 进行加工处理后将加工所得的事件放入 InputDispatcher 的派发发队列中. InputDispatcher 则在其线程循环中将派发队列中的事件取出, 查找合适的窗口, 将事件写入到窗口的事件接收管道中. 窗口事件接收线程的 Looper 从管道中将事件取出, 交由窗口事件处理函数进行事件响应. 关键流程有: 原始输入事件的读取与加工; 输入事件的派发; 输入事件的发送, 接收与反馈. 其中输入事件派发是指 InputDispatcher 不断的从派发队列取出事件, 寻找合适的窗口进行发送的过程, 输入事件的发送是 InputDispatcher 通过 Connection 对象将事件发送给窗口的过程.
InputDispatcher 与窗口之间的跨进程通信主要通过 InputChannel 来完成. 在 InputDispatcher 与窗口通过 InputChannel 建立连接之后, 就可以进行事件的发送, 接收与反馈; 输入事件的发送和接收主要流程如图所示:
输入事件发送和接收流程. PNG
其中, 将输入事件注入派发队列后, 会唤醒派发线程, 派发线程循环由 InputDispatcher.dispatchOnce 函数完成; InputDispatcher 将事件以 InputMessage 写入 InputChannel 之后, 窗口端的 looper 被唤醒, 进而执行 NativeInputReceiver::handleEvent()开始输入事件的接收, 从 InputEventReceiver 开始输入事件被派发到用户界面; 以上只是输入事件的大致流程, 更详细的流程可以参考相关资料; 在了解输入系统的大致流程之后, 我们来分析输入事件的超时监测机制.
2. 输入事件超时监测按键事件超时监测整体流程如下图所示
输入事件超时监测总体流程. PNG
(1)InputDispatcher::dispatchOnceInnerLocked(): 根据事件类型选择不同事件的处理方法: InputDispatcher::dispatchKeyLocked()或者 InputDispatcher::dispatchMotionLocked(), 我们以按键事件超时监测为例进行说明;(2)findFocusedWindowTargetsLocked()方法会调用 checkWindowReadyForMoreInputLocked(); 该方法检查窗口是否有能力再接收新的输入事件; 可能会有一系列的场景阻碍事件的继续派发, 相关场景有:
场景 1: 窗口处于 paused 状态, 不能处理输入事件 "Waiting because the [targetType] window is paused."
场景 2: 窗口还未向 InputDispatcher 注册, 无法将事件派发到窗口 "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."场景 3: 窗口和 InputDispatcher 的连接已经中断, 即 InputChannel 不能正常工作"Waiting because the [targetType] window's input connection is [status]. The window may be in the process of being removed."
场景 4: InputChannel 已经饱和, 不能再处理新的事件 "Waiting because the [targetType] window's input channel is full. Outbound queue length: %d. Wait queue length: %d."场景 5: 对于按键类型 (KeyEvent) 的输入事件, 需要等待上一个事件处理完毕"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: %d. Wait queue length: %d."场景 6: 对于触摸类型 (TouchEvent) 的输入事件, 可以立即派发到当前的窗口, 因为 TouchEvent 都是发生在用户当前可见的窗口. 但有一种情况, 如果当前应用由于队列有太多的输入事件等待派发, 导致发生了 ANR, 那 TouchEvent 事件就需要排队等待派发."Waiting to send non-key event because the %s window has not finished processing certain input events that were delivered to it over %0.1fms ago. Wait queue length: %d. Wait queue head age: %0.1fms."
以上这些场景就是我们常在日志中看到的 ANR 原因的打印.
(3)其中事件分发 5s 限制定义在 InputDispatcher.cpp;InputDispatcher::handleTargetsNotReadyLocked()方法中如果事件 5s 之内还没有分发完毕, 则调用 InputDispatcher::onANRLocked()提示用户应用发生 ANR;
// 默认分发超时间为 5sconst nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, const sp<InputApplicationHandle>& applicationHandle, const sp<InputWindowHandle>& windowHandle, nsecs_t* nextWakeupTime, const char* reason) { // 1. 如果当前没有聚焦窗口, 也没有聚焦的应用 if (applicationHandle == NULL && windowHandle == NULL) { ... } else { // 2. 有聚焦窗口或者有聚焦的应用 if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { // 获取等待的时间值 if (windowHandle != NULL) { // 存在聚焦窗口, DEFAULT_INPUT_DISPATCHING_TIMEOUT 事件为 5s timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else if (applicationHandle != NULL) { // 存在聚焦应用, 则获取聚焦应用的分发超时时间 timeout = applicationHandle->getDispatchingTimeout( DEFAULT_INPUT_DISPATCHING_TIMEOUT); } else { // 默认的分发超时时间为 5s timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT; } } } // 如果当前时间大于输入目标等待超时时间, 即当超时 5s 时进入 ANR 处理流程 // currentTime 就是系统的当前时间, mInputTargetWaitTimeoutTime 是一个全局变量, if (currentTime >= mInputTargetWaitTimeoutTime) { // 调用 ANR 处理流程 onANRLocked(currentTime, applicationHandle, windowHandle, entry->eventTime, mInputTargetWaitStartTime, reason); // 返回需要等待处理 return INPUT_EVENT_INJECTION_PENDING; } }
(4)当应用主线程被卡住的事件, 再点击该应用其它组件也是无响应, 因为事件派发是串行的, 上一个事件不处理完毕, 不会处理下一个事件.(5)Activity.onCreate 执行耗时操作, 不管用户如何操作都不会发生 ANR, 因为输入事件相关监听机制还没有建立起来; InputChannel 通道还没有建立这时是不会响应输入事件, InputDispatcher 还不能事件发送到应用窗口, ANR 监听机制也还没有建立, 所以此时是不会报告 ANR 的.(6)输入事件由 InputDispatcher 调度, 待处理的输入事件都会进入队列中等待, 设计了一个等待超时的判断, 超时机制的实现在 Native 层. 以上就是输入事件 ANR 监测机制; 具体逻辑请参考相关源码;
ANR 报告机制
无论哪种类型的 ANR 发生以后, 最终都会调用 AppErrors.appNotResponding() 方法, 所谓 "殊途同归". 这个方法的职能就是向用户或开发者报告 ANR 发生了. 最终的表现形式是: 弹出一个对话框, 告诉用户当前某个程序无响应; 输入一大堆与 ANR 相关的日志, 便于开发者解决问题.
final void appNotResponding(ProcessRecord App, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { ... if (ActivityManagerService.MONITOR_CPU_USAGE) { // 1. 更新 CPU 使用信息. ANR 的第一次 CPU 信息采样, 采样数据会保存在 mProcessStats 这个变量中 mService.updateCpuStatsNow(); } // 记录 ANR 到 EventLog 中 EventLog.writeEvent(EventLogTags.AM_ANR, App.userId, App.pid, App.processName, App.info.flags, annotation); // 输出 ANR 到 main log. StringBuilder info = new StringBuilder(); info.setLength(0); info.append("ANR in").append(App.processName); if (activity != null && activity.shortComponentName != null) { info.append("(").append(activity.shortComponentName).append(")"); } info.append("\n"); info.append("PID:").append(App.pid).append("\n"); if (annotation != null) { info.append("Reason:").append(annotation).append("\n"); } if (parent != null && parent != activity) { info.append("Parent:").append(parent.shortComponentName).append("\n"); } // 3. 打印调用栈. 具体实现由 dumpStackTraces()函数完成 File tracesFile = ActivityManagerService.dumpStackTraces( true, firstPids, (isSilentANR) ? null : processCpuTracker, (isSilentANR) ? null : lastPids, nativePids); String cpuInfo = null; // MONITOR_CPU_USAGE 默认为 true if (ActivityManagerService.MONITOR_CPU_USAGE) { // 4. 更新 CPU 使用信息. ANR 的第二次 CPU 使用信息采样. 两次采样的数据分别对应 ANR 发生前后的 CPU 使用情况 mService.updateCpuStatsNow(); synchronized (mService.mProcessCpuTracker) { // 输出 ANR 发生前一段时间内各个进程的 CPU 使用情况 cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime); } // 输出 CPU 负载 info.append(processCpuTracker.printCurrentLoad()); info.append(cpuInfo); } // 输出 ANR 发生后一段时间内各个进程的 CPU 使用率 info.append(processCpuTracker.printCurrentState(anrTime)); // 会打印发生 ANR 的原因, 如输入事件导致 ANR 的不同场景 Slog.e(TAG, info.toString()); if (tracesFile == null) { // There is no trace file, so dump (only) the alleged culprit's threads to the log // 发送 signal 3(SIGNAL_QUIT)来 dump 栈信息 Process.sendSignal(App.pid, Process.SIGNAL_QUIT); } // 将 anr 信息同时输出到 DropBox mService.addErrorToDropBox("anr", App, App.processName, activity, parent, annotation, cpuInfo, tracesFile, null); // Bring up the infamous App Not Responding dialog // 5. 显示 ANR 对话框. 抛出 SHOW_NOT_RESPONDING_MSG 消息, // AMS.MainHandler 会处理这条消息, 显示 AppNotRespondingDialog 对话框提示用户发生 ANR Message msg = Message.obtain(); HashMap<String, Object> map = new HashMap<String, Object>(); msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG; msg.obj = map; msg.arg1 = aboveSystem ? 1 : 0; map.put("app", App); if (activity != null) { map.put("activity", activity); } mService.mUiHandler.sendMessage(msg); } }
除了主体逻辑, 发生 ANR 时还会输出各种类别的日志: event log: 通过检索 "am_anr" 关键字, 可以找到发生 ANR 的应用 main log: 通过检索 "ANR in" 关键字, 可以找到 ANR 的信息, 日志的上下文会包含 CPU 的使用情况 dropbox: 通过检索 "anr" 类型, 可以找到 ANR 的信息 traces: 发生 ANR 时, 各进程的函数调用栈信息
至此 ANR 相关报告已经完成, 后续需要分析 ANR 问题, 分析 ANR 往往是从 main log 中的 CPU 使用情况和 traces 中的函数调用栈开始. 所以, 更新 CPU 的使用信息 updateCpuStatsNow()方法和打印函数栈 dumpStackTraces()方法, 是系统报告 ANR 问题关键所在, 具体分析 ANR 问题请参考相关资料.
总结
1. ANR 的监测机制: 首先分析 Service 和输入事件大致工作流程, 然后从 Service,InputEvent 两种不同的 ANR 监测机制的源码实现开始, 分析了 Android 如何发现各类 ANR. 在启动服务, 输入事件分发时, 植入超时检测, 用于发现 ANR.2. ANR 的报告机制: 分析 Android 如何输出 ANR 日志. 当 ANR 被发现后, 两个很重要的日志输出是: CPU 使用情况和进程的函数调用栈, 这两类日志是我们解决 ANR 问题的利器. 3. 监测 ANR 的核心原理是消息调度和超时处理. 4. 只有被 ANR 监测的场景才会有 ANR 报告以及 ANR 提示框.
参考资料
ANR 机制以及问题分析理解 Android ANR 的触发原理深入理解 Android 卷三(Android 输入系统)Android 开发艺术探索 Android 源码
最后, 感谢本文内容所参考文章的作者.
来源: https://juejin.im/entry/5c3e9ef6e51d4539b927dfd1