ANR

Love The Way You Lie 2022-10-01 11:47 183阅读 0赞

ANR

ANR(Application Not Responding),应用程序无响应。ANR 属于应用程序的范畴,ANR 由消息处理机制保证,Android 在系统层实现了一套精密的机制来发现 ANR,核心原理是消息调度超时处理

其次,ANR 机制主体实现在系统层。所有与 ANR 相关的消息,都会经过系统进程(system_server)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。一旦应用程序处理消息不当,超时限制就起作用了,它收集一些系统状态,例如:CPU/IO使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR 对话框)。

然后,ANR 问题本质是一个性能问题。ANR 机制实际上对应用程序主线程的限制,要求主线程在限定的时间内处理完一些最常见的操作(启动服务、处理广播、处理输入),如果处理超时,则认为主线程已经失去了响应其他操作的能力。主线程中的耗时操作,例如:密集CPU运算、大量IO、复杂界面布局等,都会降低应用程序的响应能力。

ANR 机制

ANR 机制可以分为两部分:

ANR 的监测:Android 对于不同的 ANR 类型(Broadcast,Service,InputEvent)都有一套监测机制。

ANR 的报告:在监测到 ANR 以后,需要显示 ANR 对话框、输出日志(发生 ANR 时的进程函数调用栈、CPU 使用情况等)。

ANR 的触发原因

谷歌文档中对 ANR 产生的原因是这么描述的:

Android 系统中的应用被 ActivityManagerService 及 WindowManagerService 两个系统服务监控着,系统会在如下两种情况展示出 ANR 的对话框!

KeyDispatchTimeout ( 5 seconds ) :按键或触摸事件在特定时间内无响应

BroadcastTimeout ( 10 seconds ):BroadcastReceiver 在特定时间内无法处理完成

ServiceTimeout ( 20 seconds ) :Service 在特定的时间内无法处理完成

Service 超时监测机制

Service 运行在应用程序的主线程,如果 Service 的执行时间超过 20 秒,则会引发 ANR。

当发生 Service ANR 时,一般可以先排查一下在 Service 的生命周期函数中有没有做耗时的操作,例如复杂的运算、IO 操作等。如果应用程序的代码逻辑查不出问题,就需要深入检查当前系统的状态:CPU 的使用情况、系统服务的状态等,判断当时发生 ANR 进程是否受到系统运行异常的影响。

系统是如何检测 Service 超时的呢?Android 是通过设置定时消息实现的。定时消息是由 AMS 的消息队列处理的,AMS 有 Service 运行的上下文信息,所以在 AMS 中设置一套超时检测机制也是合情合理的。

Service ANR 机制相对最为简单,主体实现在ActiveServices中。

在 Service 的启动流程中,Service 进程 attach 到 system_server 进程后会调用 realStartServiceLocked() 方法。

  1. // frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
  2. public final class ActiveServices {
  3. private final void realStartServiceLocked(ServiceRecord r,
  4. ProcessRecord app, boolean execInFg) throws RemoteException {
  5. // 发送 delay 消息(SERVICE_TIMEOUT_MSG)
  6. bumpServiceExecutingLocked(r, execInFg, "create");
  7. boolean created = false;
  8. try {
  9. // 最终执行服务的 onCreate() 方法
  10. app.thread.scheduleCreateService(r, r.serviceInfo, mAm.
  11. compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
  12. app.repProcState);
  13. ... ...
  14. }
  15. }
  16. }
  17. 复制代码
  18. private final void bumpServiceExecutingLocked(...) {
  19. scheduleServiceTimeoutLocked(r.app);
  20. }
  21. 复制代码
  22. void scheduleServiceTimeoutLocked(ProcessRecord proc) {
  23. if (proc.executingServices.size() == 0 || proc.thread == null) {
  24. return;
  25. }
  26. Message msg = mAm.mHandler.obtainMessage(
  27. ActivityManagerService.SERVICE_TIMEOUT_MSG);
  28. msg.obj = proc;
  29. // 当超时后仍没有 remove 该 SERVICE_TIMEOUT_MSG 消息,
  30. // 通过 AMS.MainHandler 抛出一个定时消息。
  31. mAm.mHandler.sendMessageDelayed(msg,
  32. proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
  33. }
  34. 复制代码

AMS.MainHandler 抛出一个定时消息 SERVICE_TIMEOUT_MSG。

前台进程中执行 Service,超时时间是 SERVICE_TIMEOUT(20 秒)

  1. // How long we wait for a service to finish executing.
  2. static final int SERVICE_TIMEOUT = 20*1000;
  3. 复制代码

后台进程中执行 Service,超时时间是 SERVICE_BACKGROUND_TIMEOUT(200 秒)

  1. // How long we wait for a service to finish executing.
  2. static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
  3. 复制代码

当 Service 的生命周期结束时(不会 ANR),会调用 serviceDoneExecutingLocked() 方法,之前抛出的 SERVICE_TIMEOUT_MSG 消息在这个方法中会被清除。

  1. void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
  2. boolean inDestroying = mDestroyingServices.contains(r);
  3. if (r != null) {
  4. ... ...
  5. serviceDoneExecutingLocked(r, inDestroying, inDestroying);
  6. }
  7. }
  8. private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
  9. boolean finishing) {
  10. ... ...
  11. if (r.executeNesting <= 0) {
  12. if (r.app != null) {
  13. ... ...
  14. // 当前服务所在进程中没有正在执行的service,清除 SERVICE_TIMEOUT_MSG 消息
  15. if (r.app.executingServices.size() == 0) {
  16. mAm.mHandler.removeMessages(
  17. ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
  18. ... ...
  19. }
  20. ... ...
  21. }
  22. 复制代码

如果没有 Remove 掉 SERVICE_TIMEOUT_MSG,在 system_server 进程中有一个 Handler 线程,名叫 ActivityManager。

如果在超时时间内,SERVICE_TIMEOUT_MSG 没有被清除,便会向该 Handler 线程发送一条信息 SERVICE_TIMEOUT_MSG。

  1. final class MainHandler extends Handler {
  2. ... ...
  3. @Override
  4. public void handleMessage(Message msg) {
  5. switch (msg.what) {
  6. ... ...
  7. case SERVICE_TIMEOUT_MSG: {
  8. mServices.serviceTimeout((ProcessRecord)msg.obj);
  9. } break;
  10. ... ...
  11. }
  12. }
  13. 复制代码
  14. void serviceTimeout(ProcessRecord proc) {
  15. String anrMessage = null;
  16. synchronized(mAm) {
  17. ... ...
  18. long nextTime = 0;
  19. // 寻找运行超时的 Service
  20. for (int i = proc.executingServices.size() - 1; i >= 0; i--) {
  21. ServiceRecord sr = proc.executingServices.valueAt(i);
  22. if (sr.executingStart < maxTime) {
  23. timeout = sr;
  24. break;
  25. }
  26. if (sr.executingStart > nextTime) {
  27. nextTime = sr.executingStart;
  28. }
  29. }
  30. // 判断执行 Service 超时的进程是否在最近运行进程列表,如果不在,则忽略这个 ANR
  31. if (timeout != null && mAm.mLruProcesses.contains(proc)) {
  32. Slog.w(TAG, "Timeout executing service: " + timeout);
  33. StringWriter sw = new StringWriter();
  34. PrintWriter pw = new FastPrintWriter(sw, false, 1024);
  35. pw.println(timeout);
  36. timeout.dump(pw, " ");
  37. pw.close();
  38. mLastAnrDump = sw.toString();
  39. mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
  40. mAm.mHandler.postDelayed(mLastAnrDumpClearer,
  41. LAST_ANR_LIFETIME_DURATION_MSECS);
  42. anrMessage = "executing service " + timeout.shortName;
  43. ... ...
  44. }
  45. if (anrMessage != null) {
  46. // 当存在 timeout 的 service,则执行 appNotResponding
  47. mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
  48. }
  49. }
  50. 复制代码

上述方法会找到当前进程已经超时的 Service,经过一些判定后,决定要报告 ANR,最终调用 AMS.appNotResponding() 方法。

走到这一步,ANR 机制已经完成了监测报告任务,剩下的任务就是 ANR 结果的输出,我们称之为 ANR 的报告机制。ANR 的报告机制是通过 AMS.appNotResponding() 完成的,Broadcast 和 InputEvent 类型的 ANR 最终也都会调用这个方法。

Broadcast 超时处理

应用程序可以注册广播接收器,实现 BroadcastReceiver.onReceive() 方法来完成对广播的处理。通常,这个方法是在主线程执行的,Android 限定它执行时间不能超过10秒,否则,就会引发 ANR。

onReceive() 也可以调度在其他线程执行,通过 Context.registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) 这个方法注册广播接收器,可以指定一个处理的 Handler,将 onReceive() 调度在非主线程执行。

广播消息的调度

AMS 维护了两个广播队列 BroadcastQueue:

foreground queue,前台队列的超时时间是 10 秒。

background queue,后台队列的超时时间是 60 秒。

之所以有两个,就是因为要区分不同超时时间。所有发送的广播都会进入到队列中等待调度,在发送广播时,可以通过 Intent.FLAG_RECEIVER_FOREGROUND 参数将广播投递到前台队列。

AMS 会不断地从队列中取出广播消息派发到各个 BroadcastReceiver。当要派发广播时,AMS 会调用 BroadcastQueue.scheduleBroadcastsLocked() 方法!

  1. public final class BroadcastQueue {
  2. public void scheduleBroadcastsLocked() {
  3. if (mBroadcastsScheduled) { return; }
  4. mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
  5. mBroadcastsScheduled = true;
  6. }
  7. }
  8. 复制代码

上述方法中,往 AMS 的消息队列发送 BROADCAST_INTENT_MSG 消息,由此也可以看到真正派发广播的是 AMS 线程。由于上述方法可能被并发调用,所以通过 mBroadcastsScheduled 这个变量来标识 BROADCAST_INTENT_MSG 是不是已经被 AMS 接收了,当已经抛出的消息还未被接受时,不需要重新抛出。

  1. public final class BroadcastQueue {
  2. private final class BroadcastHandler extends Handler {
  3. public BroadcastHandler(Looper looper) {
  4. super(looper, null, true);
  5. }
  6. @Override public void handleMessage(Message msg) {
  7. switch (msg.what) {
  8. case BROADCAST_INTENT_MSG: {
  9. processNextBroadcast(true);
  10. } break;
  11. case BROADCAST_TIMEOUT_MSG: {
  12. synchronized (mService) {
  13. broadcastTimeoutLocked(true);
  14. } } break;
  15. } } } }
  16. 复制代码

接下来调用 BroadcastQueue.processNextBroadcast() 方法,参数为 true 表示这是一次来自 BROADCAST_INTENT_MSG 消息的派发请求。

阶段 1:处理并行广播信息

  1. final void processNextBroadcast(boolean fromMsg) {
  2. synchronized(mService) {
  3. BroadcastRecord r;
  4. ... ...
  5. //阶段一:处理非串行广播消息
  6. 设置mBroadcastsScheduled
  7. if (fromMsg) {
  8. // fromMsg 参数为 true 表示这是一次来自 BROADCAST_INTENT_MSG 消息的派发请求
  9. mBroadcastsScheduled = false;
  10. }
  11. 处理“并行广播消息”
  12. while (mParallelBroadcasts.size() > 0) {
  13. ... ...
  14. final int N = r.receivers.size();
  15. for (int i=0; i<N; i++) {
  16. Object target = r.receivers.get(i);
  17. ... ...
  18. deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
  19. }
  20. addBroadcastToHistoryLocked(r);
  21. ... ...
  22. }
  23. // ③ 处理阻塞的广播消息
  24. if (mPendingBroadcast != null) {
  25. ... ...
  26. if (!isDead) {
  27. // It's still alive, so keep waiting // isDead表示当前广播消息的进程的存活状态 // 如果还活着,则返回该函数,继续等待下次派发 return; } ... ... } 复制代码

1、设置mBroadcastsScheduled

该变量在前文说过,是对 BROADCAST_INTENT_MSG 进行控制。 如果是响应 BROADCAST_INTENT_MSG 的派发调用,则将 mBroadcastsScheduled 设为false, 表示本次 BROADCAST_INTENT_MSG 已经处理完毕,可以继续抛出下一次 BROADCAST_INTENT_MSG 消息了。

2、处理“并行广播消息”

我们知道广播接收器有“动态”和“静态”之分,通过 Context.registerReceiver() 注册的广播接收器为“动态”的,通过 AndroidManifest.xml 注册的广播接收器为“静态”的。

广播消息有“并行”和“串行”之分,“并行广播消息”都会派发到“动态”接收器,“串行广播消息”则会根据实际情况派发到两种接收器。

在BroadcastQueue维护着两个队列:

mParallelBroadcasts:“并行广播消息”都会进入到此队列中排队。“并行广播消息”可以一次性派发完毕,即在一个循环中将广播派发到所有的“动态”接收器。

mOrderedBroadcasts:“串行广播消息”都会进入到此队列中排队。“串行广播消息”需要轮侯派发,当一个接收器处理完毕后,会再抛出 BROADCAST_INTENT_MSG 消息,再次进入 BroadcastQueue.processNextBroadcast() 处理下一个。

3、处理阻塞的广播消息

有时候会存在一个广播消息派发不出去的情况,这个广播消息会保存在 mPendingBroadcast 变量中。新一轮的派发启动时,会判断接收该消息的进程是否还活着,如果接收进程还活着,那么就继续等待。否则,就放弃这个广播消息。

阶段 2:处理串行广播信息

接下来是最为复杂的一部分,处理“串行广播消息”,ANR 监测机制只在这一类广播消息中才发挥作用,也就是说“并行广播消息”是不会发生ANR的。

  1. final void processNextBroadcast(boolean fromMsg) {
  2. synchronized(mService) {
  3. BroadcastRecord r;
  4. ... ...
  5. 阶段一:处理并行广播消息
  6. 阶段二:处理串行广播消息
  7. boolean looped = false;
  8. do {
  9. ... ...
  10. r = mOrderedBroadcasts.get(0);
  11. boolean forceReceive = false;
  12. int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
  13. // ① 广播消息的第一个ANR监测机制
  14. if (mService.mProcessesReady && r.dispatchTime > 0) {
  15. long now = SystemClock.uptimeMillis();
  16. if ((numReceivers > 0) && (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers)) && !mService.mAnrManager.isAnrDeferrable()) {
  17. broadcastTimeoutLocked(false);
  18. // forcibly finish this broadcast
  19. forceReceive = true;
  20. r.state = BroadcastRecord.IDLE;
  21. }
  22. }
  23. ... ...
  24. // ② 判断该广播消息是否处理完毕
  25. if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) {
  26. ... ...
  27. cancelBroadcastTimeoutLocked();
  28. ... ...
  29. mOrderedBroadcasts.remove(0);
  30. r = null;
  31. looped = true;
  32. continue;
  33. }
  34. } while (r == null);
  35. }
  36. 复制代码

这部分是一个 do-while 循环,每次都从 mOrderedBroadcasts 队列中取出第一条广播消息进行处理。第一个 Broadcast ANR 监测机制千呼万唤总算是出现了:

1、判定当前时间是否已经超过了 r.dispatchTime + 2×mTimeoutPeriod×numReceivers。

dispatchTime 表示这一系列广播消息开始派发的时间。“串行广播消息”是逐个接收器派发的,一个接收器处理完毕后,才开始处理下一个消息派发。开始派发到第一个接收器的时间就是 dispatchTime。 dispatchTime 需要开始等广播消息派发以后才会设定,也就是说,第一次进入processNextBroadcast()时,dispatchTime=0,并不会进入该条件判断。

mTimeoutPeriod 由当前 BroadcastQueue 的类型决定(forground为10秒,background为60秒)。这个时间在初始化 BroadcastQueue 的时候就设置好了,本意是限定每一个 Receiver 处理广播的时间,这里利用它做了一个超时计算。

2、如果广播消息已经处理完毕,则从mOrderedBroadcasts中移除,重新循环,处理下一条;否则,就会跳出循环。

阶段 3:设定定时消息

以上代码块完成的主要任务是从队列中取一条“串行广播消息”,接下来就准备派发了。

  1. final void processNextBroadcast(boolean fromMsg) {
  2. synchronized(mService) {
  3. BroadcastRecord r;
  4. ... ...
  5. 阶段一:处理并行广播消息
  6. 阶段二:处理串行广播消息
  7. 阶段三:设定定时消息
  8. ... ...
  9. // 串行广播消息的第二个ANR监测机制
  10. r.receiverTime = SystemClock.uptimeMillis();
  11. ... ...
  12. if (! mPendingBroadcastTimeoutMessage) {
  13. long timeoutTime = r.receiverTime + mTimeoutPeriod;
  14. setBroadcastTimeoutLocked(timeoutTime);
  15. }
  16. 复制代码

取出“串行广播消息”后,一旦要开始派发,第二个ANR检测机制就出现了。

mPendingBroadcastTimeoutMessage 变量用于标识当前是否有阻塞的超时消息,如果没有则调用 BroadcastQueue.setBroadcastTimeoutLocked():

  1. final void setBroadcastTimeoutLocked(long timeoutTime) {
  2. if (! mPendingBroadcastTimeoutMessage) {
  3. Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
  4. mHandler.sendMessageAtTime(msg, timeoutTime);
  5. mPendingBroadcastTimeoutMessage = true;
  6. mService.mAnrManager.sendBroadcastMonitorMessage(timeoutTime, mTimeoutPeriod);
  7. }
  8. }
  9. 复制代码

通过设置一个定时消息 BROADCAST_TIMEOUT_MSG 来跟踪当前广播消息的执行情况,这种超时监测机制跟 Service ANR 很类似,也是抛到 AMS 线程的消息队列。如果所有的接收器都处理完毕了,则会调用 cancelBroadcastTimeoutLocked() 清除该消息;否则,该消息就会响应,并调用 broadcastTimeoutLocked(),这个方法在第一种 ANR 监测机制的时候调用过,第二种 ANR 监测机制也会调用。

阶段 4:向“动态”接收器派发广播消息

设置完定时消息后,就开始派发广播消息了,首先是“动态”接收器:

  1. final void processNextBroadcast(boolean fromMsg) {
  2. synchronized(mService) {
  3. BroadcastRecord r;
  4. ... ...
  5. 阶段一:处理并行广播消息
  6. 阶段二:处理串行广播消息
  7. 阶段三:设定定时消息
  8. 阶段四:向“动态”接收器派发广播消息
  9. final BroadcastOptions brOptions = r.options;
  10. final Object nextReceiver = r.receivers.get(recIdx);
  11. // 动态接收器的类型都是BroadcastFilter
  12. if (nextReceiver instanceof BroadcastFilter) {
  13. BroadcastFilter filter = (BroadcastFilter)nextReceiver;
  14. deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
  15. ... ...
  16. return;
  17. }
  18. 复制代码

“动态”接收器的载体进程一般是处于运行状态的,所以向这种类型的接收器派发消息相对简单,调用 BroadcastQueue.deliverToRegisteredReceiverLocked() 完成接下来的工作。

阶段 5:向“静态”接收器派发广播消息

“静态”接收器是在AndroidManifest.xml中注册的,派发的时候,可能广播接收器的载体进程还没有启动,所以,这种场景会复杂很多。

  1. final void processNextBroadcast(boolean fromMsg) {
  2. synchronized(mService) {
  3. BroadcastRecord r;
  4. ... ...
  5. 阶段一:处理并行广播消息
  6. 阶段二:处理串行广播消息
  7. 阶段三:设定定时消息
  8. 阶段四:向“动态”接收器派发广播消息 阶段五:向“静态”接收器派发广播消息
  9. // 静态接收器的类型都是 ResolveInfo
  10. ResolveInfo info = (ResolveInfo)nextReceiver;
  11. // ① 权限检查
  12. ComponentName component = new ComponentName(info.activityInfo.applicationInfo.packageName, info.activityInfo.name);
  13. ... ...
  14. String targetProcess = info.activityInfo.processName;
  15. // ② 获取接收器所在的进程
  16. ProcessRecord app = mService.getProcessRecordLocked(targetProcess, info.activityInfo.applicationInfo.uid, false);
  17. ... ...
  18. // ③ 进程已经启动
  19. // Is this receiver's application already running? if (app != null && app.thread != null && !app.killed) { try { app.addPackage(info.activityInfo.packageName, info.activityInfo.applicationInfo.versionCode, mService.mProcessStats); processCurBroadcastLocked(r, app); return; } catch (RemoteException e) { ... ... } } // ④ 进程还未启动 if ((r.curApp=mService.startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, "broadcast", r.curComponent, (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false)) == null) { logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false); scheduleBroadcastsLocked(); r.state = BroadcastRecord.IDLE; return; } // ⑤ 进程启动失败 mPendingBroadcast = r; mPendingBroadcastRecvIndex = recIdx; } } 复制代码

1、权限检查

“静态”接收器是 ResolveInfo ,需要通过 PackageManager 获取包信息,进行权限检查。

2、获取接收器所在的进程

经过一系列复杂的权限检查后,终于可以向目标接收器派发了。通过 AMS.getProcessRecordLocked() 获取广播接收器的进程信息。

3、进程已经启动

如果 app.thread != null ,则进程已经启动,就可以调用 BroadcastQueue.processCurBroadcastLocked() 进行接下来的派发处理了。

4、进程还未启动

如果进程还没有启动,则需要通过 AMS.startProcessLocked() 来启动进程,当前消息并未派发,调用 BroadcastQueue.scheduleBroadcastsLocked() 进入下一次的调度。

5、进程启动失败

如果进程启动失败了,则当前消息记录成 mPendingBroadcast ,即阻塞的广播消息,等待下一次调度时处理。

最终有两个方法将广播消息派发出去: BroadcastQueue.deliverToRegisteredReceiverLocked() 和 BroadcastQueue.processCurBroadcastLocked()。

抛开这两个函数的逻辑,试想要将广播消息从 AMS 所在的 system_server 进程传递到应用程序的进程,该怎么实现?

自然需要用到跨进程调用,Android中最常规的手段就是 Binder机制

对于应用程序已经启动(app.thread != null)的情况,会通过 IApplicationThread 发起跨进程调用:

  1. // Is this receiver's application already running? if (app != null && app.thread != null && !app.killed) { try { app.addPackage(info.activityInfo.packageName, info.activityInfo.applicationInfo.versionCode, mService.mProcessStats); processCurBroadcastLocked(r, app); 复制代码
  2. private final void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) throws RemoteException {
  3. ... ...
  4. boolean started = false;
  5. try {
  6. mService.notifyPackageUse(r.intent.getComponent().getPackageName(), PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
  7. // 通过 IApplicationThread 发起跨进程调用
  8. app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, app.repProcState);
  9. started = true;
  10. } finally {
  11. ... ...
  12. }
  13. }
  14. 复制代码
  15. ActivityThread.ApplicationThread.scheduleReceiver()
  16. └── ActivityThread.handleReceiver()
  17. └── BroadcastReceiver.onReceive()
  18. 复制代码

对于应用程序还未启动的情况,会调用 IIntentReceiver 发起跨进程调用,应用进程的实现在LoadedApk.ReceiverDispatcher.IntentReceiver中,调用关系如下:

  1. LoadedApk.ReceiverDispatcher.IntentReceiver.performReceive()
  2. └── LoadedApk.ReceiverDispatcher.performReceiver()
  3. └── LoadedApk.ReceiverDispatcher.Args.run()
  4. └── BroadcastReceiver.onReceive()
  5. 复制代码

最终都会调用到 BroadcastReceiver.onReceive() ,在应用进程执行接收广播消息的具体动作。

对于“串行广播消息”而言,执行完了以后,还需要通知 system_server 进程,才能继续将广播消息派发到下一个接收器,这又需要跨进程调用了。

应用进程在处理完广播消息后,即在 BroadcastReceiver.onReceive() 执行完毕后,会调用 BroadcastReceiver.PendingResult.finish() , 接下来的调用关系如下:

  1. BroadcastReceiver.PendingResult.finish()
  2. └── BroadcastReceiver.PendingResult.sendFinished()
  3. └── IActivityManager.finishReceiver()
  4. └── ActivityManagerService.finishReceiver()
  5. └── BroadcastQueue.processNextBroadcat()
  6. 复制代码

通过IActivityManager发起了一个从应用进程到 system_server 进程的调用,最终在AMS线程中,又走到了 BroadcastQueue.processNextBroadcat(), 开始下一轮的调度。

两种 ANR 机制最终都会调用 BroadcastQueue.broadcastTimeoutLocked() 方法, 第一种 ANR 监测生效时,会将 fromMsg 设置为 false ;第二种 ANR 监测生效时,会将 fromMsg 参数为 true 时,表示当前正在响应 BROADCAST_TIMEOUT_MSG 消息。

  1. final void broadcastTimeoutLocked(boolean fromMsg) {
  2. // ① 设置mPendingBroadcastTimeoutMessage
  3. if (fromMsg) {
  4. mPendingBroadcastTimeoutMessage = false;
  5. mService.mAnrManager.removeBroadcastMonitorMessage();
  6. }
  7. ... ...
  8. long now = SystemClock.uptimeMillis();
  9. // ② 判断第二种ANR机制是否超时
  10. BroadcastRecord r = mOrderedBroadcasts.get(0);
  11. if (fromMsg) {
  12. ... ...
  13. long timeoutTime = r.receiverTime + mTimeoutPeriod;
  14. if (timeoutTime > now) {
  15. ... ...
  16. setBroadcastTimeoutLocked(timeoutTime);
  17. return;
  18. }
  19. }
  20. ... ...
  21. // ③ 已经超时,则结束对当前接收器,开始新一轮调度 finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false);
  22. scheduleBroadcastsLocked();
  23. // ④ 抛出绘制ANR对话框的消息
  24. if (anrMessage != null) {
  25. // Post the ANR to the handler since we do not want to process ANRs while
  26. // potentially holding our lock.
  27. mHandler.post(new AppNotResponding(app, anrMessage));
  28. }
  29. }
  30. 复制代码

1、设置mPendingBroadcastTimeoutMessage

mPendingBroadcastTimeoutMessage 标识是否存在未处理的 BROADCAST_TIMEOUT_MSG 消息,将其设置成false,允许继续抛出BROADCAST_TIMEOUT_MSG消息。

2、判断第二种ANR机制是否超时

每次将广播派发到接收器,都会将 r.receiverTime 更新,如果判断当前还未超时,则又抛出一个 BROADCAST_TIMEOUT_MSG 消息。 正常情况下,所有接收器处理完毕后,才会清除 BROADCAST_TIMEOUT_MSG ;否则,每进行一次广播消息的调度,都会抛出 BROADCAST_TIMEOUT_MSG 消息。

3、已经超时,则结束对当前接收器,开始新一轮调度

判断已经超时了,说明当前的广播接收器还未处理完毕,则结束掉当前的接收器,开始新一轮广播调度。

4、抛出绘制ANR对话框的消息

最终,发出绘制 ANR 对话框的消息。

AMS 维护着广播队列 BroadcastQueue ,AMS 线程不断从队列中取出消息进行调度,完成广播消息的派发。在派发“串行广播消息”时,会抛出一个定时消息 BROADCAST_TIMEOUT_MSG ,在广播接收器处理完毕后,AMS 会将定时消息清除。如果 BROADCAST_TIMEOUT_MSG 得到了响应,就会判断是否广播消息处理超时,最终通知ANR的发生。

Input 超时处理

应用程序可以接收输入事件(按键、触屏、轨迹球等),当 5秒 内没有处理完毕时,则会引发 ANR。

输入事件经历了一些什么工序才能被派发到应用的界面?

如何检测输入事件处理超时?

输入事件派发工序

输入事件最开始由硬件设备(如按键或触摸屏幕)发起,Android 有一套输入子系统来发现各种输入事件,这些事件最终都会被 InputDispatcher 分发到各个需要接收事件的窗口。那么,窗口如何告之 InputDispatcher 自己需要处理输入事件呢?Android 通过 InputChannel 连接 InputDispatcher 和窗口,InputChannel 其实是封装后的 Linux管道(Pipe)。每一个窗口都会有一个独立的 InputChannel ,窗口需要将这个 InputChannel 注册到 InputDispatcher 中:

  1. status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
  2. ...
  3. sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
  4. int fd = inputChannel->getFd();
  5. mConnectionsByFd.add(fd, connection);
  6. ...
  7. mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
  8. ...
  9. mLooper->wake();
  10. return OK;
  11. }
  12. 复制代码

对于 InputDispatcher 而言,每注册一个 InputChannel 都被视为一个 Connection ,通过文件描述符来区别。InputDispatcher 是一个消息处理循环,当有新的 Connection 时,就需要唤醒消息循环队列进行处理。

输入事件的类型有很多,按键、轨迹球、触屏等,Android 对这些事件进行了分类,处理这些事件的窗口也被赋予了一个类型(targetType):Foucused 或 Touched。

如果当前输入事件是按键类型,则寻找 Focused 类型的窗口。如果当前输入事件类型是触摸类型,则寻找 Touched 类型的窗口。

InputDispatcher 需要经过以下复杂的调用关系,才能把一个输入事件派发出去(调用关系以按键事件为例,触屏事件的调用关系类似):

  1. InputDispatcherThread::threadLoop()
  2. └── InputDispatcher::dispatchOnce()
  3. └── InputDispatcher::dispatchOnceInnerLocked()
  4. └── InputDispatcher::dispatchKeyLocked()
  5. └── InputDispatcher::dispatchEventLocked()
  6. └── InputDispatcher::prepareDispatchCycleLocked()
  7. └── InputDispatcher::enqueueDispatchEntriesLocked()
  8. └── InputDispatcher::startDispatchCycleLocked()
  9. └── InputPublisher::publishKeyEvent()
  10. 复制代码

具体每个函数的实现逻辑此处不表。我们提炼出几个关键点:

  1. InputDispatcherThread 是一个线程,它处理一次消息的派发。
  2. 输入事件作为一个消息,需要排队等待派发,每一个 Connection 都维护两个队列:
  3. · outboundQueue: 等待发送给窗口的事件。每一个新消息到来,都会先进入到此队列。
  4. · waitQueue: 已经发送给窗口的事件。
  5. publishKeyEvent 完成后,表示事件已经派发了,就将事件从 outboundQueue 挪到了 waitQueue
  6. 复制代码

事件经过这么一轮处理,就算是从 InputDispatcher 派发出去了,但事件是不是被窗口收到了,还需要等待接收方的 “finished” 通知。在向 InputDispatcher 注册 InputChannel 的时候,同时会注册一个回调函数 handleReceiveCallback():

  1. int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
  2. ...
  3. for (;;) {
  4. ...
  5. status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
  6. if (status) { break; }
  7. d->finishDispatchCycleLocked(currentTime, connection, seq, handled); ...
  8. }
  9. ...
  10. d->unregisterInputChannelLocked(connection->inputChannel, notify);
  11. }
  12. 复制代码

当收到的 status 为 OK 时,会调用 finishDispatchCycleLocked() 来完成一个消息的处理:

  1. InputDispatcher::finishDispatchCycleLocked()
  2. └── InputDispatcher::onDispatchCycleFinishedLocked()
  3. └── InputDispatcher::doDispatchCycleFinishedLockedInterruptible()
  4. └── InputDispatcher::startDispatchCycleLocked()
  5. 复制代码

调用到 doDispatchCycleFinishedLockedInterruptible() 方法时,会将已经成功派发的消息从 waitQueue 中移除, 进一步调用会 startDispatchCycleLocked 开始派发新的事件。

至此,我们回答了第一个问题:

一个正常的输入事件会经过从 outboundQueue 挪到 waitQueue 的过程,表示消息已经派发出去;再经过从 waitQueue 中移除的过程,表示消息已经被窗口接收。InputDispatcher 作为中枢,不停地在递送着输入事件,当一个事件无法得到处理的时候,InputDispatcher 不能就此死掉啊,否则系统也太容易崩溃了。InputDispatcher 的策略是放弃掉处理不过来的事件,并发出通知(这个通知机制就是ANR),继续进行下一轮消息的处理。

理解输入事件分发模型,我们可以举一个生活中的例子:

  1. 每一个输入事件可以比做一个快递,InputDispatcher 就像一个快递中转站,窗口就像是收件人,InputChannel 就像是快递员。 所有快递都会经过中转站中处理,中转站需要知道每一个快递的收件人是谁,通过快递员将快递发送到具体的收件人。 这其中有很多场景导致快递不能及时送到:譬如联系不到收件人;快递很多,快递员会忙不过来;快递员受伤休假了等等… 这时候快递员就需要告知中转站:有快递无法及时送到了。中转站在收到快递员的通知后,一边继续派发其他快递,一边报告上级。
  2. 复制代码

检测输入事件处理超时

在派发事件时,dispatchKeyLocked() 和 dispatchMotionLocked(),需要找到当前的焦点窗口,焦点窗口才是最终接收事件的地方,找窗口的过程就会判断是否已经发生了ANR:

  1. InputDispatcher::findFocusedWindowTargetsLocked()
  2. InputDispatcher::findTouchedWindowTargetsLocked()
  3. └── InputDispatcher::handleTargetsNotReadyLocked()
  4. └── InputDispatcher::onANRLocked()
  5. └── InputDispatcher::doNotifyANRLockedInterruptible()
  6. └── NativeInputManager::notifyANR()
  7. 复制代码

(1)首先,会调用 findFocusedWindowTargetsLocked() 或 findTouchedWindowTargetsLocked() 寻找接收输入事件的窗口。

在找到窗口以后,会调用 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.”

(2)然后,上述有任何一个场景发生了,则输入事件需要继续等待,紧接着就会调用 handleTargetsNotReadyLocked() 来判断是不是已经的等待超时了:

  1. int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, const sp<InputApplicationHandle>& applicationHandle, const sp<InputWindowHandle>& windowHandle, nsecs_t* nextWakeupTime, const char* reason) {
  2. ...
  3. if (currentTime >= mInputTargetWaitTimeoutTime) {
  4. onANRLocked(currentTime, applicationHandle, windowHandle, entry->eventTime, mInputTargetWaitStartTime, reason);
  5. *nextWakeupTime = LONG_LONG_MIN;
  6. return INPUT_EVENT_INJECTION_PENDING;
  7. }
  8. ...
  9. }
  10. 复制代码

最后,如果当前事件派发已经超时,则说明已经检测到了 ANR ,调用 onANRLocked() 方法,然后将 nextWakeupTime 设置为最小值,马上开始下一轮调度。在 onANRLocked() 方法中, 会保存 ANR 的一些状态信息,调用 doNotifyANRLockedInterruptible() ,进一步会调用到 JNI 层的 NativeInputManager::notifyANR() 方法,它的主要功能就是衔接 Native 层和 Java 层,直接调用 Java 层的 InputManagerService.notifyANR() 方法。

至此,ANR的处理逻辑转交到了Java层。底层(Native)发现一旦有输入事件派发超时,就会通知上层(Java),上层收到ANR通知后,决定是否终止当前输入事件的派发。

发生ANR时,Java层最开始的入口是 InputManagerService.notifyANR() ,它是直接被 Native 层调用的。我们先把 ANR 的Java层调用关系列出来:

  1. InputManagerService.notifyANR()
  2. └── InputMonitor.notifyANR()
  3. ├── IApplicationToken.keyDispatchingTimedOut()
  4. └── ActivityRecord.keyDispatchingTimedOut()
  5. └── AMS.inputDispatchingTimedOut()
  6. └── AMS.appNotResponding()
  7. └── AMS.inputDispatchingTimedOut()
  8. └── AMS.appNotResponding()
  9. 复制代码

InputManagerService.notifyANR() 只是为 Native层 定义了一个接口,它直接调用 InputMonitor.notifyANR()。如果该方法的返回值等于0,则放弃本次输入事件;如果大于0,则表示需要继续等待的时间。

  1. public long notifyANR(InputApplicationHandle inputApplicationHandle, InputWindowHandle inputWindowHandle, String reason) {
  2. ...
  3. if (appWindowToken != null && appWindowToken.appToken != null) {
  4. // appToken实际上就是当前的ActivityRecord。
  5. // 如果发生ANR的Activity还存在,则直接通过ActivityRecord通知事件派发超时
  6. boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason);
  7. if (! abort) {
  8. return appWindowToken.inputDispatchingTimeoutNanos;
  9. }
  10. } else if (windowState != null) {
  11. // 如果发生ANR的Activity已经销毁了,则通过AMS通知事件派发超时
  12. long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut( windowState.mSession.mPid, aboveSystem, reason);
  13. if (timeout >= 0) { return timeout; }
  14. }
  15. return 0;
  16. // abort dispatching
  17. }
  18. 复制代码

上述方法中有两种不同的调用方式,但最终都会交由 AMS.inputDispatchingTimedOut() 处理。AMS 有重载的 inputDispatchingTimedOut() 方法,他们的参数不一样。ActivityRecord 调用时,可以传入的信息更多一点(当前发生ANR的界面是哪一个)。

  1. @Override public long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
  2. // 1. 根据进程号获取到ProcessRecord
  3. proc = mPidsSelfLocked.get(pid);
  4. ...
  5. // 2. 获取超时时间
  6. // 测试环境下的超时时间是INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT(60秒),
  7. // 正常环境下的超时时间是KEY_DISPATCHING_TIMEOUT(5秒)
  8. timeout = getInputDispatchingTimeoutLocked(proc);
  9. // 调用重载的函数,如果返回True,则表示需要中断当前的事件派发;
  10. if (!inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) { return -1; }
  11. // 3. 返回继续等待的时间,这个值会传递到Native层
  12. return timeout;
  13. }
  14. public boolean inputDispatchingTimedOut(final ProcessRecord proc, final ActivityRecord activity, final ActivityRecord parent, final boolean aboveSystem, String reason) {
  15. ...
  16. // 1. 发生ANR进程正处于调试状态,不需要中断事件
  17. if (proc.debugging) { return false; }
  18. // 2. 当前正在做dexopt操作,这会比较耗时,不需要中断
  19. if (mDidDexOpt) {
  20. // Give more time since we were dexopting.
  21. mDidDexOpt = false; return false;
  22. }
  23. // 3. 发生ANR的进程是测试进程,需要中断,但不在UI界面显示ANR信息判断
  24. if (proc.instrumentationClass != null) { ...
  25. finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info); return true;
  26. }
  27. // 4. 通知UI界面显示ANR信息
  28. mHandler.post(new Runnable() {
  29. @Override public void run() {
  30. appNotResponding(proc, activity, parent, aboveSystem, annotation);
  31. } });
  32. ...
  33. return true;
  34. }
  35. 复制代码

在 InputDispatcher 派发输入事件时,会寻找接收事件的窗口,如果无法正常派发,则可能会导致当前需要派发的事件超时(默认是5秒)。Native层发现超时了,会通知Java层,Java层经过一些处理后,会反馈给Native层,是继续等待还是丢弃当前派发的事件。

ANR 信息收集过程

  1. // frameworks/base/services/core/java/com/android/server/am/AppErrors.java
  2. class AppErrors {
  3. final void appNotResponding(ProcessRecord app, ActivityRecord activity,
  4. ActivityRecord parent, boolean aboveSystem, final String annotation) {
  5. ... ...
  6. long anrTime = SystemClock.uptimeMillis();
  7. if (ActivityManagerService.MONITOR_CPU_USAGE) {
  8. mService.updateCpuStatsNow(); // 更新 cpu 统计信息
  9. }
  10. boolean showBackground = Settings.Secure.
  11. getInt(mContext.getContentResolver(),
  12. Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
  13. boolean isSilentANR;
  14. synchronized (mService) {
  15. if (mService.mShuttingDown) {
  16. return;
  17. } else if (app.notResponding) {
  18. return;
  19. } else if (app.crashing) {
  20. return;
  21. } else if (app.killedByAm) {
  22. return;
  23. } else if (app.killed) {
  24. return;
  25. }
  26. // In case we come through here for the same app before completing
  27. // this one, mark as anring now so we will bail out.
  28. app.notResponding = true;
  29. // 记录 ANR 到 EventLog
  30. EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
  31. app.processName, app.info.flags, annotation);
  32. // 将当前进程添加到 firstPids
  33. firstPids.add(app.pid);
  34. // Don't dump other PIDs if it's a background ANR
  35. isSilentANR = !showBackground
  36. && !isInterestingForBackgroundTraces(app);
  37. if (!isSilentANR) {
  38. int parentPid = app.pid;
  39. if (parent != null && parent.app != null && parent.app.pid > 0) {
  40. parentPid = parent.app.pid;
  41. }
  42. if (parentPid != app.pid) firstPids.add(parentPid);
  43. // 将 system_server 进程添加到 firstPids
  44. if (MY_PID != app.pid
  45. && MY_PID != parentPid) firstPids.add(MY_PID);
  46. for (int i = mService.mLruProcesses.size() - 1; i >= 0; i--) {
  47. ProcessRecord r = mService.mLruProcesses.get(i);
  48. if (r != null && r.thread != null) {
  49. int pid = r.pid;
  50. if (pid > 0 && pid != app.pid
  51. && pid != parentPid && pid != MY_PID) {
  52. if (r.persistent) {
  53. // 将 persistent 进程添加到 firstPids
  54. firstPids.add(pid);
  55. } else if (r.treatLikeActivity) {
  56. firstPids.add(pid);
  57. } else {
  58. // 其他进程添加到 lastPids
  59. lastPids.put(pid, Boolean.TRUE);
  60. }
  61. }
  62. }
  63. }
  64. }
  65. }
  66. // 记录 ANR 输出到 main log
  67. StringBuilder info = new StringBuilder();
  68. info.setLength(0);
  69. info.append("ANR in ").append(app.processName);
  70. if (activity != null && activity.shortComponentName != null) {
  71. info.append(" (").append(activity.shortComponentName).append(")");
  72. }
  73. info.append("\n");
  74. info.append("PID: ").append(app.pid).append("\n");
  75. if (annotation != null) {
  76. info.append("Reason: ").append(annotation).append("\n");
  77. }
  78. if (parent != null && parent != activity) {
  79. info.append("Parent: ").append(parent.shortComponentName).append("\n");
  80. }
  81. // 创建 CPU tracker 对象
  82. ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
  83. ... ...
  84. // 输出 traces 信息
  85. File tracesFile = ActivityManagerService.dumpStackTraces(
  86. true, firstPids,
  87. (isSilentANR) ? null : processCpuTracker,
  88. (isSilentANR) ? null : lastPids,
  89. nativePids);
  90. String cpuInfo = null;
  91. if (ActivityManagerService.MONITOR_CPU_USAGE) {
  92. mService.updateCpuStatsNow();
  93. synchronized (mService.mProcessCpuTracker) {
  94. cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
  95. }
  96. // 记录当前 CPU 负载情况
  97. info.append(processCpuTracker.printCurrentLoad());
  98. info.append(cpuInfo);
  99. }
  100. // 记录从 anr 时间开始的 Cpu 使用情况
  101. info.append(processCpuTracker.printCurrentState(anrTime));
  102. // 输出当前 ANR 的 reason,以及 CPU 使用率、负载信息
  103. Slog.e(TAG, info.toString());
  104. if (tracesFile == null) {
  105. Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
  106. }
  107. ... ...
  108. // 将 traces 文件和 CPU 使用率信息保存到 dropbox,即 data/system/dropbox 目录
  109. mService.addErrorToDropBox("anr", app, app.processName,
  110. activity, parent, annotation, cpuInfo, tracesFile, null);
  111. ... ...
  112. synchronized (mService) {
  113. mService.mBatteryStatsService.noteProcessAnr(app.processName, app.uid);
  114. // 后台 ANR 的情况, 直接杀掉
  115. if (isSilentANR) {
  116. app.kill("bg anr", true);
  117. return;
  118. }
  119. // 设置 app 的 ANR 状态,病查询错误报告 receiver
  120. makeAppNotRespondingLocked(app,
  121. activity != null ? activity.shortComponentName : null,
  122. annotation != null ? "ANR " + annotation : "ANR",
  123. info.toString());
  124. // 弹出 ANR 对话框
  125. Message msg = Message.obtain();
  126. msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
  127. msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
  128. // 向 ui 线程发送,内容为 SHOW_NOT_RESPONDING_MSG 的消息
  129. mService.mUiHandler.sendMessage(msg);
  130. }
  131. }
  132. }
  133. 复制代码

当发生 ANR 时, 会按顺序依次执行:

1、输出 ANR Reason 信息到 EventLog,也就是说 ANR 触发的时间点最接近的就是 EventLog 中输出的 am_anr 信息;

2、收集并输出重要进程列表中的各个线程的 traces 信息,该方法较耗时;

3、输出当前各个进程的 CPU 使用情况以及 CPU 负载情况;

4、将 traces 文件和 CPU 使用情况信息保存到 dropbox,即 data/system/dropbox 目录;

5、根据进程类型,来决定直接后台杀掉,还是弹框告知用户。

ANR输出重要进程的traces信息,这些进程包含:

1、firstPids 队列:第一个是 ANR 进程,第二个是 system_server,剩余是所有 persistent 进程;

2、Native 队列:是指 /system/bin/ 目录的 mediaserver、sdcard 以及 surfaceflinger 进程;

3、lastPids 队列: 是指 mLruProcesses 中的不属于 firstPids 的所有进程。

dump 出 trace 信息的流程:

  1. // ActivityManagerService.java
  2. public static File dumpStackTraces(boolean clearTraces, ... ,nativePids) {
  3. ... ...
  4. if (tracesDirProp.isEmpty()) {
  5. // 默认为 data/anr/traces.txt
  6. String globalTracesPath =
  7. SystemProperties.get("dalvik.vm.stack-trace-file", null);
  8. tracesFile = new File(globalTracesPath);
  9. try {
  10. if (clearTraces && tracesFile.exists()) {
  11. tracesFile.delete(); // 删除已存在的 traces 文件
  12. }
  13. // 这里会保证 data/anr/traces.txt 文件内容是全新的方式,而非追加
  14. tracesFile.createNewFile(); // 创建 traces 文件
  15. FileUtils.setPermissions(globalTracesPath, 0666, -1, -1);
  16. } catch (IOException e) {
  17. Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesFile, e);
  18. return null;
  19. }
  20. } else {
  21. }
  22. // 输出 trace 内容
  23. dumpStackTraces(tracesFile.getAbsolutePath(), firstPids, nativePids,
  24. extraPids, useTombstonedForJavaTraces);
  25. return tracesFile;
  26. }
  27. 复制代码
  28. // ActivityManagerService.java
  29. private static void dumpStackTraces(String tracesFile, ...) {
  30. final DumpStackFileObserver observer;
  31. if (useTombstonedForJavaTraces) {
  32. observer = null;
  33. } else {
  34. observer = new DumpStackFileObserver(tracesFile);
  35. }
  36. // We must complete all stack dumps within 20 seconds.
  37. long remainingTime = 20 * 1000;
  38. try {
  39. if (observer != null) {
  40. observer.startWatching();
  41. }
  42. // 首先,获取 firstPids 进程的 stacks
  43. if (firstPids != null) {
  44. int num = firstPids.size();
  45. for (int i = 0; i < num; i++) {
  46. final long timeTaken;
  47. if (useTombstonedForJavaTraces) {
  48. timeTaken = dumpJavaTracesTombstoned(firstPids.get(i),
  49. tracesFile, remainingTime);
  50. } else {
  51. timeTaken = observer.dumpWithTimeout(firstPids.get(i),
  52. remainingTime);
  53. }
  54. ... ...
  55. }
  56. }
  57. // 下一步,获取 native 进程的 stacks
  58. if (nativePids != null) {
  59. for (int pid : nativePids) {
  60. ... ...
  61. // 输出 native 进程的 trace
  62. Debug.dumpNativeBacktraceToFileTimeout(
  63. pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
  64. final long timeTaken = SystemClock.elapsedRealtime() - start;
  65. ... ...
  66. }
  67. }
  68. // Lastly, dump stacks for all extra PIDs from the CPU tracker.
  69. if (extraPids != null) {
  70. ... ...
  71. }
  72. }
  73. } finally {
  74. if (observer != null) {
  75. observer.stopWatching();
  76. }
  77. }
  78. }
  79. 复制代码

触发 ANR 时系统会输出关键信息:

1、将 am_anr 信息,输出到 EventLog;

2、获取重要进程 trace 信息,保存到 /data/anr/traces.txt;

3、ANR reason 以及 CPU 使用情况信息,输出到 main log;

4、再将 CPU使用情况 和进程 trace 文件信息,再保存到 /data/system/dropbox。

小结

ANR监测机制包含三种:

  1. Service ANR,前台进程中Service生命周期不能超过 20秒,后台进程中Service的生命周期不能超过 200秒。 在启动Service时,抛出定时消息 SERVICE_TIMEOUT_MSG SERVICE_BACKGOURND_TIMEOUT_MSG,如果定时消息响应了,则说明发生了ANR
  2. Broadcast ANR,前台的“串行广播消息”必须在 10 内处理完毕,后台的“串行广播消息”必须在 60 处理完毕, 每派发串行广播消息到一个接收器时,都会抛出一个定时消息 BROADCAST_TIMEOUT_MSG,如果定时消息响应,则判断是否广播消息处理超时,超时就说明发生了ANR
  3. Input ANR,输入事件必须在 5 内处理完毕。在派发一个输入事件时,会判断当前输入事件是否需要等待,如果需要等待,则判断是否等待已经超时,超时就说明发生了ANR
  4. 复制代码

ANR监测机制实际上是对应用程序主线程的要求,要求主线成必须在限定的时间内,完成对几种操作的响应;否则,就可以认为应用程序主线程失去响应能力。

从ANR的三种监测机制中,我们看到不同超时机制的设计:

  1. Service Broadcast 都是由 AMS 调度,利用 Handler Looper,设计了一个 TIMEOUT 消息交由 AMS 线程来处理,整个超时机制的实现都是在Java层;
  2. InputEvent InputDispatcher 调度,待处理的输入事件都会进入队列中等待,设计了一个等待超时的判断,超时机制的实现在Native层。复制代码

转载于:https://juejin.im/post/5cd04f9b6fb9a03223353da0

发表评论

表情:
评论列表 (有 0 条评论,183人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ANR

    ANR ANR(Application Not Responding),应用程序无响应。ANR 属于应用程序的范畴,ANR 由消息处理机制保证,Android 在系统层实

    相关 Android ANR优化 2

    在实际情况中,当Android项目的用户量特别大时候,一些细小的问题也会被放大,ANR问题就是一个典型的例子。 一些ANR问题只会发生在用户实际使用的情景,当系统资源比较紧