ANR基础概念:从定义到系统机制

我刚开始做 Android 开发时,第一次遇到 ANR,那会儿完全不知道发生了什么。屏幕弹出对话框「应用无响应」,用户只能选择等待或关闭。后来我才搞明白——ANR 就是主线程被卡住了,系统觉得你不行了。

ANR 全称 Application Not Responding。说白了,就是主线程在规定时间内没有处理完该做的事。系统为了保证用户体验,会强行弹窗告诉你:这个应用可能没救了。

每种 ANR 的触发场景和超时时间都不一样。我整理了一张表,方便大家对照排查。

ANR 类型超时时间典型触发点堆栈特征
输入事件5秒按键、触摸主线程在 InputEvent 或 handleMessage
广播接收器(前台)10秒onReceive()主线程执行耗时操作
广播接收器(后台)60秒onReceive()同上,超时更长
Service(前台)20秒onCreate / onStartCommand主线程阻塞
Service(后台)200秒onCreate / onStartCommand同上
ContentProvider无固定值query / insert 等主线程执行数据库或网络请求

系统是怎么检测的?

内部有一个 watchdog 线程专门盯着主线程。当输入事件被分发后,系统会启动一个 5 秒的定时器。如果 5 秒内主线程的 Looper 还没处理这条消息,定时器就会炸。然后系统马上收集主线程堆栈,写到 /data/anr/traces.txt,同时弹出对话框。

广播和 Service 的机制差不多,只是超时时间不同。这里注意一点:前台广播的超时只有 10 秒,后台广播是 60 秒。我曾经排查过一个案例,开发者把广播注册在清单里,属于后台广播,但他在 onReceive 里做了网络请求,结果用户反馈在低端机上经常 ANR——就是那 60 秒的宽限也被耗尽了。

核心要点:ANR 的本质是主线程的 Looper 在指定时间内没有处理完消息。系统通过计时器 + 堆栈快照来判定和记录。

常见原因有哪些?

  • 主线程执行 IO 操作(网络、文件、数据库)
  • 复杂布局渲染或大量 Bitmap 解码
  • 线程死锁或锁竞争
  • Binder 调用长期不返回
  • 大量 Log 输出造成磁盘 IO 压力
  • 长时间 GC 暂停(尤其在低端机上)

我遇到过最奇葩的一次是,某同事在 Activity 的 onCreate 里直接调用了 Thread.sleep(15000) 来做所谓的「欢迎页停留」——结果用户还没看到欢迎页,ANR 先弹出来了。

避坑指南:千万不要在主线程中调用 Thread.sleep()、发起同步网络请求或执行大文件读写。我曾经见过一个案例,开发者在点击事件里直接用 OkHttp 的同步请求,导致 ANR 频发,最后只能加上请求超时回调才解决。

一个典型的 ANR 现场

看看这段代码,为什么它会触发输入事件 ANR?

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        try {
            // 模拟耗时操作:睡眠10秒
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

答案很明显:点击事件是在主线程执行的。睡眠 10 秒 > 5 秒的超时阈值,系统就会判定 ANR。实际项目中你不会直接写 sleep,但数据库查询、图片解码、文件 IO 都可能造成类似的效果。

小技巧:当你拿到一份 ANR 的 traces.txt,先看堆栈中最顶层的方法是不是在主线程("main" 线程)。如果是,再看它卡在哪一步——FileInputStream.read、BitmapFactory.nativeDecodeByteArray、SQLiteSession.executeForCursorWindow 这些都是高频嫌疑人。

往下我们来看这个章节的知识体系。下面这张图帮你把 ANR 的基本骨架理清楚。

ANR 知识体系 ANR 定义 输入事件 广播接收器 ContentProvider Service JobScheduler 系统检测机制 常见原因(主线程阻塞) 核心思路:搞清楚 ANR 的触发类型 → 理解系统检测机制 → 定位常见原因,是排查的第一步

理解这些基础概念后,你就能读懂 ANR 日志里的关键字了。举个例子,当你在 traces 里看到 InputDispatcher 相关字样,那基本就是输入事件超时。看到 BroadcastQueue 就是广播 ANR。每种类型对应的排查方向都不一样。

嗯,这一章我们主要把 ANR 的「家底」摸清了。后面章节会深入实战,用具体案例教你如何从日志、堆栈、代码层面一步步定位根因。