ANR怎么产生的,怎么分析ANR?(一)

简介: ANR怎么产生的,怎么分析ANR?

ANR:Application Not Responding,即“应用程序无响应”。Android 运行时,AMS 和 WMS 会监测应用程序的响应时间,如果应用程序主线程(即UI线程)在超时时间内对输入事件没有处理完毕,或者对特定操作没有执行完毕,就会上报 ANR。

ANR 的触发分为以下几类,

  • InputDispatching Timeout:输入事件(包括按键和触屏事件)在5秒内无响应,就会弹出 ANR 提示框,供用户选择继续等待程序响应或者关闭这个应用程序(也就是杀掉这个应用程序的进程)。输入超时类的 ANR 可以细分为以下两类:
  • 处理消息超时:顾名思义这一类是指因为消息处理超时而发生的 ANR,在 log,会看到 “Input dispatching timed out (Waiting because the focused window has not finished processing the input events that were previously delivered to it.)”
  • 无法获取焦点:这一类通常因为新窗口创建慢或旧窗口退出慢而造成窗口无法获得焦点从而发生 ANR,典型 Log “Reason: Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.”
  • Broadcast Timeout:BroadcastReceiver在规定时间内(前台广播10秒,后台广播60秒)无法处理完成,即报出广播超时的消息。这一类型没有提示框弹出,多发于 statusbar,settings 应用中。
  • Service Timeout:Service在规定时间内(前台服务20秒,后台服务200秒)无法处理完成,即报出服务超时。这一类也不会弹框提示,偶尔会在 Bluetooth 和 wifi 中出现,但是很少碰到。
  • ContentProvider Timeout:ContentProvider 的 publish 在10s内没有完成,会报出此类 ANR。多发于android.process.media中。

产生 ANR 需要满足以下条件,

  1. 只有应用程序进程的主线程(UI 线程)响应超时才会产生 ANR。
  2. 只有达到超时时间才能触发 ANR。产生 ANR 的上下文不同,超时时间也会不同。
  3. 只有输入事件或特定操作才能触发 ANR。输入事件是指按键、触屏等设备输入事件,特定操作是指 BroadcastReceiver 和 Service 的生命周期中的各个函数。产生 ANR 的上下文不同,导致 ANR 的原因也会不同。

防止产生 ANR 的方法主要就是避免在主线程中执行耗时的操作,可以降耗时操作放入子线程中执行。耗时操作包括:

  • 数据库操作。数据库操作尽量采用异步方法做处理
  • 初始化的数据和控件太多
  • 频繁的创建线程或者其它大对象;
  • 加载过大数据和图片;
  • 对大数据排序和循环操作;
  • 过多的广播和滥用广播;
  • 大对象的传递和共享;
  • 网络操作

InputDispatching Timeout

在 Android Input 系统中,InputDispatcher 负责将输入事件分发给 UI 主线程。UI 主线程接收到输入事件后,使用 InputConsumer 来处理事件。经过一系列的 InputStage 完成事件分发后,执行 finishInputEvent() 方法来告知 InputDispatcher 事件已经处理完成。InputDispatcher 中使用 handleReceiveCallback() 方法来处理 UI 主线程返回的消息,最终将 dispatchEntry 事件从等待队列中移除。

InputDispatching Timeout ANR 就产生于输入事件分发的过程中。InputDispatcher 分发事件过程中会检测上一个输入事件的状态,如果上一个输入事件在限定时间内没有完成分发,就会触发 ANR。InputDispatching Timeout 的默认限定时间的5s,有两处对其进行定义。


frameworks/native/services/inputflinger/InputDispatcher.cpp
constexpr nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT
 = 5000 * 1000000LL; // 5 sec
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS =
 5000 * 1000000L;

Input 分发事件时,通过 InputDispatcherThread 的 threadLoop 来循环读取 Input 事件,然后使用 dispatchOnce() 进行事件分发,实际的实现在 dispatchOnceInnerLocked() 中。


frameworks/native/services/inputflinger/InputDispatcher.cpp
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now(); // 记录事件分发时间
    ......
    // 没有正在分发的事件时,获取一个事件
    if (! mPendingEvent) {
        if (mInboundQueue.isEmpty()) {
            // Inbound Queue 为空时,处理应用 switch key,根据状态生成 repeat key
            ......
        } else {
            // 从 Inbound Queue 中获取一个事件
            mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();
        }
          ......
        // 重置 ANR Timeout 状态
        resetANRTimeoutsLocked();
    }
    ......
    // 根据事件类型进行分发
    switch (mPendingEvent->type) {
    case EventEntry::TYPE_CONFIGURATION_CHANGED: {
        // 配置改变
        ......
    case EventEntry::TYPE_DEVICE_RESET: {
        // 设备重置
        ......
    case EventEntry::TYPE_KEY: {
        // 按键输入
        ......
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }
    case EventEntry::TYPE_MOTION: {
        // 触摸屏输入
        ......
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }
    ......
    if (done) {
        // 根据 dropReason 来决定是否丢弃事件
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        releasePendingEventLocked(); // 释放正在处理的事件,也会重置 ANR timeout
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}

接下来看具体输入事件的分发,以按键输入为例,会使用 dispatchKeyLocked() 进行分发。

    frameworks/native/services/inputflinger/InputDispatcher.cpp
    bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
            DropReason* dropReason, nsecs_t* nextWakeupTime) {
        // 新事件需要进行预处理
        if (! entry->dispatchInProgress) {
            ......
        }
        // 处理 try again 事件
        if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
            ......
        }
        // 如果 flag 为 POLICY_FLAG_PASS_TO_USER,注册事件拦截
        if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
            ......
        }
        ......
        // 寻找当前的焦点窗口,这里可能会触发 ANR
        Vector<InputTarget> inputTargets;
        int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
            return false;
        }
        ......
        // 分发按键
        dispatchEventLocked(currentTime, entry, inputTargets);
        return true;
    }

    我们主要关注 ANR 的触发,看一下 findFocusedWindowTargetsLocked() 的实现。

      frameworks/native/services/inputflinger/InputDispatcher.cpp
      int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
              const EventEntry* entry,
              const sp<InputApplicationHandle>& applicationHandle,
              const sp<InputWindowHandle>& windowHandle,
              nsecs_t* nextWakeupTime, const char* reason) {
          if (applicationHandle == NULL && windowHandle == NULL) {
              if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
                  // 等待系统就绪,无窗口无应用的情形进入一次
                  ......
              }
          } else {
              if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
                  // 等待应用就绪,一个窗口进入一次
                  ......
                  // 设置超时,默认值都是5s
                  nsecs_t timeout; 
                  ......
                  mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
                  mInputTargetWaitStartTime = currentTime; // 当前事件第一次分发时间
                  mInputTargetWaitTimeoutTime = currentTime + timeout; // 超时时间
                  mInputTargetWaitTimeoutExpired = false;
                  mInputTargetWaitApplicationHandle.clear(); // 清除当前等待的应用
                  // 设置当前等待的应用
                  if (windowHandle != NULL) {
                      mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
                  }
                  if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
                      mInputTargetWaitApplicationHandle = applicationHandle;
                  }
              }
          }
          if (mInputTargetWaitTimeoutExpired) {
              return INPUT_EVENT_INJECTION_TIMED_OUT;
          }
          if (currentTime >= mInputTargetWaitTimeoutTime) {
              // 事件分发超时,触发 ANR
              onANRLocked(currentTime, applicationHandle, windowHandle,
                      entry->eventTime, mInputTargetWaitStartTime, reason);
              // ANR 触发时,立即唤醒下一个轮询
              *nextWakeupTime = LONG_LONG_MIN;
              return INPUT_EVENT_INJECTION_PENDING;
          } else {
              // 超时时强制唤醒轮询
              if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
                  *nextWakeupTime = mInputTargetWaitTimeoutTime;
              }
              return INPUT_EVENT_INJECTION_PENDING;
          }
      }
      

      上述代码使用 checkWindowReadyForMoreInputLocked() 来检查窗口是否准备就绪,它使用字符串返回 window connection 的状态,当窗口正常时返回空。接下来会使用 handleTargetsNotReadyLocked() 来处理窗口未就绪的情形。

        frameworks/native/services/inputflinger/InputDispatcher.cpp
        int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
                const EventEntry* entry,
                const sp<InputApplicationHandle>& applicationHandle,
                const sp<InputWindowHandle>& windowHandle,
                nsecs_t* nextWakeupTime, const char* reason) {
            if (applicationHandle == NULL && windowHandle == NULL) {
                if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
                    // 等待系统就绪,无窗口无应用的情形进入一次
                    ......
                }
            } else {
                if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
                    // 等待应用就绪,一个窗口进入一次
                    ......
                    // 设置超时,默认值都是5s
                    nsecs_t timeout; 
                    ......
                    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
                    mInputTargetWaitStartTime = currentTime; // 当前事件第一次分发时间
                    mInputTargetWaitTimeoutTime = currentTime + timeout; // 超时时间
                    mInputTargetWaitTimeoutExpired = false;
                    mInputTargetWaitApplicationHandle.clear(); // 清除当前等待的应用
                    // 设置当前等待的应用
                    if (windowHandle != NULL) {
                        mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
                    }
                    if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
                        mInputTargetWaitApplicationHandle = applicationHandle;
                    }
                }
            }
            if (mInputTargetWaitTimeoutExpired) {
                return INPUT_EVENT_INJECTION_TIMED_OUT;
            }
            if (currentTime >= mInputTargetWaitTimeoutTime) {
                // 事件分发超时,触发 ANR
                onANRLocked(currentTime, applicationHandle, windowHandle,
                        entry->eventTime, mInputTargetWaitStartTime, reason);
                // ANR 触发时,立即唤醒下一个轮询
                *nextWakeupTime = LONG_LONG_MIN;
                return INPUT_EVENT_INJECTION_PENDING;
            } else {
                // 超时时强制唤醒轮询
                if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
                    *nextWakeupTime = mInputTargetWaitTimeoutTime;
                }
                return INPUT_EVENT_INJECTION_PENDING;
            }
        }

        上述代码显示了一个正常输入事件分发过程中触发 ANR 的过程,触发 ANR 的判定是在一个限定的时间内是否可以完成事件分发。正常的分发过程中会有两处位置重置 ANR timeout,

        • 获取一个新的分发事件时。
        • 事件完成分发时。
        void InputDispatcher::resetANRTimeoutsLocked() {
            // ANR timeout 重置就是重置了等待状态,清除了等待应用
            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
            mInputTargetWaitApplicationHandle.clear();
        }

        系统运行时,主要是以下4个场景,会有机会执行resetANRTimeoutsLocked:

        • 解冻屏幕, 系统开/关机的时刻点 (thawInputDispatchingLw, setEventDispatchingLw)
        • wms聚焦app的改变 (WMS.setFocusedApp, WMS.removeAppToken)
        • 设置input filter的过程 (IMS.setInputFilter)
        • 再次分发事件的过程(dispatchOnceInnerLocked)

        触发 ANR 后,会调用 onANRLocked() 来捕获 ANR 的相关信息。大致的调用流程为,

          InputDispatcher::onANRLocked --> InputDispatcher::doNotifyANRLockedInterruptible
              --> InputManagerService.notifyANR --> InputMonitor.notifyANR
              --> ActivityManagerService.inputDispatchingTimedOut 
              --> AppErrors.appNotResponding
          相关文章
          |
          8月前
          |
          Android开发
          ANR怎么产生的,怎么分析ANR?(二)
          ANR怎么产生的,怎么分析ANR?(二)
          35 0
          |
          8月前
          ANR怎么产生的,怎么分析ANR?(三)
          ANR怎么产生的,怎么分析ANR?(三)
          39 0
          |
          Java 调度 C++
          ANR分析总结
          ANR分析总结
          1072 0
          ANR分析总结
          |
          存储 监控 Android开发
          Android卡顿优化 | ANR分析与实战(附ANR-WatchDog源码分析及实战、与AndroidPerformanceMonitor的区别)
          Android卡顿优化 | ANR分析与实战(附ANR-WatchDog源码分析及实战、与AndroidPerformanceMonitor的区别)
          |
          8月前
          |
          Android开发
          为什么会触发ANR,从源码中扒一扒
          为什么会触发ANR,从源码中扒一扒
          50 0
          |
          4天前
          |
          JavaScript IDE Java
          bugly崩溃排查3:观察是谁调用了崩溃函数
          bugly崩溃排查3:观察是谁调用了崩溃函数
          24 0
          |
          6月前
          |
          编译器 Android开发
          深度解读Android崩溃日志案例分析1:so崩溃
          深度解读Android崩溃日志案例分析1:so崩溃
          117 1
          |
          6月前
          |
          缓存 Android开发 C++
          [√]Android平台ParticleSystem内存泄露的排查过程
          [√]Android平台ParticleSystem内存泄露的排查过程
          38 1
          |
          12月前
          ANR的三种类型
          ANR的三种类型
          64 0
          |
          Android开发 开发者
          Android系统是如何计算应用启动耗时的?能否更精准定位性能瓶颈呢?
          Android系统是如何计算应用启动耗时的?能否更精准定位性能瓶颈呢?
          Android系统是如何计算应用启动耗时的?能否更精准定位性能瓶颈呢?