ANR 的阈值与超时机制归纳总结

说到 ANR,很多人第一反应是「不就是超时了嘛」。嗯,对也不对。
不同组件的超时时间完全不一样,检测机制也有细微差别。
我习惯把它们分成四类:Input 事件、Service、BroadcastReceiver 和 ContentProvider。
后来 Android 还加了 JobScheduler 的超时,但那个通常归到 Service 相关。

核心结论:ANR 的阈值不是写死的,不同版本、不同厂商可能有差异。但 AOSP 源码里的默认值,是咱们要掌握的基础。

各组件 ANR 阈值一览

下面这个表是我从 Android 源码里扒出来的,你直接拿去用:

组件类型默认超时时间触发条件备注
Input 事件 (Key/Dispatch)5 秒主线程没能在 5 秒内处理完当前输入事件最常见的一种,用户触摸卡顿时立马弹框
Service20 秒 (前台) / 200 秒 (后台)onStartCommand 或 onCreate 超时前台服务阈值短,容易踩坑
BroadcastReceiver10 秒onReceive 方法执行超过 10 秒注意低内存时会更严格
ContentProvider10 秒创建 Provider 超时一般发生在冷启动首次加载时
JobScheduler几分钟~几十分钟onStartJob 回调超时取决于 Job 的 deadline,系统会按需触发

我自己的经验:Input 5 秒看起来很长,实际上用户连点 3 下没反应就开始骂了。所以我建议把自定义监控的阈值设在 2 秒左右,提前预警。

超时机制到底怎么工作的?

说白了,系统给每个关键组件都装了一个「倒计时器」。
启动组件时开始计时,组件处理完就取消计时。
如果超时器先响了,系统就会标记这个进程为 ANR。

举个例子,Service 的流程是这样的:

// 伪代码,只看逻辑
class ActiveServices {
    void realStartServiceLocked() {
        // 启动倒计时
        scheduleServiceTimeout(service);
        // 执行真实启动
        service.onStartCommand(intent, flags, startId);
    }

    void serviceDoneExecuting() {
        // 取消倒计时
        cancelServiceTimeout(service);
    }

    void serviceTimeout(ServiceRecord r) {
        // 倒计时到 —— 触发 ANR
        appNotResponding("Executing service " + r);
    }
}

你看,逻辑很清晰。但问题出在哪?
我曾经遇到过一个场景:Service 里调了远程 Binder 方法,对方进程卡住了。
Service 本身没做耗时操作,但 Binder 调用是同步的,导致整个 Service 超时。
嗯,这个坑我后面专门讲。

不同场景的超时差异

  • 前台 vs 后台:Service 前台 20 秒,后台 200 秒。系统对用户可见的进程要求更苛刻。
  • 低内存情况:BroadcastReceiver 在低内存时可能 5 秒就炸。别问我怎么知道的……
  • 多窗口&画中画:输入事件的超时可能会略微放宽,但仍然是 5 秒以内。

我曾经踩过的坑:有一次我在 ContentProvider 的 onCreate 里做了数据库迁移,数据量有几十万条。结果首屏就 ANR 了,用户还没看到界面。那时我才知道 Provider 的超时只有 10 秒。后来我把迁移挪到了异步线程,并且用 android:initOrder 控制顺序。

如何用日志定位超时来源?

系统发生 ANR 后,会在 /data/anr/traces.txt 里记录当时的堆栈。
你分析的时候重点看两行:

"main" prio=5 tid=1 Runnable
  | group="main" sCount=0 dsCount=0 obj=0x768a0000 self=0x7f8000
  | sysTid=12345 nice=0 cgroup=foreground
  | schedstat=( 123456 7890 123 )
  at com.example.MyApp.onCreate(MyApp.java:10)
  ...

找 main 线程的堆栈,看它卡在哪一行。通常就是耗时操作或者死锁。

不过要注意,某些低端机的 kernel 可能不生成 traces 文件。
我建议你配合 Logcat,过滤 ANR in 关键词,能把系统判定超时的时刻抓出来。

小技巧:你可以用 adb 命令手动模拟超时,验证自己的监控逻辑:
adb shell am broadcast -a android.intent.action.SLEEP --es duration 15000
(当然,你需要一个自定义 Receiver 接收这个广播并 sleep 15 秒)

一张图看懂 ANR 超时机制

我画了张流程图,把上面所有信息串起来:

ANR 超时机制流程图 ANR 触发源 Input 事件 Service BroadcastReceiver ContentProvider 超时检测 超时? ANR 发生 生成 traces 文件 弹框提示用户 Input 5s、Service 20s/200s 等

看图窍门:从触发源开始,不同组件的超时阈值不一样,但最终都会汇聚到同一个超时检测逻辑。只要倒计时到了,系统就认为你 ANR 了。

总结几个关键点

  • Input 事件 5 秒,是所有类型里最敏感的,因为它直接感知用户操作。
  • Service 分前台后台,后台 200 秒看似很宽松,但系统可能会因为其他原因提前杀进程。
  • BroadcastReceiver 和 ContentProvider 都是 10 秒,前者容易被低内存影响,后者容易在冷启动时触发。
  • 真要规避 ANR,不是靠调整阈值,而是把耗时操作从主线程挪走。阈值只是最后的安全绳。

特别提醒:某些厂商(如华为、小米)可能会自己修改 ANR 阈值。我曾经在小米 6 上见到过 Input 超时 4 秒就弹框的情况。所以,千万不要迷信标准值,最好在你的目标设备上实际测试一下。