Input系统之ANR原理分析

354 阅读5分钟

本文深入解析 Android 输入系统中ANR(应用无响应)的触发原理与处理流程,结合输入系统的整体架构,阐述输入事件处理超时导致 ANR 的核心机制。以下以通俗语言和场景化描述,带你理解 ANR 的触发条件、监测机制及常见场景。

一、ANR 触发的核心场景:输入事件处理超时

Android 系统对输入事件的处理设有严格的超时机制,若应用在规定时间内未完成事件处理,将触发 ANR。核心场景包括:

  1. 应用无响应:应用主线程被阻塞(如耗时操作),无法及时处理输入事件。
  2. 窗口状态异常:窗口处于暂停(paused)、销毁或输入通道阻塞状态,无法接收事件。
  3. 跨进程通信延迟:InputDispatcher 与应用 UI 线程间的 socket 通信延迟,导致事件发送或确认超时。

二、ANR 触发流程:从超时检测到系统响应

2.1 超时检测:InputDispatcher 的等待逻辑

InputDispatcher 在分发事件时,会通过findFocusedWindowTargetsLocked检查目标窗口是否就绪。若窗口未就绪(如应用启动中、输入通道满),进入等待状态:

  • 等待超时时间:默认 5 秒(DEFAULT_INPUT_DISPATCHING_TIMEOUT)。

  • 超时判断:若等待时间超过 5 秒,调用onANRLocked触发 ANR 流程。

cpp

// InputDispatcher.cpp
void InputDispatcher::handleTargetsNotReadyLocked(...) {
    if (currentTime >= mInputTargetWaitTimeoutTime) { // 超过5秒
        onANRLocked(...); // 触发ANR
    }
}

2.2 记录 ANR 现场:onANRLocked 的作用

超时后,InputDispatcher 记录 ANR 现场信息(如应用名称、超时原因、等待时长),并将 ANR 命令加入队列:

cpp

// InputDispatcher.cpp
void InputDispatcher::onANRLocked(...) {
    // 记录ANR日志,如应用名称、超时时间、原因
    mLastANRState.appendFormat("Window: %s, DispatchLatency: %0.1fms", windowLabel, latency);
    // 添加ANR处理命令到队列
    postCommandLocked(&InputDispatcher::doNotifyANRLockedInterruptible);
}

2.3 通知系统处理 ANR:从 Native 到 Java 层的回调

ANR 命令触发后,通过 JNI 调用 Java 层的InputManagerService.notifyANR,最终通知 ActivityManagerService(AMS)处理:

java

// InputManagerService.java
private long notifyANR(...) {
    return mWindowManagerCallbacks.notifyANR(...); // 调用InputMonitor
}

// InputMonitor.java
public long notifyANR(...) {
    // 通知AMS输入分发超时
    ActivityManagerNative.getDefault().inputDispatchingTimedOut(...);
    return KEY_DISPATCHING_TIMEOUT; // 5秒
}

2.4 AMS 处理 ANR:弹出对话框与进程追踪

AMS 收到通知后,触发 ANR 对话框显示,并 dump 进程信息(如主线程堆栈),用于开发者定位问题:

java

// ActivityManagerService.java
public boolean inputDispatchingTimedOut(...) {
    mHandler.post(() -> appNotResponding(...)); // 显示ANR对话框
    return true;
}

三、ANR 的常见原因与日志特征

3.1 超时原因分类

根据checkWindowReadyForMoreInputLocked的检测结果,ANR 原因通常包括:

  1. 窗口未就绪

    • 窗口暂停:应用进入后台或被系统暂停,如Waiting because the focused window is paused
    • 输入通道未注册:窗口正在销毁,如Waiting because input channel is not registered
  2. 应用处理延迟

    • 输入队列积压:应用未及时处理事件,如Outbound queue length: 10, Wait queue length: 5
    • 非按键事件超时:触摸事件等待超过 500ms 未处理,如Wait queue head age: 600ms
  3. 系统状态异常

    • 无焦点窗口:应用有进程但无可见窗口,如No focused window or focused application

3.2 日志特征

ANR 发生时,系统日志(adb logcat)会输出以下关键信息:

log

Input event dispatching timed out sending to com.example.app/.MainActivity. Reason: Window is paused
  • TAGWindowManager,标识输入系统相关 ANR。
  • Reason:具体超时原因,如窗口暂停、队列积压等。

四、死锁监测:Watchdog 与输入系统的健康检查

Android 通过Watchdog 线程监测输入系统线程(InputReader、InputDispatcher)是否死锁,确保流程正常:

  1. 监测机制

    • InputManagerService 启动时加入 Watchdog 的监测队列。

    • Watchdog 定时调用InputReader.monitorInputDispatcher.monitor,通过尝试获取锁并唤醒线程,检测是否卡死。

java

// InputManagerService.java
public void start() {
    Watchdog.getInstance().addMonitor(this); // 加入监测
}
  1. 关键方法

    • InputReader.monitor:获取锁并等待loopOnce的唤醒信号,验证线程活性。
    • InputDispatcher.monitor:类似逻辑,确保分发线程未阻塞。

五、调试与排查:从日志到系统状态

5.1 查看输入系统状态

通过adb shell dumpsys input可获取输入系统实时状态:

  • EventHub:当前注册的输入设备与事件队列。
  • InputReader:事件处理线程状态、设备映射器信息。
  • InputDispatcher:当前处理的事件、等待队列、ANR 历史记录。

5.2 定位 ANR 根源

  1. 日志分析

    • 搜索Input event dispatching timed out,结合Reason字段判断原因。
    • 查看应用主线程堆栈(ANR 对话框中的 “Wait” 标签),确认是否被阻塞。
  2. 性能工具

    • 使用 Systrace 跟踪输入事件流程,定位延迟环节(如 InputDispatcher 分发延迟、UI 线程处理延迟)。
    • 通过 Profiler 分析应用主线程,查找耗时操作(如 I/O、复杂渲染)。

六、总结:ANR 的本质与预防

6.1 本质原因

ANR 是系统对输入响应超时的保护机制,核心矛盾在于:

  • 输入系统的实时性要求:输入事件需在 5 秒内处理完毕。
  • 应用主线程的阻塞风险:耗时操作(如网络请求、复杂计算)导致无法及时响应。

6.2 预防策略

  1. 避免主线程阻塞

    • 将耗时操作移至子线程(如使用 WorkManager、AsyncTask)。
    • 优化 UI 渲染,避免布局过度嵌套或频繁刷新。
  2. 合理管理窗口状态

    • 及时释放不再使用的窗口资源,避免输入通道泄漏。
    • 在窗口暂停(onPause)时暂停非必要的输入处理。
  3. 监控输入延迟

    • 通过自定义逻辑监听输入事件处理耗时,提前预警潜在卡顿。

通过本文,可清晰理解 Android 输入系统 ANR 的触发逻辑、常见场景及排查方法。掌握这些知识有助于在开发中针对性优化输入响应性能,提升应用稳定性。