ANR(Application Not Responding)是 Android 中的一种常见问题。当应用程序未能在规定时间内响应用户输入、完成后台任务或处理系统消息时,系统会弹出一个对话框,提示"应用无响应"。这通常会严重影响用户体验。
在 Android 系统中,ANR 的触发主要有以下几种情境:
应用的主线程长时间阻塞,导致 UI 无法响应用户操作。
应用在处理特定类型的任务(如广播或服务)时未能在系统设定的时间内完成。
本文将结合 ANR 的源码逻辑和常见场景,深入解析 ANR 的三种主要类型及其解决方案。
一、ANR 的三种主要类型
1. Input Dispatching Timeout(输入事件分发超时)
问题描述
系统对用户输入事件(如点击、触摸等)的分发存在时限。默认情况下,主线程需要在 5 秒 内处理输入事件,否则会触发 ANR。
优化布局和绘制性能使用 ViewStub 或延迟加载减少布局复杂度。
2. BroadcastQueue Timeout(广播处理超时)
问题描述
广播接收器必须在指定时间内完成其任务:
前台广播:10 秒。
后台广播:60 秒。
如果超时,系统会认为应用发生了 ANR。
触发条件
在 onReceive() 方法中执行了耗时操作。
使用了复杂的广播逻辑或多次调用网络请求。
减少广播的频繁触发合理设计广播逻辑,避免不必要的广播。
使用 WorkManager 替代广播对于复杂任务,可以考虑使用 WorkManager,支持异步任务调度。
3. Service Timeout(服务处理超时)
问题描述
服务必须在规定时间内完成启动或执行任务:
前台服务:20 秒。
后台服务:200 秒。
触发条件
服务启动时进行复杂的初始化操作。
服务中执行了长时间未完成的任务。
二、ANR 的排查与分析
- 使用日志定位
在 logcat 中,ANR 相关日志通常以以下关键字开头:
"ANR in":发生 ANR 的应用包名。
"Reason":触发 ANR 的原因。
Reason: Input dispatching timed out (App did not respond to an input event within 5.0s)
Reason: Broadcast of Intent { act=android.intent.action.BOOT_COMPLETED }
Reason: executing service com.example.app/.MyService
"Load":设备负载情况(CPU 使用率等)。
示例:
ANR in com.example.app
PID: 12345
Reason: Input dispatching timed out (App did not respond to an input event within 5.0s)
Load: 5.68 / 4.23 / 2.11
- Trace 文件分析
trace.txt 文件是 Android 系统在发生 ANR 时自动生成的日志文件,记录了系统中所有线程的堆栈信息,特别是 主线程的状态。通过分析这个文件,开发者可以定位导致 ANR 的根本原因。以下是详细讲解分析 trace.txt 的步骤。
2.1 如何获取 trace.txt 文件
adb pull /data/anr/traces.txt
在 logcat 中标记文件生成路径每次发生 ANR 时,logcat 会记录 trace.txt 的路径。
在调试工具中直接查看部分集成开发环境(如 Android Studio)可以在运行时直接查看 ANR 信息。
2.2 trace.txt 的结构
trace.txt 文件主要包括以下内容:
线程信息每个线程的状态以线程名开头,如:
"main" prio=5 tid=1 Native
1
AI助手
"main":线程名称。主线程通常是 main。
prio=5:线程优先级。
tid=1:线程 ID。
Native/VM:线程运行环境。Native 表示本地代码,VM 表示虚拟机代码。
线程状态紧随线程信息的是其状态,如:
| state=RUNNABLE
1
AI助手
常见状态:
RUNNABLE:线程正在运行或等待 CPU 分配。
WAITING/TIMED_WAITING:线程等待某个条件或超时时间到达。
BLOCKED:线程被阻塞,通常因为锁争用。
堆栈信息每个线程的堆栈信息按调用顺序列出,从最顶层开始,如:
2.3 如何分析主线程的堆栈
定位主线程
主线程通常标识为:
"main" prio=5 tid=1 Native
主线程是 ANR 问题的重点,因为 UI 操作、输入事件、服务启动等都依赖于主线程。如果主线程被长时间阻塞,系统会认为应用无响应。
查找线程状态
主线程的状态直接决定了问题的方向:
RUNNABLE:线程在运行或等待 CPU。原因:可能是计算任务过于耗时或 CPU 资源不足。
WAITING:线程正在等待另一个线程的信号或资源。原因:通常是锁等待或阻塞操作。
BLOCKED:线程正在尝试获取另一个线程持有的锁。原因:锁争用或死锁问题。
分析堆栈信息
从主线程堆栈中,重点关注以下几点:
调用路径是否正常:是否存在循环调用或无意义的深层递归。
方法是否耗时:堆栈中是否包含 I/O 操作、网络请求、大量数据处理等。
第三方库问题:检查堆栈中是否包含第三方库的调用,它们可能引发性能问题。
2.4 示例分析
假设 trace.txt 文件中记录了以下主线程堆栈:
"main" prio=5 tid=1 Native
| state=WAITING
at java.lang.Object.wait(Native Method)
- waiting on <0x12345> (a java.lang.Object)
at com.example.app.Worker.performTask(Worker.java:50)
- locked <0x12345> (a java.lang.Object)
at com.example.app.MainActivity.onButtonClick(MainActivity.java:32)
at android.view.View.performClick(View.java:7125)
at android.view.View$PerformClick.run(View.java:28314)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:268)
at android.app.ActivityThread.main(ActivityThread.java:7870)
state=WAITING 表明主线程正在等待另一个线程释放资源。
定位阻塞点
java.lang.Object.wait():主线程在等待一个 java.lang.Object 锁(对象 ID 为 <0x12345>)。
锁被 com.example.app.Worker.performTask 方法持有。
问题根源是 Worker.performTask() 中未能及时释放锁。
解决方法:优化 performTask 方法,确保锁释放及时或改用非阻塞式并发工具。
2.5 综合分析多个线程
ANR 问题可能涉及多个线程的交互。例如,主线程等待某个工作线程,而该工作线程又被其他线程阻塞。这种情况下,需要结合其他线程堆栈信息:
示例
"WorkerThread" prio=5 tid=12 Native
| state=BLOCKED
at com.example.app.DatabaseHelper.query(DatabaseHelper.java:120)
- waiting to lock <0x54321>> (a java.lang.Object) held by tid=14
继续查找 tid=14 的堆栈信息,找到问题根源。
2.6 常见问题与解决策略
问题类型 表现 解决策略
主线程阻塞 堆栈显示主线程处于 WAITING/BLOCKED 检查锁争用,优化主线程任务逻辑。
后台线程卡死 辅助线程长时间运行,导致主线程等待 使用线程池或优化线程任务分配。
耗时操作未异步化 堆栈中出现 sleep() 或 I/O 操作 将耗时操作移至工作线程。
网络或数据库瓶颈 堆栈中显示网络或数据库方法长时间运行 优化查询逻辑或使用缓存。
- 使用 Systrace 或 Perfetto
通过 Systrace 或 Perfetto 工具分析应用的线程调度和性能瓶颈,定位主线程阻塞原因。后续文章会详细讲解Perfetto的相关知识。
三、总结
ANR 的本质是主线程未能在规定时间内完成预期任务或响应输入。
常见类型包括输入事件分发超时、广播处理超时和服务启动超时。
解决方案的核心是优化主线程逻辑,将耗时任务移至后台线程,并充分利用 Android 提供的异步工具(如 AsyncTask、HandlerThread 和 WorkManager)。
通过合理设计代码和使用调试工具,开发者可以有效避免和解决 ANR 问题,提升应用性能和用户体验