理解 Android ANR 的触发原理

一、概述

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

那么哪些场景会造成ANR呢?

  • Service Timeout:比如前台服务在20s内未执行完成;
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成
  • ContentProvider Timeout:内容提供者,在publish过超时10s;
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

触发ANR的过程可分为三个步骤: 埋炸弹, 拆炸弹, 引爆炸弹

二 Service

Service Timeout是位于"ActivityManager"线程中的AMS.MainHandler收到SERVICE_TIMEOUT_MSG消息时触发。

对于Service有两类:

  • 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
  • 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s

由变量ProcessRecord.execServicesFg来决定是否前台启动

2.1 埋炸弹

文章 startService 流程分析详细介绍Service启动流程.

其中在Service进程attach到system_server进程的过程中会调用realStartServiceLocked()方法来埋下炸弹.

2.1.1 AS.realStartServiceLocked

[-> ActiveServices.java]

  • 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 {
  • ...
  • }
  • }

2.1.2 AS.bumpServiceExecutingLocked

  • 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));
  • }

该方法的主要工作发送delay消息(SERVICE_TIMEOUT_MSG). 炸弹已埋下, 我们并不希望炸弹被引爆, 那么就需要在炸弹爆炸之前拆除炸弹.

2.2 拆炸弹

在system_server进程AS.realStartServiceLocked()调用的过程会埋下一颗炸弹, 超时没有启动完成则会爆炸.

那么什么时候会拆除这颗炸弹的引线呢? 经过Binder等层层调用进入目标进程的主线程handleCreateService()的过程.

2.2.1 AT.handleCreateService

[-> 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) {
  • ...
  • }
  • }

在这个过程会创建目标服务对象,以及回调onCreate()方法, 紧接再次经过多次调用回到system_server来执行serviceDoneExecuting.

2.2.2 AS.serviceDoneExecutingLocked

  • private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
  • boolean finishing) {
  • ...
  • if (r.executeNesting <= 0) {
  • if (r.app != null) {
  • r.app.execServicesFg = false;
  • r.app.executingServices.remove(r);
  • if (r.app.executingServices.size() == 0) {
  • //当前服务所在进程中没有正在执行的service
  • mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
  • ...
  • }
  • ...
  • }

该方法的主要工作是当service启动完成,则移除服务超时消息SERVICE_TIMEOUT_MSG

2.3 引爆炸弹

前面介绍了埋炸弹和拆炸弹的过程, 如果在炸弹倒计时结束之前成功拆卸炸弹,那么就没有爆炸的机会, 但是世事难料.

总有些极端情况下无法即时拆除炸弹,导致炸弹爆炸, 其结果就是App发生ANR. 接下来,带大家来看看炸弹爆炸的现场:

在system_server进程中有一个Handler线程, 名叫"ActivityManager".当倒计时结束便会向该Handler线程发送一条信息SERVICE_TIMEOUT_MSG,

2.3.1 MainHandler.handleMessage

[-> ActivityManagerService.java ::MainHandler]

  • 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;
  • ...
  • }
  • ...
  • }
  • }

2.3.2 AS.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);
  • }
  • }

其中anrMessage的内容为"executing service [发送超时serviceRecord信息]";

三 BroadcastReceiver

BroadcastReceiver Timeout是位于"ActivityManager"线程中的BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG消息时触发。

对于广播队列有两个: foreground队列和background队列:

  • 对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
  • 对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s

3.1 埋炸弹

文章 Android Broadcast 广播机制分析详细介绍广播启动流程,通过调用 processNextBroadcast 来处理广播.其流程为先处理并行广播,再处理当前有序广播,最后获取并处理下条有序广播.

3.1.1 processNextBroadcast

[-> BroadcastQueue.java]

  • final void processNextBroadcast(boolean fromMsg) {
  • synchronized(mService) {
  • ...
  • //part 2: 处理当前有序广播
  • do {
  • r = mOrderedBroadcasts.get(0);
  • //获取所有该广播所有的接收者
  • int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
  • if (mService.mProcessesReady && r.dispatchTime > 0) {
  • long now = SystemClock.uptimeMillis();
  • if ((numReceivers > 0) &&
  • (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
  • //当广播处理时间超时,则强制结束这条广播【见小节3.3.2】
  • broadcastTimeoutLocked(false);
  • ...
  • }
  • }
  • if (r.receivers == null || r.nextReceiver >= numReceivers
  • || r.resultAbort || forceReceive) {
  • if (r.resultTo != null) {
  • //处理广播消息消息
  • performReceiveLocked(r.callerApp, r.resultTo,
  • new Intent(r.intent), r.resultCode,
  • r.resultData, r.resultExtras, false, false, r.userId);
  • r.resultTo = null;
  • }
  • //拆炸弹【见小节3.2.1】
  • cancelBroadcastTimeoutLocked();
  • }
  • } while (r == null);
  • ...
  • //part 3: 获取下条有序广播
  • r.receiverTime = SystemClock.uptimeMillis();
  • if (!mPendingBroadcastTimeoutMessage) {
  • long timeoutTime = r.receiverTime + mTimeoutPeriod;
  • //埋炸弹【见小节3.1.3】
  • setBroadcastTimeoutLocked(timeoutTime);
  • }
  • ...
  • }
  • }

对于广播超时处理时机:

  1. 首先在part3的过程中setBroadcastTimeoutLocked(timeoutTime) 设置超时广播消息;
  2. 然后在part2根据广播处理情况来处理:
    • 当广播接收者等待时间过长,则调用broadcastTimeoutLocked(false);
    • 当执行完广播,则调用cancelBroadcastTimeoutLocked;

3.1.2 setBroadcastTimeoutLocked

  • final void setBroadcastTimeoutLocked(long timeoutTime) {
  • if (! mPendingBroadcastTimeoutMessage) {
  • Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
  • mHandler.sendMessageAtTime(msg, timeoutTime);
  • mPendingBroadcastTimeoutMessage = true;
  • }
  • }

设置定时广播BROADCAST_TIMEOUT_MSG,即当前往后推mTimeoutPeriod时间广播还没处理完毕,则进入广播超时流程。

3.2 拆炸弹

在processNextBroadcast()过程, 执行完performReceiveLocked,便会拆除炸弹.

3.2.1 cancelBroadcastTimeoutLocked

  • final void cancelBroadcastTimeoutLocked() {
  • if (mPendingBroadcastTimeoutMessage) {
  • mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
  • mPendingBroadcastTimeoutMessage = false;
  • }
  • }

移除广播超时消息BROADCAST_TIMEOUT_MSG

3.3 引爆炸弹

3.3.1 BroadcastHandler.handleMessage

[-> BroadcastQueue.java ::BroadcastHandler]

  • private final class BroadcastHandler extends Handler {
  • public void handleMessage(Message msg) {
  • switch (msg.what) {
  • case BROADCAST_TIMEOUT_MSG: {
  • synchronized (mService) {
  • //【见小节3.3.2】
  • broadcastTimeoutLocked(true);
  • }
  • } break;
  • ...
  • }
  • ...
  • }
  • }

3.3.2 broadcastTimeoutLocked

[-> BroadcastRecord.java]
```java
//fromMsg = true

top Created with Sketch.