ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## 简介 ANR(Application Not Responding),应用程序无响应,简单一个定义,却涵盖了很多Android系统的设计思想。 ANR由消息处理机制保证,Android在系统层实现了一套精密的机制来发现ANR,核心原理是 **消息调度** 和 **超时处理** 。 造成ANR的场景有 * Service Timeout:比如前台服务在20s内未执行完成; * BroadcastQueue Timeout:比如前台广播在10s内未执行完成 * ContentProvider Timeout:内容提供者,在publish过超时10s; * InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。 ## ANR监测机制 ANR监测机制包含三种: * **Service ANR**,前台进程中Service生命周期不能超过20秒,后台进程中Service的生命周期不能超过200秒。 在启动Service时,抛出定时消息SERVICE\_TIMEOUT\_MSG或SERVICE\_BACKGOURND\_TIMEOUT\_MSG,如果定时消息响应了,则说明发生了ANR * **Broadcast ANR**,前台的“串行广播消息”必须在10秒内处理完毕,后台的“串行广播消息”必须在60秒处理完毕, 每派发串行广播消息到一个接收器时,都会抛出一个定时消息BROADCAST\_TIMEOUT\_MSG,如果定时消息响应,则判断是否广播消息处理超时,超时就说明发生了ANR * **Input ANR**,输入事件必须在5秒内处理完毕。在派发一个输入事件时,会判断当前输入事件是否需要等待,如果需要等待,则判断是否等待已经超时,超时就说明发生了ANR ## Service处理超时源码介绍 Service运行在应用程序的主线程,如果Service的执行时间超过20秒,则会引发ANR。 当发生Service ANR时,一般可以先排查一下在Service的生命周期函数中(onCreate(), onStartCommand()等)有没有做耗时的操作,譬如复杂的运算、IO操作等。 如何检测Service超时呢?Android是通过设置定时消息实现的。定时消息是由AMS的消息队列处理的(system\_server的ActivityManager线程)。 AMS有Service运行的上下文信息,所以在AMS中设置一套超时检测机制也是合情合理的。 Service ANR机制相对最为简单,主体实现在[ActiveServices.java,**realStartServiceLocked** 中。 当Service的生命周期开始时,**bumpServiceExecutingLocked** 会被调用,紧接着会调**scheduleServiceTimeoutLocked**ActiveServices.java ### 埋炸弹 ActiveServices(mAm.mHandler ) ~~~ private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... //发送delay消息(SERVICE_TIMEOUT_MSG),【见小节2.1.2】 bumpServiceExecutingLocked(r, execInFg, "create"); try { ... //最终执行服务的onCreate()方法 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); } catch (DeadObjectException e) { mAm.appDiedLocked(app); throw e; } finally { ... } } private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { ... scheduleServiceTimeoutLocked(r.app); } void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } long now = SystemClock.uptimeMillis(); Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; //当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程【见2.3.1】 mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT)); } ~~~ ### 拆炸弹 ActivityThread.java ~~~ private void handleCreateService(CreateServiceData data) { ... java.lang.ClassLoader cl = packageInfo.getClassLoader(); Service service = (Service) cl.loadClass(data.info.name).newInstance(); ... try { //创建ContextImpl对象 ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); //创建Application对象 Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); //调用服务onCreate()方法 service.onCreate(); //拆除炸弹引线[见小节2.2.2] ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (Exception e) { ... } } ~~~ ### 引爆炸弹 ~~~ final class MainHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case SERVICE_TIMEOUT_MSG: { ... //【见小节2.3.2】 mServices.serviceTimeout((ProcessRecord)msg.obj); } break; ... } ... } } ~~~ serviceTimeout ~~~ void serviceTimeout(ProcessRecord proc) { String anrMessage = null; synchronized(mAm) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } final long now = SystemClock.uptimeMillis(); final long maxTime = now - (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); ServiceRecord timeout = null; long nextTime = 0; for (int i=proc.executingServices.size()-1; i>=0; i--) { ServiceRecord sr = proc.executingServices.valueAt(i); if (sr.executingStart < maxTime) { timeout = sr; break; } if (sr.executingStart > nextTime) { nextTime = sr.executingStart; } } if (timeout != null && mAm.mLruProcesses.contains(proc)) { Slog.w(TAG, "Timeout executing service: " + timeout); StringWriter sw = new StringWriter(); PrintWriter pw = new FastPrintWriter(sw, false, 1024); pw.println(timeout); timeout.dump(pw, " "); pw.close(); mLastAnrDump = sw.toString(); mAm.mHandler.removeCallbacks(mLastAnrDumpClearer); mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS); anrMessage = "executing service " + timeout.shortName; } } if (anrMessage != null) { //当存在timeout的service,则执行appNotResponding mAm.appNotResponding(proc, null, null, false, anrMessage); } ~~~ ## Input处理超时 ![](https://img.kancloud.cn/a6/9f/a69fd2570e083d83ac74e55bfd11411f_863x231.png) 内核将原始事件写入到设备节点中,InputReader在其线程循环中不断地从EventHub中抽取原始输入事件,进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出,查找合适的窗口,将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出,交由窗口事件处理函数进行事件响应。关键流程有:原始输入事件的读取与加工;输入事件的派发;输入事件的发送、接收与反馈。其中输入事件派发是指InputDispatcher不断的从派发队列取出事件、寻找合适的窗口进行发送的过程,输入事件的发送是InputDispatcher通过Connection对象将事件发送给窗口的过程。 InputDispatcher与窗口之间的跨进程通信主要通过InputChannel来完成。在InputDispatcher与窗口通过InputChannel建立连接之后,就可以进行事件的发送、接收与反馈;输入事件的发送和接收主要流程如图所示 InputDispatcher作为中枢,不停地在递送着输入事件,当一个事件无法得到处理的时候,InputDispatcher不能就此死掉啊,否则系统也太容易崩溃了。 **InputDispatcher的策略是放弃掉处理不过来的事件,并发出通知(这个通知机制就是ANR)**,继续进行下一轮消息的处理。 事件分发5s限制定义在InputDispatcher.cppInputDispatcher::handleTargetsNotReadyLocked方法中如果事件5s之内还没有分发完毕,则调用InputDispatcher::onANRLocked()提示用户应用发生ANR; ~~~ //默认分发超时间为5s const 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; } } ~~~ 当应用主线程被卡住的事件,再点击该应用其它组件也是无响应,因为事件派发是串行的,上一个事件不处理完毕,不会处理下一个事件。 Activity.onCreate执行耗时操作,不管用户如何操作都不会发生ANR,因为输入事件相关监听机制还没有建立起来;InputChannel通道还没有建立这时是不会响应输入事件,InputDispatcher还不能事件发送到应用窗口,ANR监听机制也还没有建立,所以此时是不会报告ANR的。 ## ANR的报告机制 无论哪种类型的ANR发生以后,最终都会调用 AMS.appNotResponding() 方法,所谓“殊途同归”。这个方法的职能就是向用户或开发者报告ANR发生了。 最终的表现形式是:弹出一个对话框,告诉用户当前某个程序无响应;输入一大堆与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); } ~~~ 该方法的主体逻辑可以分成五个部分来看: * 更新CPU的统计信息。这是发生ANR时,第一次CPU使用信息的采样,采样数据会保存在mProcessStats这个变量中 * 填充firstPids和lastPids数组。当前发生ANR的应用会首先被添加到firstPids中,这样打印函数栈的时候,当前进程总是在trace文件的最前面 * 打印函数调用栈(StackTrace)。具体实现由dumpStackTraces()函数完成 * 更新CPU的统计信息。这是发生ANR时,第二次CPU使用信息的采样,两次采样的数据分别对应ANR发生前后的CPU使用情况 * 显示ANR对话框。抛出SHOW\_NOT\_RESPONDING\_MSG消息,AMS.MainHandler会处理这条消息,显示AppNotRespondingDialog 当然,除了主体逻辑,发生ANR时还会输出各种类别的日志: * event log,通过检索”am\_anr”关键字,可以找到发生ANR的应用 * main log,通过检索”ANR in “关键字,可以找到ANR的信息,日志的上下文会包含CPU的使用情况 * dropbox,通过检索”anr”类型,可以找到ANR的信息 * traces, 发生ANR时,各进程的函数调用栈信息 我们分析ANR问题,往往是从main log中的CPU使用情况和traces中的函数调用栈开始。所以,更新CPU的使用信息updateCpuStatsNow()方法和打印函数栈dumpStackTraces()方法,是系统报告ANR问题关键所在。 ## 参考资料 [ANR机制以及问题分析](https://duanqz.github.io/2015-10-12-ANR-Analysis) [理解Android ANR的触发原理](http://gityuan.com/2016/07/02/android-anr/)