一文教会你如何解决Android的ANR问题

ANR(Application Not Responding)是 Android 中的一种常见问题。当应用程序未能在规定时间内响应用户输入、完成后台任务或处理系统消息时,系统会弹出一个对话框,提示“应用无响应”。这通常会严重影响用户体验。

在 Android 系统中,ANR 的触发主要有以下几种情境:

  • 应用的主线程长时间阻塞,导致 UI 无法响应用户操作。
  • 应用在处理特定类型的任务(如广播或服务)时未能在系统设定的时间内完成。
    本文将结合 ANR 的源码逻辑和常见场景,深入解析 ANR 的三种主要类型及其解决方案。

一、ANR 的三种主要类型

1. Input Dispatching Timeout(输入事件分发超时)
问题描述

系统对用户输入事件(如点击、触摸等)的分发存在时限。默认情况下,主线程需要在 5 秒 内处理输入事件,否则会触发 ANR。

触发条件
  • 主线程被耗时任务(如 I/O、计算任务)阻塞。
  • 主线程因线程同步(如锁竞争)而无法处理输入事件。
源码分析

输入事件由 InputDispatcher 模块负责分发,其主要逻辑如下:

void dispatchInputEvent(InputEvent event) {
    if (isMainThreadBlocked()) {
        scheduleInputTimeout(); // 触发超时处理
    } else {
        deliverEventToApp(event); // 正常分发
    }
}

ActivityManagerService 中,如果发现某个应用未响应,则记录并上报:

if (!appResponding) {
    mHandler.postDelayed(() -> {
        logAnrEvent();
        showAnrDialog();
    }, ANR_TIMEOUT); // 5 秒默认超时时间
}
解决方案
  • 避免主线程阻塞确保耗时任务在工作线程中完成,主线程仅用于处理 UI 相关的逻辑。
// 示例:在主线程中避免长时间操作
new Thread(() -> {
    performHeavyOperation(); // 耗时任务
    runOnUiThread(() -> updateUI());
}).start();
  • 排查线程同步问题检查是否存在锁竞争,避免长时间占用主线程:
synchronized (lock) {
    performCriticalTask(); // 确保锁范围尽可能小
}
  • 优化布局和绘制性能使用 ViewStub 或延迟加载减少布局复杂度。
    在这里插入图片描述
2. BroadcastQueue Timeout(广播处理超时)
问题描述

广播接收器必须在指定时间内完成其任务:

  • 前台广播:10 秒。
  • 后台广播:60 秒。
    如果超时,系统会认为应用发生了 ANR。
触发条件
  • 在 onReceive() 方法中执行了耗时操作。
  • 使用了复杂的广播逻辑或多次调用网络请求。
源码分析

广播超时主要由 BroadcastQueue 管理:

void processNextBroadcast() {
    if (currentBroadcast.isRunningLong()) {
        handleBroadcastTimeout(); // 广播超时处理
    }
}

若某个广播未在指定时间内完成,系统会触发 AppNotRespondingDialog

if (isTimeout) {
    triggerAnrDialogForBroadcast();
}
解决方案
  • 避免在广播中执行耗时操作使用 IntentService 或后台线程处理耗时任务:
@Override
public void onReceive(Context context, Intent intent) {
    new Thread(() -> performLongTask()).start();
}
  • 减少广播的频繁触发合理设计广播逻辑,避免不必要的广播。
  • 使用 WorkManager 替代广播对于复杂任务,可以考虑使用 WorkManager,支持异步任务调度。
  • 在这里插入图片描述
3. Service Timeout(服务处理超时)
问题描述

服务必须在规定时间内完成启动或执行任务:

  • 前台服务:20 秒。
  • 后台服务:200 秒。
触发条件
  • 服务启动时进行复杂的初始化操作。
  • 服务中执行了长时间未完成的任务。
源码分析

服务启动逻辑由 ActiveServices 管理:

void startServiceLocked() {
    if (timeoutOccurred) {
        throwAnrException(); // 抛出 ANR 异常
    }
}

如果服务未在规定时间内完成启动,则记录日志并显示 ANR 对话框:

if (timeoutHappens) {
    logServiceTimeout();
    showAnrDialog();
}
解决方案
  • 优化服务启动逻辑避免在 onStartCommand()onCreate() 方法中执行耗时任务:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(() -> performHeavyInitialization()).start();
    return START_STICKY;
}
  • 分解复杂任务将任务拆分为多个小任务,并通过定时器或线程池异步执行。
  • 使用 WorkManager 替代长时间运行的服务对于需要后台执行的任务,推荐使用 WorkManager,其生命周期更加安全可靠。
    在这里插入图片描述

二、ANR 的排查与分析

1. 使用日志定位

在 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
2. Trace 文件分析

trace.txt 文件是 Android 系统在发生 ANR 时自动生成的日志文件,记录了系统中所有线程的堆栈信息,特别是 主线程的状态。通过分析这个文件,开发者可以定位导致 ANR 的根本原因。以下是详细讲解分析 trace.txt 的步骤。

2.1 如何获取 trace.txt 文件
  1. 从设备中提取使用 ADB 命令获取 ANR 堆栈:
adb pull /data/anr/traces.txt
  1. 在 logcat 中标记文件生成路径每次发生 ANR 时,logcat 会记录 trace.txt 的路径。
  2. 在调试工具中直接查看部分集成开发环境(如 Android Studio)可以在运行时直接查看 ANR 信息。
2.2 trace.txt 的结构

trace.txt 文件主要包括以下内容:

  1. 线程信息每个线程的状态以线程名开头,如:
"main" prio=5 tid=1 Native
  • "main":线程名称。主线程通常是 main。
  • prio=5:线程优先级。
  • tid=1:线程 ID。
  • Native/VM:线程运行环境。Native 表示本地代码,VM 表示虚拟机代码。
  1. 线程状态紧随线程信息的是其状态,如:
| state=RUNNABLE

常见状态:

  • RUNNABLE:线程正在运行或等待 CPU 分配。
  • WAITING/TIMED_WAITING:线程等待某个条件或超时时间到达。
  • BLOCKED:线程被阻塞,通常因为锁争用。
  1. 堆栈信息每个线程的堆栈信息按调用顺序列出,从最顶层开始,如:
at android.os.SystemClock.sleep(SystemClock.java:112)
at com.example.app.MainActivity.longRunningTask(MainActivity.java:42)
at com.example.app.MainActivity.onClick(MainActivity.java:30)
2.3 如何分析主线程的堆栈
  1. 定位主线程

主线程通常标识为:

"main" prio=5 tid=1 Native

主线程是 ANR 问题的重点,因为 UI 操作、输入事件、服务启动等都依赖于主线程。如果主线程被长时间阻塞,系统会认为应用无响应。

  1. 查找线程状态

主线程的状态直接决定了问题的方向:

  • RUNNABLE:线程在运行或等待 CPU。原因:可能是计算任务过于耗时或 CPU 资源不足。
  • WAITING:线程正在等待另一个线程的信号或资源。原因:通常是锁等待或阻塞操作。
  • BLOCKED:线程正在尝试获取另一个线程持有的锁。原因:锁争用或死锁问题。
  1. 分析堆栈信息

从主线程堆栈中,重点关注以下几点:

  • 调用路径是否正常:是否存在循环调用或无意义的深层递归。
  • 方法是否耗时:堆栈中是否包含 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)
分析步骤:
  1. 线程状态
  • state=WAITING 表明主线程正在等待另一个线程释放资源。
  1. 定位阻塞点
  • java.lang.Object.wait():主线程在等待一个 java.lang.Object 锁(对象 ID 为 <0x12345>)。
  • 锁被 com.example.app.Worker.performTask 方法持有。
  1. 分析调用链
  • 主线程调用了 MainActivity.onButtonClick(),而此方法依赖了 Worker.performTask()。
  • 堆栈显示 Worker.performTask 持有锁 <0x12345> 并未释放,导致主线程阻塞。
  1. 推断问题
  • 问题根源是 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&gt> (a java.lang.Object) held by tid=14
  • WorkerThread 被阻塞,原因是它在 query() 方法中试图获取锁 <0x54321>,而该锁被线程 tid=14 持有。
  • 继续查找 tid=14 的堆栈信息,找到问题根源。
2.6 常见问题与解决策略
问题类型表现解决策略
主线程阻塞堆栈显示主线程处于 WAITING/BLOCKED检查锁争用,优化主线程任务逻辑。
后台线程卡死辅助线程长时间运行,导致主线程等待使用线程池或优化线程任务分配。
耗时操作未异步化堆栈中出现 sleep() 或 I/O 操作将耗时操作移至工作线程。
网络或数据库瓶颈堆栈中显示网络或数据库方法长时间运行优化查询逻辑或使用缓存。
3. 使用 Systrace 或 Perfetto

通过 Systrace 或 Perfetto 工具分析应用的线程调度和性能瓶颈,定位主线程阻塞原因。后续文章会详细讲解Perfetto的相关知识。

三、总结

  • ANR 的本质是主线程未能在规定时间内完成预期任务或响应输入。
  • 常见类型包括输入事件分发超时、广播处理超时和服务启动超时。
  • 解决方案的核心是优化主线程逻辑,将耗时任务移至后台线程,并充分利用 Android 提供的异步工具(如 AsyncTask、HandlerThread 和 WorkManager)。
    通过合理设计代码和使用调试工具,开发者可以有效避免和解决 ANR 问题,提升应用性能和用户体验。

参考

诊断和修复 ANR

### ANR (Application Not Responding) 原因及解决方法 #### 主要成因 ANR现象主要发生在应用程序的用户界面(UI)线程未能在规定时间内完成特定任务的情况下。具体来说: - **按键或触摸事件**:如果这些交互事件超过5秒未被处理,就会触发ANR警告[^1]。 - **BroadcastReceiver组件**:当此类组件接收广播消息后的执行时间超过了10秒钟,则会引发ANR提示。 - **Service服务调用**:对于长时间运行的服务,在其首次创建时若花费的时间超出20秒也会导致ANR情况的发生。 上述情形通常是因为当前正在处理的任务要么完全没有得到机会被执行,要么虽然已经开始但因为计算量过大或其他因素而未能迅速结束[^3]。 #### 导致ANR的具体场景 除了以上提到的时间限制外,还有几种常见的情况可能导致ANR: - 进行复杂的图形渲染或者大量的DOM操作; - 执行网络请求、文件读写等I/O密集型工作; - 处理大规模数据集或是进行深度嵌套循环等高负载CPU运算; - 错误地将耗时较长的操作放在了主线程中而不是异步方式下进行; 这些问题都可能使得UI线程陷入忙碌状态,从而影响到整个应用对外界输入做出即时反馈的能力[^4]。 #### 解决方案建议 为了避免ANR问题并提高用户体验质量,可以采取如下措施来优化代码逻辑和架构设计: - 将任何潜在的大规模计算移至后台线程(如通过`AsyncTask`, `Thread`, 或者使用更现代的选择如Kotlin协程),确保不会阻塞主线程; ```kotlin CoroutineScope(Dispatchers.IO).launch { // 耗时操作... } ``` - 对于需要频繁更新UI的内容,考虑采用分页加载策略减少一次性展示的数据量; - 使用高效算法代替低效实现以加快处理速度; - 定期监控性能指标并通过工具链诊断瓶颈所在位置以便针对性改进; - 利用Logcat记录详细的调试信息帮助定位实际发生的错误点,并据此调整业务流程使之更加流畅稳定[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值