Android系统ANR机制源码解析

ANR(Application Not Responding)说白了就是系统觉得你的App“卡死”了。我在项目早期调试一个视频播放器时,第一次抓到ANR日志,看到“Input dispatching timed out”还以为是自己代码写崩了。后来跟了几遍源码才明白——ANR其实是AMS(ActivityManagerService)和InputDispatcher联手搞的一套“看门狗”机制。

今天我就带你把这套机制的源码脉络理一遍。不要求你记住每一行代码,但核心逻辑和关键节点必须印在脑子里。原因很简单——你不懂系统怎么判定ANR,就永远写不出精准的埋点。

三大触发场景

Android系统会在三种场景下判定ANR:

  • Service超时:前台Service 20秒,后台Service 200秒没执行完onStartCommand。
  • Broadcast超时:前台广播10秒,后台广播60秒没从onReceive返回。
  • Input超时:触摸/按键事件5秒内没被处理完。

嗯,这里要注意:ContentProvider 和 JobScheduler 也会触发ANR,但系统没有单独的超时类型,它们通常表现为Service或Input超时。

源码入口:AMS中的调度器

所有超时检测的起点都在 ActivityManagerService 里。我记得第一次看这个类时,被它那几千行的代码吓到过。其实它内部用了一堆 Handler 和 Message 来管理超时。

比如Service超时的核心逻辑在 ServiceRecord 里:

// ServiceRecord.java (简化)
void startTimeoutLocked(long now) {
    if (timeout != null) {
        mAm.mHandler.removeCallbacks(timeout);
    }
    timeout = () -> {
        synchronized (mAm) {
            // 检查是否仍在执行超时限制内
            if (isExecutingRemotely()) {
                mAm.appNotResponding(proc, this, null, null, false, "service timeout");
            }
        }
    };
    // 前台20秒,后台200秒
    long delay = foreground ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT;
    mAm.mHandler.postDelayed(timeout, delay);
}

这个 appNotResponding() 就是ANR爆发点。它会做三件事:

  1. 收集所有线程的stack trace(通过 Process.myTid() 和信号机制)
  2. 生成 traces.txt 文件
  3. 弹出ANR对话框或者直接杀进程
关键类调用链:
ServiceRecord.startTimeoutLocked() → AMS.appNotResponding() → Process.sendSignal() → Debug.dumpNativeAndJavaTraces()

输入超时的特殊之处

Input方面的ANR和AMS其实分属两条线。它由 InputDispatcher 在native层直接检测。当事件分发到窗口后,如果5秒内没有收到“处理后”的回执,InputDispatcher会调用 InputManagerService 的回调,最终也走到 AMS.appNotResponding()。

我自己调试时就踩过一个坑:Input ANR的日志里经常写“Waiting because the touched window has not finished handling input events”,很多人以为是主线程卡,但实际上可能是Binder线程池满了导致消息发不出去。

ANR类型对比表格

类型 超时时间 触发处理器 典型堆栈特征
Service 前台20s / 后台200s AMS.mHandler ActivityManagerService.waitForService
Broadcast 前台10s / 后台60s BroadcastQueue.mHandler BroadcastQueue.processNextBroadcast
Input 5s InputDispatcher (native) InputDispatcher.dispatchOnce
ContentProvider 10s (通常) AMS ProviderRefCount.wait
我个人的一个小习惯: 抓ANR日志后,先看 traces.txt 里主线程的 java.lang stack。如果主线程在等待锁(比如 BinderProxy.transact() 或 Object.wait()),那多半不是主线程慢,而是另一个线程太慢导致它被堵住了。

ANR机制全流程(SVG)

Android ANR机制核心流程图 App主线程 AMS / InputDispatcher 超时消息到达 执行任务 超时 收集Java/Native栈 写入traces.txt 弹对话框 / 杀进程 常见ANR来源 • 主线程做IO/网络请求 • Binder调用阻塞 • 锁竞争导致等待太久

避坑指南:别被“无响应”骗了

我曾经遇到过一件怪事:App明明在正常滑动,却弹出了ANR对话框。翻看 traces.txt,主线程停在 InputMethodManager 的binder调用上。后来发现是系统输入法服务重启了,导致binder返回超时。这种ANR其实和App性能无关,是系统侧的问题。

所以我的建议是:不要一看到ANR就往自己代码上找原因。先分清楚是系统服务超时还是App主线程阻塞。怎么分?看 traces.txt 里的“why”字段——如果是 Input dispatching timed out,重点查Input Channel;如果是 executing service,去查那个Service的生命周期。

注意: Android 12之后,系统对后台Service和Broadcast的超时检测更严格了。如果你的App targetSdkVersion >= 31,后台Service 200秒必定触发ANR,JobScheduler 都无法豁免。我建议你早点加上 android:foregroundServiceType 并启动前台通知,否则用户手机静置几分钟就可能弹出ANR。

一点源码阅读经验

如果你是第一次看 ActivityManagerService 源码,很容易被“谁调用了谁”绕晕。我个人的读法:先找到 appNotResponding() 方法,倒着往上找出触发它的所有路径。大体上就三条:Service超时、Broadcast超时、Input超时。把这三条链分别啃下来,整体脉络就清晰了。

另外,traces.txt 的生成其实是靠 Process.sendSignal(pid, SIGNAL_QUIT) 触发的。你可以在终端手动执行 adb shell kill -3 [pid] 立刻触发一次stack dump,效果完全一样。我调试时经常用这招手动“模拟ANR”,不看对话框,只抓堆栈。