1、ANR基础概念:什么是ANR?

ANR,全称是 Application Not Responding,应用无响应。

说白了,就是你的 App 卡住了,系统等得不耐烦,直接弹个对话框问用户:「要强行关掉吗?」 用户一看,大概率点「关闭」。嗯,用户流失就这么来的。

我最早接触 ANR 是做多媒体播放器那年。用户反馈视频点开后黑屏几秒,然后弹出「××无响应」。当时我第一反应是「网络问题」,后来才发现——是主线程在解码器初始化上卡死了。

从系统视角看,ANR 是 Android 保护机制。它会监控你的主线程(UI 线程)有没有在规定时间内完成消息处理。没完成?那就触发 ANR。

1.1 ANR 的类型

系统根据触发场景不同,把 ANR 分成了几种常见类型。我整理了一张表,方便对照:

类型 触发条件 典型场景举例
输入分发超时 主线程 5 秒内未处理完按键或触屏事件 列表滑动时做网络请求,卡住 UI
广播超时 前台广播 10 秒 / 后台广播 60 秒未执行完 onReceive onReceive 里做文件拷贝或数据库写入
服务超时 前台服务 20 秒 / 后台服务 200 秒未执行完 onCreate/onStartCommand 服务启动时加载超大资源文件
ContentProvider 超时 Provider 发布时在 10 秒内未完成 onCreate Provider 初始化时连接远端服务器

其中,和我们多媒体启动卡顿最相关的是 输入分发超时 和 广播超时。为什么?因为启动过程往往发生在用户点击播放按钮后,系统先把点击事件投递到主线程,如果主线程忙着做解码器初始化、资源加载,就超时了。

避坑指南: 我曾经遇到过一个问题:后台广播里做缩略图生成,结果慢了几秒,用户切前台时直接触发前台广播 ANR。后来我把缩略图放到 WorkManager 里,就再没出现过。

1.2 触发条件深度解析

很多人以为只有代码死循环才会触发 ANR。其实不然。触发条件有严格的超时时间,并且分前台和后台两个维度。来看官方的定义:

  • 输入事件:5 秒内没有完成分发(dispatch 返回 false 或没返回)。
  • 前台广播:onReceive 在 10 秒内没执行完。
  • 后台广播:onReceive 在 60 秒内没执行完。
  • 前台服务:生命周期方法 20 秒内没执行完。
  • 后台服务:生命周期方法 200 秒内没执行完。
  • ContentProvider:onCreate 在 10 秒内没执行完。

注意:这些时间是系统在 ActivityManagerService 里硬编码的。不同厂商可能会调高阈值,但 Google 官方是这么定的。

为什么会有前台后台的区别?系统认为前台进程直接影响用户体验,所以阈值更严格。你想想看,用户正在看视频,突然卡住 5 秒,肯定要骂娘。后台服务慢一点,用户感知不到,系统也就宽容一些。

核心要点: 多媒体启动卡顿,99% 发生在「点击播放按钮 → 画面首帧出现」的时间里。这段时间主线程一旦被阻塞超过 5 秒,就会触发「输入分发超时」ANR。所以,我们需要重点关注这个 5 秒窗口。

1.3 ANR 日志长什么样?

眼尖的工程师应该见过这个文件:/data/anr/traces.txt。当 ANR 发生时,系统会把所有线程的堆栈 dump 到这个文件里。我来展示一个典型的片段:

----- pid 12345 at 2025-03-01 10:30:00 -----
Cmd line: com.example.player
CPU usage: 12% total, 85% from app (user=78%, kernel=7%)
JNI: CheckJNI is off; workarounds are off
...
"main" prio=5 tid=1 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x752345f0
  | sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x7a3c4a8000
  at com.example.media.MediaDecoder.init(Native Method)
  at com.example.media.PlayerController.prepare(PlayerController.java:45)
  at com.example.player.MainActivity$1.onClick(MainActivity.java:30)
  - locked <0x07a3c4b0> (a com.example.media.MediaDecoder)
  at android.view.View.performClick(View.java:7444)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:246)
  at android.app.ActivityThread.main(ActivityThread.java:8527)

注意看两个关键信息:

  • main 线程状态是 Blocked:说明主线程在等待某个锁。
  • 堆栈顶部是 MediaDecoder.init (Native Method):解码器初始化是耗时操作,而且居然是 native 方法。我个人的经验是,这种 native 方法阻塞往往和底层驱动或资源加载有关。

看到这里,我们就能确认 ANR 是由多媒体解码器初始化引起的。接下来才能对症下药。

1.4 本章知识脉络图

我把 ANR 的核心概念、类型、触发条件以及它在多媒体场景下的关联画成了一张逻辑图,方便你整体理解:

ANR 基础概念与多媒体卡顿关联图 ANR (应用无响应) ANR 类型 触发条件 日志特征 输入超时 广播超时 服务超时 Provider超时 前台5s / 前台广播10s 前台服务20s / 后台200s traces.txt 主线程 Blocked CPU/内存使用率分析 多媒体启动卡顿:主线程解码器初始化阻塞 💡 聚焦 5 秒输入超时窗口是定位多媒体 ANR 的关键第一步

这张图从 ANR 核心出发,分支出类型、触发条件和日志特征三个维度,最后汇聚到多媒体启动卡顿这个具体场景。这也是我们整门课的定位——从日志反推问题根因。

1.5 小结与行动指南

这一章我们搞清楚了:

  • ANR 本质是主线程超时未响应。
  • 常见类型有 4 种,多媒体场景最常见的是输入分发超时。
  • 触发条件有严格的超时时间,前台比后台更苛刻。
  • 日志里要看主线程堆栈顶部的方法名和线程状态。

警告: 不要以为只有主线程死循环才会卡。很多 ANR 其实是主线程在等一把锁,而锁被其他线程(比如解码器线程或 IO 线程)占着不放。我在一次攻防演练中就吃过这个亏——主线程等锁,锁被一个持有了 3 秒多的文件写入线程占用,加起来刚好超过 5 秒。