一、ANR基础概念与系统机制

ANR 这个词,做 Android 开发的应该都不陌生。说白了就是应用没反应了,系统觉得你在摸鱼,直接弹个对话框问你"要关闭吗?"。我刚开始做 Android 那会儿,第一次看到 ANR 弹窗还以为是手机中毒了……后来才知道,这是系统在替用户"维权"。

这一章我们先打好基础。把 ANR 的定义、触发条件、四类超时类型,以及系统底层怎么检测的,都捋清楚。你只有搞懂了系统怎么判定 ANR,才能反过来定位它。

1.1 什么是 ANR

ANR 的全称是 Application Not Responding,中文就叫"应用无响应"。系统会在主线程(UI 线程)阻塞超过一定时间后,判定该应用已失去响应能力,进而弹出 ANR 对话框,甚至直接杀掉进程。

触发条件其实就一句话:主线程在规定时间内没有完成某件事。这个"规定时间"因场景而异,我们待会细讲。

💡 个人经验: 我在项目中遇到过很多开发新手以为 ANR 只是"卡顿",其实卡顿是主观感受,ANR 是系统层面的硬性判定。一旦触发,系统会记录 traces.txt,这是分析的第一手资料。

看个最简单的例子——在主线程睡 10 秒:

public void onClick(View v) {
    // 主线程阻塞 10 秒 —— 必出 ANR
    try {
        Thread.sleep(10_000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

嗯,这段代码写出来就是找骂的。但确实有线上代码这么干过,不是开玩笑。

1.2 四类超时类型详解

Android 系统把 ANR 分成了四种类型。每一种的触发场景、超时阈值、系统处理方式都不一样。我整理了一张表格,你对比着看会更清楚:

超时类型 触发场景 超时阈值 系统处理方式
输入事件 (InputEvent) 按键、触摸事件分发超时 5 秒 直接弹出 ANR 对话框
广播 (Broadcast) onReceive 执行超时 前台 10 秒 / 后台 60 秒 可配置是否弹窗,低版本直接弹
服务 (Service) 生命周期方法(onCreate 等)超时 20 秒 系统记录日志,视版本弹窗
ContentProvider CP 发布超时或查询超时 10 秒 影响调用方进程,可能连锁 ANR

下面我们逐个拆开来说。

① 输入事件超时(5 秒)

这是最常见、也最"暴力"的一种。系统在分发按键或触摸事件时,如果主线程 5 秒内没有处理完上一个消息,直接判定 ANR。我记得有一次定位一个输入 ANR,最后发现是主线程在做 SharedPreferences 的写操作,数据量大时 I/O 阻塞了。你想想看,SP 的 apply 虽然是异步,但 commit 可是同步的,踩坑的人不在少数。

② 广播超时(前台 10 秒 / 后台 60 秒)

广播的 onReceive 默认跑在主线程。如果你在里面做网络请求、数据库写入、或者调了 Thread.sleep(),超时是必然的。前台广播阈值只有 10 秒,后台宽松一些到 60 秒。但别以为后台 60 秒就很安全——我见过有人在 onReceive 里做图片压缩,一张图压了 30 秒,连着处理两张,直接超时。

③ 服务超时(20 秒)

Service 的生命周期方法(onCreate、onStartCommand、onBind 等)必须在 20 秒内返回。如果你的 Service 在 onCreate 里初始化了 ExoPlayer 或者做了网络预加载,稍不注意就会超时。这和我们后面要讲的 ExoPlayer 初始化超时强相关,先埋个伏笔。

④ ContentProvider 超时(10 秒)

ContentProvider 的发布超时是在进程启动时,系统等待 CP 完成 onCreate 的时间上限是 10 秒。如果 CP 初始化太慢,会导致调用方进程一起 ANR。这种 ANR 的典型特征就是——多个进程同时挂掉,traces.txt 里能看到一堆进程都在等同一个 CP。

ANR 触发机制与四类超时类型 ANR 触发条件 输入事件 (5s) 广播 (10s/60s) 服务 (20s) ContentProvider (10s) 系统 Watchdog 检测 提示:四类超时均通过 AMS 的 Watchdog 机制统一检测,但处理策略不同 输入事件超时直接弹窗,广播和服务可配置,ContentProvider 影响调用链

1.3 系统检测机制

系统怎么知道主线程卡住了?靠的是 Watchdog。它本质上是一个系统级的内核线程,周期性地检查各个关键服务的"心跳"。

具体来说,AMS(ActivityManagerService)内部维护了一个超时监控器。对于上面四类场景,AMS 会分别启动对应的超时计时器。一旦超时,AMS 就会走以下流程:

  1. 收集现场:让系统 dump 出 /data/anr/traces.txt,记录所有线程的堆栈。
  2. 判定类型:根据超时来源,标记是输入事件、广播、服务还是 CP 超时。
  3. 执行策略:弹出 ANR 对话框(可配置)、写入事件日志、或者直接杀进程。

🔍 重要:Watchdog 检测的是"主线程是否在指定时间内完成了指定操作"。它不会管你子线程有多忙,只要主线程能及时处理消息,就不会触发 ANR。所以,优化的核心思路就是——把主线程的活搬走。

我记得有一次排查一个线上 ANR,看 traces.txt 发现主线程卡在了一个 ConcurrentHashMap 的遍历上。奇怪的是,那个遍历逻辑很简单,不应该卡那么久。后来才发现是另一个线程在做结构修改,导致了并发竞争。这就是典型的"主线程被间接阻塞"案例。

1.4 避坑指南

我自己踩过不少 ANR 的坑,也帮别人擦过不少屁股。这里列几个最常见的误区:

  • 误区一:"我用的是异步,不会 ANR" —— 异步回调如果切回主线程做耗时操作,一样会卡。回调里做数据库操作是最常见的翻车现场。
  • 误区二:"后台广播 60 秒阈值很安全" —— 60 秒不是让你用完的。广播接收器里做大量 I/O 或网络请求,不仅 ANR 风险高,还会被系统列入"后台耗电应用"黑名单。
  • 误区三:"ContentProvider 很轻量,不会超时" —— ContentProvider 的 onCreate 跑在主线程,如果你在里头初始化 SDK、加载缓存、或者做跨进程通信,10 秒很容易就超了。

⚠️ 曾经我有个血的教训:在一个社交 App 的 ContentProvider 里初始化了图片加载库的磁盘缓存,结果在低端机上 I/O 阻塞了 12 秒,直接导致桌面进程一起 ANR。从那以后,我所有的 ContentProvider 初始化都做了"懒加载 + 异步"处理。

这一章的内容就到这里。理解了 ANR 的基础概念和四类超时类型,下回看到 traces.txt 的时候,你至少能一眼判断出"这是哪一类 ANR"。这对后续的根因分析至关重要。