为什么会触发ANR,从源码中扒一扒

简介: 为什么会触发ANR,从源码中扒一扒

在面试过程中,难免会遇到一道这样的问题,为什么Android程序会ANR?你得解释什么是ANR;

ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。

造成ANR的场景:

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

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

说了一大堆之后,面试官觉得听得不还够,可能会接着问系统是怎么触发的呢?


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

对于Service有两类:

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

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

埋炸弹阶段:在Service进程attach到system_server进程的过程中会调用realStartServiceLocked()方法  (准确说是scheduleServiceTimeoutLocked方法)  中来埋下炸弹.

    private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
        ...
        //发送delay消息(SERVICE_TIMEOUT_MSG),
        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流程
        mAm.mHandler.sendMessageAtTime(msg,
            proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
    }

    拆炸弹阶段:经过Binder等层层调用进入目标进程的主线程ActivityThread.handleCreateService()的过程. 在这个过程会创建目标服务对象,以及回调onCreate()方法, 紧接再次经过多次调用回到system_server来执行serviceDoneExecuting.

    最终在serviceDoneExecutingLocked中移除服务超时消息SERVICE_TIMEOUT_MSG

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


      引爆炸弹阶段:在system_server进程中有一个Handler线程, 名叫”ActivityManager”.当倒计时结束便会向该Handler线程发送 一条信息SERVICE_TIMEOUT_MSG

        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信息]”


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

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

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

        埋炸弹阶段:

        通过调用 BroadcastQueue.processNextBroadcast() 来处理广播.其流程为先处理并行广播,再处理当前有序广播,最后获取并处理下条有序广播

          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))) {
                              //当广播处理时间超时,则强制结束这条广播
                              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;
                          }
                          //拆炸弹
                          cancelBroadcastTimeoutLocked();
                      }
                  } while (r == null);
                  ...
                  //part 3: 获取下条有序广播
                  r.receiverTime = SystemClock.uptimeMillis();
                  if (!mPendingBroadcastTimeoutMessage) {
                      long timeoutTime = r.receiverTime + mTimeoutPeriod;
                      //埋炸弹
                      setBroadcastTimeoutLocked(timeoutTime);
                  }
                  ...
              }
          }

          对于广播超时处理时机:

          1. 首先在part3的过程中setBroadcastTimeoutLocked(timeoutTime) 设置超时广播消息;
          2. 然后在part2根据广播处理情况来处理:
          • 当广播接收者等待时间过长,则调用broadcastTimeoutLocked(false);
          • 当执行完广播,则调用cancelBroadcastTimeoutLocked;
            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时间广播还没处理完毕,则进入广播超时流程。

            拆炸弹阶段:

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

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

              引爆炸弹阶段:

              BroadcastHandler.handleMessage

              不会引爆的四种情况

              1. mOrderedBroadcasts已处理完成,则不会anr;
              2. 正在执行dexopt,则不会anr;
              3. 系统还没有进入ready状态(mProcessesReady=false),则不会anr;
              4. 如果当前正在执行的receiver没有超时,则重新设置广播超时,不会anr;

              总结:

              当出现ANR时,都是会调用到AMS.appNotResponding()方法

              Timeout时长

              • 对于前台服务,则超时为SERVICE_TIMEOUT = 20s;
              • 对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
              • 对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s;
              • 对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s;
              • ContentProvider超时为CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10s;

              超时检测

              Service超时检测机制:

              • 超过timeout时长没有执行完相应操作来触发移除延时消息,则会触发anr;

              BroadcastReceiver超时检测机制:

              • 有序广播的总执行时间超过 2* receiver个数 * timeout时长,则会触发anr;
              • 有序广播的某一个receiver执行过程超过 timeout时长,则会触发anr;

              另外:

              • 对于Service, Broadcast, Input发生ANR之后,最终都会调用AMS.appNotResponding;
              相关文章
              |
              6月前
              |
              Java Android开发
              Android系统 获取用户最后操作时间回调实现和原理分析
              Android系统 获取用户最后操作时间回调实现和原理分析
              194 0
              ANR怎么产生的,怎么分析ANR?(三)
              ANR怎么产生的,怎么分析ANR?(三)
              56 0
              |
              大数据 数据库 Android开发
              ANR怎么产生的,怎么分析ANR?(一)
              ANR怎么产生的,怎么分析ANR?
              143 0
              |
              Android开发
              ANR怎么产生的,怎么分析ANR?(二)
              ANR怎么产生的,怎么分析ANR?(二)
              97 0
              |
              Java 调度 C++
              ANR分析总结
              ANR分析总结
              1252 0
              ANR分析总结
              |
              存储 监控 Android开发
              Android卡顿优化 | ANR分析与实战(附ANR-WatchDog源码分析及实战、与AndroidPerformanceMonitor的区别)
              Android卡顿优化 | ANR分析与实战(附ANR-WatchDog源码分析及实战、与AndroidPerformanceMonitor的区别)
              |
              6月前
              |
              存储 Java Linux
              Android系统获取event事件回调等几种实现和原理分析
              Android系统获取event事件回调等几种实现和原理分析
              356 0
              |
              Web App开发 JavaScript 前端开发
              页面运行中事件频繁触发会阻塞页面吗?
              之前看`防抖`和`节流`的时候,看到短时间内大量的事件触发会引起浏览器卡死,浪费浏览器性能,那么为什么事件触发会引起阻塞页面的情况呢?引起页面阻塞的原因真的是因为事件触发太多了吗?
              |
              监控 Java Android开发
              RxJava 异常时堆栈显示不正确?解决方法都在这里
              RxJava 异常时堆栈显示不正确?解决方法都在这里
              143 0
              RxJava 异常时堆栈显示不正确?解决方法都在这里