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
          相关文章
          |
          监控 调度 Android开发
          看完这篇 Android ANR 分析,就可以和面试官装逼了!
          ANR概述 首先,ANR(Application Not responding)是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。
          8566 0
          |
          Android开发
          ANR怎么产生的,怎么分析ANR?(二)
          ANR怎么产生的,怎么分析ANR?(二)
          256 0
          |
          Android开发
          Android 11 添加Service服务SELinux问题
          Android 11 添加Service服务SELinux问题
          874 1
          |
          Android开发
          Android 13 Qs面板的加载流程
          Android 13 Qs面板的加载流程
          1575 0
          Android 13 Qs面板的加载流程
          |
          Java 调度 C++
          ANR分析总结
          ANR分析总结
          1585 0
          ANR分析总结
          |
          缓存 Java 数据库
          Android的ANR原理
          【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
          652 56
          |
          算法 Java API
          Android性能优化面试题经典之ANR的分析和优化
          Android ANR发生于应用无法在限定时间内响应用户输入或完成操作。主要条件包括:输入超时(5秒)、广播超时(前台10秒/后台60秒)、服务超时及ContentProvider超时。常见原因有网络、数据库、文件操作、计算任务、UI渲染、锁等待、ContentProvider和BroadcastReceiver的不当使用。分析ANR可借助logcat和traces.txt。主线程执行生命周期回调、Service、BroadcastReceiver等,避免主线程耗时操作
          471 3
          |
          Java 测试技术 Android开发
          Android性能测试——发现和定位内存泄露和卡顿
          本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
          1178 4
          Android性能测试——发现和定位内存泄露和卡顿
          |
          XML Android开发 数据格式
          Android 中如何设置activity的启动动画,让它像dialog一样从底部往上出来
          在 Android 中实现 Activity 的对话框式过渡动画:从底部滑入与从顶部滑出。需定义两个 XML 动画文件 `activity_slide_in.xml` 和 `activity_slide_out.xml`,分别控制 Activity 的进入与退出动画。使用 `overridePendingTransition` 方法在启动 (`startActivity`) 或结束 (`finish`) Activity 时应用这些动画。为了使前 Activity 保持静止,可定义 `no_animation.xml` 并在启动新 Activity 时仅设置新 Activity 的进入动画。
          727 12
          |
          Android开发
          38. 【Android教程】Handler 消息传递机制
          38. 【Android教程】Handler 消息传递机制
          407 2