Skip to content

ANR 消息边界

从 InputDispatcher、AMS 服务计时器和 AnrHelper 源码追踪超时检测、主线程阻塞与 ANR 处理边界。

基于android-17.0.0_r1
AndroidANRMessageQueueInputDispatcher源码阅读

ANR 消息边界 ​

ANR 不是“主线程超过某个固定毫秒数”这一条规则,而是某个受监管的事件或服务没有在其 当前 deadline 前完成。Android 17 至少有三层相关实现:InputDispatcher 追踪 InputChannel 等待 ACK;AMS/ActiveServices 为 Service 执行计时;AnrHelper 在检测结果产生后异步收集 trace 和处理记录。主线程 MessageQueue 阻塞会让应用无法消费输入或 Service 生命周期消息, 但 ANR 的检测者、deadline 和后续处理并不都在这个 MessageQueue 中。

本文面向已经读过 Looper消息监控、输入事件派发路径、 AMS Handler 分流 和 WMS Handler 分流 的读者。本文只追踪输入 dispatch timeout、Service timeout 和 AnrHelper 的连接,不列出所有 ANR 类型、固定秒数或产品策略。重点是辨认“谁启动 deadline、谁发现过期、谁提交处理、哪条 消息仍可能在主线程排队”。

读完后,读者应能从 ANR reason 反查检测 owner,区分 InputDispatcher 的 native wait queue 与应用 MessageQueue,解释 Service timeout 消息为何需要在成功路径撤销,并判断 AnrHelper 开始处理后为什么不等于用户对话框已经显示。

1. 检测分层 ​

源码文件:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

InputDispatcher 在 native 线程中维护 connection 的 wait queue 和 mAnrTracker;它根据每个 window 的 dispatching timeout 计算下一次 ANR 检查时间。

cpp
nsecs_t InputDispatcher::processAnrsLocked() {
    const nsecs_t currentTime = now();
    nsecs_t nextAnrCheck = LLONG_MAX;

    if (mNoFocusedWindowAnrState.has_value()) {
        if (currentTime >= mNoFocusedWindowAnrState->timeoutEndTime) {
            processNoFocusedWindowAnrLocked();
            resetNoFocusedWindowTimeoutLocked();
            return LLONG_MIN;
        }
        nextAnrCheck = mNoFocusedWindowAnrState->timeoutEndTime;
    }

    nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
    if (currentTime < nextAnrCheck) {
        return nextAnrCheck;
    }
    // 找到过期 connection 并调用 onAnrLocked
}

InputDispatcher 的检查并不依赖应用主线程 Handler;它自己的 Looper/线程在 deadline 到达时 发现 ACK 等待超时,再通过 policy 进入 system_server 的 ANR 处理。

2. 输入 deadline ​

源码文件:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

事件写入 connection 后,startDispatchCycleLocked() 为 dispatch entry 写入 delivery time 和 timeout time。timeout 可由窗口配置提供,否则使用默认值;不能把一个旧版本的固定常数 写成所有窗口的当前保证。

cpp
const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
dispatchEntry->deliveryTime = currentTime;
dispatchEntry->timeoutTime = currentTime + timeout.count();

超时值由 connection/window 状态继续决定:

cpp
std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
        const std::shared_ptr<Connection>& connection) {
    if (connection->isFocusMonitor) {
        return mMonitorDispatchingTimeout;
    }
    const sp<WindowInfoHandle> window =
            mWindowInfos.findWindowHandle(connection->getToken());
    if (window != nullptr) {
        return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
    }
    return DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}

应用收到事件后,必须沿 InputEventReceiver → ViewRootImpl → finishInputEvent() 回写 ACK; 消息异步只改变 MessageQueue 的选择资格,不会让 native wait queue 认为事件已完成。

3. 输入过期 ​

源码文件:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

当 mAnrTracker.firstTimeout() 到期,InputDispatcher 找到 connection,标记为不响应,移除 tracker token,并调用 onAnrLocked()。如果 connection 已不存在,tracker entry 会被删除, 避免同一错误反复记录。

cpp
std::shared_ptr<Connection> connection =
        mConnectionManager.getConnection(mAnrTracker.firstToken());
if (connection == nullptr) {
    mAnrTracker.eraseToken(mAnrTracker.firstToken());
    return nextAnrCheck;
}

connection->responsive = false;
mAnrTracker.eraseToken(connection->getToken());
onAnrLocked(connection);
return LLONG_MIN;

onAnrLocked() 还会再次检查 wait queue 是否已经恢复为空;如果应用恰好在检测前完成 ACK, 源码会记录“已恢复”并不升格为 ANR。这是检测时状态复查,而不是 Handler 的取消消息。

cpp
if (connection->waitQueue.empty()) {
    ALOGI("Not raising ANR because the connection has recovered");
    return;
}

4. 服务计时器 ​

源码文件:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

Service 超时由 ActiveServices 的计时器对象维护,计时消息使用 AMS mHandler 的消息编号 消费。当前源码把普通执行服务、短时前台服务和启动前台服务拆成不同 timer,不应简化成一个 固定 SERVICE_TIMEOUT。

java
private final ProcessAnrTimer mActiveServiceAnrTimer =
        new ProcessAnrTimer(service,
                ActivityManagerService.SERVICE_TIMEOUT_MSG,
                "SERVICE_TIMEOUT", /* args */);
private final ServiceAnrTimer mServiceFGAnrTimer =
        new ServiceAnrTimer(service,
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                "SERVICE_FOREGROUND_TIMEOUT", /* args */);

服务开始执行时,timer 记录 ProcessRecord/ServiceRecord 和 deadline;服务完成、停止或状态 改变时由对应 owner 清理 timer。消息到达后,ActiveServices 还要检查进程是否仍执行服务、 是否被杀和当前 foreground 状态。

5. 服务超时消费 ​

源码文件:frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

普通 service timeout 的消费方法先在 AMS 锁下确认进程和执行服务状态,再选择实际超时记录, 最后交给 AnrHelper 或 warning 路径。消息进入 AMS Handler 不等于一定会产生 ANR。

java
void serviceTimeout(ProcessRecord proc) {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceTimeout()");
        synchronized (mAm) {
            if (proc.isDebugging() || proc.mServices.numberOfExecutingServices() == 0
                    || proc.getThread() == null || proc.isKilled()) {
                return;
            }
            // 找到仍然超时的 ServiceRecord,再进入 ANR 处理
        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    }
}

这条路径和输入 ANR 的检测者不同:InputDispatcher 在 native connection 上计时,Service timeout 由 system_server 的 AMS Handler/计时器消费,但最终都需要判断目标状态是否仍成立。

6. ANR 记录 ​

源码文件:frameworks/base/services/core/java/com/android/server/am/AnrHelper.java

AnrHelper.appNotResponding() 不同步执行完整 dump,而是先在 mAnrRecords 锁下去重、提交 早期进程 dump,并把 AnrRecord 放入队列。

java
void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
    final int incomingPid = anrProcess.mPid;
    synchronized (mAnrRecords) {
        if (incomingPid == 0 || mProcessingPid == incomingPid) {
            return;
        }
        if (!mTempDumpedPids.add(incomingPid)) {
            return;
        }
        Future<File> firstPidDumpPromise = mEarlyDumpExecutor.submit(() ->
                StackTracesDumpHelper.dumpStackTracesTempFile(
                        incomingPid, timeoutRecord.mLatencyTracker));
        mAnrRecords.add(new AnrRecord(
                anrProcess, timeoutRecord, firstPidDumpPromise));
    }
    startAnrConsumerIfNeeded();
}

去重条件意味着同一 pid 的 ANR 可能被跳过;“检测到超时”与“新增一条 ANR 处理记录”不是 一一对应。

7. ANR 消费线程 ​

源码文件:frameworks/base/services/core/java/com/android/server/am/AnrHelper.java

startAnrConsumerIfNeeded() 使用原子 mRunning 保证只启动一个 consumer。consumer 从记录 队列取出一个 ANR,检查进程 pid 是否仍匹配,再调用 AnrRecord.appNotResponding()。

java
private void startAnrConsumerIfNeeded() {
    if (mRunning.compareAndSet(false, true)) {
        new AnrConsumerThread().start();
    }
}

private AnrRecord next() {
    synchronized (mAnrRecords) {
        if (mAnrRecords.isEmpty()) return null;
        AnrRecord record = mAnrRecords.remove(0);
        mProcessingPid = record.mPid;
        return record;
    }
}

取出记录后,consumer 才检查 pid 是否仍匹配并执行 ANR 处理:

java
public void run() {
    AnrRecord record;
    while ((record = next()) != null) {
        final int currentPid = record.mApp.mPid;
        if (currentPid != record.mPid) {
            continue;
        }
        record.appNotResponding(/* onlyDumpSelf */ false);
    }
    mRunning.set(false);
}

ANR consumer 不是 AMS MainHandler,也不是 InputDispatcher 线程;它是后续处理线程。主线程 阻塞可能是 ANR 诱因,但 ANR trace 采集和处理不应再压回同一条主 Handler。

8. 消息阻塞 ​

源码文件:frameworks/base/core/java/android/os/Looper.java

Looper 的 Printer/slow log 可以区分 delivery 与 dispatch,但 ANR 检测不只是“某条消息慢”。 如果主线程在长 callback 内,下一条输入消息可能已经被 MessageQueue 接收但尚未 dispatch; InputDispatcher 观察的是 ACK wait queue,Looper 观察的是自身 dispatch 区间。

java
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
msg.target.dispatchMessage(msg);
final long dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;

主线程也可能阻塞在锁、Binder、native I/O 或 GC,而不是 Java Handler 的 handleMessage() 代码;因此 Message logging 只能缩小范围,不能替代 ANR trace。

9. 输入与 Handler ​

源码文件:frameworks/base/core/java/android/view/ViewRootImpl.java

ViewRootImpl 把输入事件放进自己的 pending queue,必要时发送异步 MSG_PROCESS_INPUT_EVENTS; 即使该消息绕过 traversal barrier,也不能抢占当前正在执行的 callback。

java
private void scheduleProcessInputEvents() {
    if (!mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = true;
        Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }
}

这条代码解释“输入消息在队列里但仍发生 ANR”:异步只改变屏障选择,当前主线程忙碌时仍不能 取出并完成 finishInputEvent()。

10. 超时复查 ​

源码文件:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

InputDispatcher 在 onAnrLocked() 中复查 wait queue;WMS/AMS 的 Service timeout 也会在 handler case 中检查进程、线程和执行服务状态。所有检测器都遵循“deadline 到达后再次确认” 原则,避免把已恢复对象误报为 ANR。

text
deadline 到达
  -> 查找仍存在的 connection/ProcessRecord/ServiceRecord
  -> 检查 wait queue、进程存活和执行状态
  -> 仍未完成:标记 unresponsive,进入 ANR 处理
  -> 已恢复/对象消失:清理计时状态,不升级

因此不能只看一条“timeout message”就断言 ANR 已发生;应跟进检测函数的最终条件和状态写入。

11. Watchdog 边界 ​

源码文件:frameworks/base/services/core/java/com/android/server/Watchdog.java

Watchdog 监控的是 system_server Handler 线程,通过把检查 Runnable 投递到目标 Handler,再 观察 mCompleted 是否在 deadline 前变为 true。它保护的是 system_server 线程活性,不是应用 输入/Service ANR。

java
public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
    mCompleted = false;
    mStartTime = SystemClock.uptimeMillis();
    mHandler.postAtFrontOfQueue(this);
}

@Override
public void run() {
    mCompleted = true;
}

把 Watchdog 的 system_server 处置逻辑和应用 ANR 混为一谈,会错误地认为每个 Handler timeout 都会杀进程;两者检测 owner、结果和恢复策略不同。

12. 证据阅读 ​

源码文件:frameworks/base/services/core/java/com/android/server/am/AnrHelper.java

AnrHelper 记录 mTimestamp,consumer 计算 report latency;如果处理排队太久,源码会选择 只 dump ANR 进程,避免收集已经过时且进一步增加系统负载的全量信息。

java
final long startTime = SystemClock.uptimeMillis();
final long reportLatency = startTime - record.mTimestamp;
final boolean onlyDumpSelf = reportLatency > EXPIRED_REPORT_TIME_MS
        || startTime < SELF_ONLY_AFTER_BOOT_MS;
record.appNotResponding(onlyDumpSelf);

这说明 traces 的内容受采集时延和启动阶段影响;一份 trace 是某个时间点的证据,不是永远 有效的线程状态快照。

13. 验证输入 ​

源码文件:frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

第一组验证设置 connection 的 dispatch timeout,发送事件但不回 ACK,推进 dispatcher 时钟, 断言 processAnrsLocked() 标记 connection 不响应;随后补 ACK/清空 wait queue,验证恢复 分支不会再次升级。

第二组验证使用 AMS/ActiveServices 的注入 Handler:安排 Service timeout,先模拟服务完成并 移除 timer,再推进 Handler;断言不会调用 ANR 处理。另一输入保持 executing service 状态, 断言进入 timeout/AnrHelper。

第三组验证 AnrHelper:提交同一 pid 的两个 appNotResponding(),断言处理中/已排队/预 dump 状态会去重;让 consumer 延迟,验证 onlyDumpSelf 的过期分支。以上验证不能外推固定秒数或 真实设备 CPU/IO 行为。

14. 复查命令 ​

源码文件:

  • frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
  • frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
  • frameworks/base/services/core/java/com/android/server/am/AnrHelper.java
  • frameworks/base/core/java/android/os/Looper.java
  • frameworks/base/core/java/android/view/ViewRootImpl.java
  • frameworks/base/services/core/java/com/android/server/Watchdog.java
bash
rg -n "processAnrsLocked|mAnrTracker|getDispatchingTimeoutLocked|onAnrLocked" \
  frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

rg -n "ProcessAnrTimer|SERVICE_TIMEOUT_MSG|serviceTimeout|scheduleServiceTimeout" \
  frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

rg -n "appNotResponding|mAnrRecords|startAnrConsumerIfNeeded|onlyDumpSelf" \
  frameworks/base/services/core/java/com/android/server/am/AnrHelper.java

rg -n "MSG_PROCESS_INPUT_EVENTS|setAsynchronous|doProcessInputEvents" \
  frameworks/base/core/java/android/view/ViewRootImpl.java

阅读 ANR 时应按“检测 owner → deadline 状态 → 最终复查 → 处理 owner → 证据时间点”走源码。 只看到主线程 stack 在 MessageQueue 或某个 Handler 中,不能单独证明 ANR 类型和根因。

15. 适用边界 ​

消息队列阻塞是许多 ANR 的共同诱因,但不是所有 ANR 的统一实现。InputDispatcher、AMS Service timer、Watchdog 和 AnrHelper 各有独立 owner、线程和状态。异步 Message 不能抢占忙碌 主线程,Handler 日志不能替代 native dispatcher 状态和 ANR trace。

排查时先确认 ANR reason,再回到对应检测器:输入看 connection wait queue 与 ACK,Service 看执行记录和 timer,system_server 看 Watchdog,处理阶段看 AnrHelper 的去重、排队和采集时延。 这样才能避免用一个“主线程卡住”结论覆盖不同的源码路径。