Android 系统 ANR 触发机制归纳总结

Android ANR 说白了就是系统在「等不及了」。

我和很多同学交流时发现,大家往往只盯着自己代码里的耗时操作,却忽略了系统是怎么判定「卡住」的。搞懂了触发机制你才能知道——为什么有时主线程 sleep 3 秒就 ANR,有时 10 秒都没事?

一、四大组件各自的「超时红线」

每个组件都有自己的超时阈值,记住这张表就够了。

组件类型前台超时后台超时我踩过的坑
Service20s200s后台同步绑了前台通知,超时却按后台算
BroadcastReceiver(broadcast)10s60sonReceive 里做了网络请求,直接崩
Input 事件 (按键/触摸)5s–布局复杂时 ScrollView 滑动竟然能卡 5s+
ContentProvider10s(发布时间)–ContentProvider.onCreate 里初始化数据库写太久

注意了:前台/后台 不是看 App 是否可见,而是看是否处于 Focusable + 可交互 状态。比如 Service 绑定了前台通知,但如果系统判断这个进程是「后台调度」,超时额度仍然按照 200s 算。

核心原则:系统会为每个等待的操作注册一个「定时炸弹」。炸弹倒计时结束时,如果操作还没完成,系统直接炸掉——弹出 ANR dialog 或直接 kill 进程。

二、Service 的 ANR 触发流程

Service 的倒计时从 onStartCommand 或 onBind 返回后才开始。很多人误以为一调 startService 就计时,其实不是。

// 系统会发一个 SERVICE_TIMEOUT_MSG 消息
// 超时后,如果 service 还没有执行完 onCreate/onStartCommand 并返回
// 则触发 ANR
private void serviceTimeout(ProcessRecord proc) {
    synchronized (this) {
        if (proc.execServicesFg != 0 && proc.executingServiceFg) {
            // 前台服务超时
        } else {
            // 后台服务超时
        }
    }
}

我习惯在 Service 的 onStartCommand 里一上来就启动一个工作线程,然后立即返回 START_STICKY 或 START_NOT_STICKY,保证主线程不被阻塞。

避坑指南:我曾经把网络请求直接写在 Service 的 onStartCommand 里,想着反正只跑十几毫秒……结果那段时间网络抖了一下,耗了 22 秒,前台 ANR 直接来了。从此所有耗时操作不管多短都塞到子线程。

三、BroadcastReceiver 的特殊性

广播接收器有两个关键限制:

  • 主线程执行:onReceive 必须在主线程完成,不能开线程
  • 超时窗口:前台 10 秒,后台 60 秒

很多人会问:那后台广播难道就没办法延长?有。通过 goAsync() 返回一个 PendingResult,然后自己管理生命周期。

// 示例:使用 goAsync 延长广播处理时间
@Override
public void onReceive(Context context, Intent intent) {
    final PendingResult pendingResult = goAsync();
    AsyncTask.execute(() -> {
        // 这里做耗时操作
        // 注意!pendingResult.finish() 必须在 60s 内调用
        pendingResult.finish();
    });
}

说白了,goAsync() 就是告诉系统「我还活着,但需要更多时间」。可一旦超时,系统照杀不误。

四、Input 事件——最敏感的那个

用户能感知的 ANR,90% 是 Input 事件超时。系统每 5 秒检查一次:如果主线程没有响应输入事件(触摸、按键),直接判定 ANR。

触发条件比 Service 更苛刻——哪怕你只是绘制耗时,也能造成 Input ANR。比如 View#onDraw 里执行复杂计算,或者 RecyclerView 嵌套多层布局导致 measure 耗时。

个人经验:有一次项目里的弹窗动画用了 CPU 密集型计算,在低端机上每帧渲染超过 16ms,但累计 5 秒后用户点击无反应——ANR 来了。我检查 trace 发现 CPU 占用 90% 在 onDraw 里,后来改用硬件加速和缓存才解决。

五、ContentProvider 发布超时

这个比较隐蔽。当 App 被启动时,AMS 会等待 ContentProvider 发布完成,超时 10 秒。如果你的 ContentProvider 的 onCreate 里初始化了数据库、做了网络请求,十万火急的启动流程就会卡住。

// 系统用 publishContentProviders 方法等待 provider
// 超时后会输出 ANR: "ContentProvider not published"

我见过有团队在 ContentProvider 的 onCreate 里做全量数据库迁移,直接导致进程启动就 ANR。后来改成惰性初始化 + 异步迁移才解决。

六、系统级 ANR 与 Watchdog

除了组件超时,Android 还有一个「终极警察」——Watchdog。它每隔 30 秒检查一次关键系统服务(如 AMS、WMS、PMS)是否无响应。如果某个服务被死锁或长时间持有锁,Watchdog 会直接杀死 system_server 进程,导致手机重启。

所以,你写的 App 如果死锁了 system_server 中的某个锁(比如通过 Binder 调用阻塞),就会触发系统级 ANR。

记住:ANR 不仅仅是用户态的事。Binder 调用超时(默认 1s)、文件描述符泄漏、主线程死锁、甚至是 CPU 过载导致的调度延迟,都可能被系统归纳到 ANR 里。

七、完整触发链路图

下面这张图把整个机制串起来,方便记忆。

Android ANR 触发机制流程 应用线程执行操作 Service Broadcast Input事件 ContentProvider AMS 注册定时炸弹 超时判定(T > 阈值) ANR dialog 弹窗 输入事件 ANR → 杀进程 dump /data/anr/traces.txt Watchdog 30s 轮询 说明 虚线表示 Watchdog 额外路径 实线为主流程 ANR dialog 仅对前台用户可见

八、总结——记住三个层次

  1. 应用层:四大组件的超时阈值各不同,要按场景分别防御。
  2. 系统层:AMS 是总管,Watchdog 是保镖,它们共同守护界面响应性。
  3. 内核层:Binder 超时、锁竞争、CPU 饥饿,都是隐藏的 ANR 导火索。

搞明白了触发机制,你才能对症下药。我做个项目时,App 老是在后台 Service 里 ANR,排查了半天才发现是前台通知的 flag 没设置对导致系统按后台超时算。了解阈值细节能帮你省下大把抓头发的时间。

嗯,这一章就到这里。希望你能把这张表格和图刻在脑子里——下次遇到 ANR,先看超时类型,再看组件,最后反推代码。思路就清晰了。