诊断和修复 ANR

当 Android 应用的 UI 线程被阻塞过长时间时,系统会发送“应用程序无响应”(ANR)错误。此页面描述了不同类型的 ANR、如何诊断它们以及修复它们的建议。列出的所有默认超时时间范围均适用于 AOSP 和 Pixel 设备;这些时间可能因 OEM 而异。

请记住,在确定 ANR 的原因时,区分系统问题和应用问题很有帮助。

当系统处于不良状态时,以下问题可能会导致 ANR

  • 系统服务器中的瞬态问题通常会导致 Binder 调用速度变慢。
  • 系统服务器问题和高设备负载会导致应用线程无法调度。

如果可以使用,区分系统问题和应用问题的一个好方法是使用 Perfetto 跟踪

  • 查看 Perfetto 中的线程状态跟踪,了解应用的主线程是否已调度,以查看它是否正在运行或可运行。
  • 查看 system_server 线程是否存在诸如锁争用之类的问题。
  • 对于缓慢的 Binder 调用,请查看答复线程(如果存在)以了解其缓慢的原因。

输入分发超时

当应用的主线程未及时响应输入事件(如轻扫或按键)时,就会发生输入分发 ANR。由于应用在发生输入分发超时时处于前台,因此它们几乎总是对用户可见,并且非常重要,需要缓解。

默认超时时间段:5 秒。

输入分发 ANR 通常是由主线程上的问题引起的。如果主线程被阻塞以等待获取锁,则持有者线程也可能参与其中。

为避免输入分发 ANR,请遵循以下最佳实践

  • 不要在主线程上执行阻塞或长时间运行的操作。考虑使用 StrictMode 来捕获主线程上的意外活动。
  • 最大程度减少主线程和其他线程之间的锁争用。
  • 最大程度减少主线程上的非 UI 工作,例如处理广播或运行服务时。

常见原因

以下是输入分发 ANR 的一些常见原因和建议的修复方法。

原因 发生了什么 建议的修复方法
Binder 调用缓慢 主线程进行长时间的同步 Binder 调用。 将调用移出主线程,或者如果您拥有 API,则尝试优化调用。
连续多次 Binder 调用 主线程进行连续多次的同步 Binder 调用。 不要在紧密循环中执行 Binder 调用。
阻塞式 I/O 主线程进行阻塞式 I/O 调用,例如数据库或网络访问。 将所有阻塞式 IO 移出主线程。
锁争用 主线程被阻塞,等待获取锁。 减少主线程和其他线程之间的锁争用。优化其他线程中的缓慢代码。
帧渲染开销大 在一帧中渲染过多内容,导致严重卡顿。 减少帧渲染工作量。不要使用 n2 算法。对于滚动或分页等操作,使用高效的组件——例如,Jetpack 的 分页库
被其他组件阻塞 其他组件(例如广播接收器)正在运行并阻塞主线程。 尽可能将非 UI 工作移出主线程。在其他线程上运行广播接收器。
GPU 冻结 GPU 冻结是一个系统或硬件问题,会导致渲染被阻塞,从而导致输入分发 ANR。 不幸的是,通常在应用端没有修复方法。如果可能,请联系硬件团队进行故障排除。

如何调试

从查看 Google Play ConsoleFirebase Crashlytics 中的 ANR 集群签名开始调试。该集群通常包含疑似导致 ANR 的顶级帧。

以下流程图显示了如何确定输入超时分发 ANR 的原因。

图 1. 如何调试输入分发 ANR。

Play 诊断可以检测并帮助调试这些常见 ANR 原因中的一些。例如,如果诊断检测到 ANR 是由于锁争用导致的,它可以在 ANR 的“见解”部分总结问题并推荐修复方法。

图 2. Play 诊断 ANR 检测。

没有焦点窗口

虽然像触摸这样的事件会根据命中测试直接发送到相关的窗口,但像按键这样的事件需要一个目标。此目标称为“焦点窗口”。每个显示器只有一个焦点窗口,它通常是用户当前正在交互的窗口。如果找不到焦点窗口,输入将引发“无焦点窗口 ANR”。无焦点窗口 ANR 是一种输入分发 ANR。

默认超时时间段:5 秒。

常见原因

无焦点窗口 ANR 通常由以下任一问题导致

  • 应用正在执行太多工作,并且绘制第一帧的速度太慢。
  • 主窗口不可聚焦。如果一个窗口使用 FLAG_NOT_FOCUSABLE 标记,则用户无法向其发送键或按钮事件。

Kotlin

override fun onCreate(savedInstanceState: Bundle) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)
  window.addFlags(WindowManager.LayoutParams.FLAG_FLAG_NOT_FOCUSABLE)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}

广播接收器超时

当广播接收器未及时处理广播时,会发生广播接收器 ANR。对于同步接收器或未调用 goAsync() 的接收器,超时表示 onReceive() 未及时完成。对于异步接收器或调用 goAsync() 的接收器,超时表示 PendingResult.finish() 未及时调用。

广播接收器 ANR 通常发生在以下线程中

  • 主线程(如果问题是应用启动缓慢)。
  • 运行广播接收器的线程(如果问题是 onReceive() 代码缓慢)。
  • 广播工作线程(如果问题是 goAsync() 广播代码缓慢)。

为避免广播接收器 ANR,请遵循以下最佳实践

  • 确保应用启动速度很快,因为如果启动应用是为了处理广播,则应用启动时间将计入 ANR 超时时间。
  • 如果使用了 goAsync(),请确保快速调用 PendingResult.finish()。这与同步广播接收器具有相同的 ANR 超时时间。
  • 如果使用了 goAsync(),请确保工作线程未与其他长时间运行或阻塞操作共享。
  • 考虑使用 registerReceiver() 在非主线程中运行广播接收器,以避免阻塞主线程中运行的 UI 代码。

超时时间段

广播接收超时时间段取决于是否设置了前台 Intent 标志以及平台版本。

Intent 类型 Android 13 及更低版本 Android 14 及更高版本

前台优先级 Intent

(已设置 FLAG_RECEIVER_FOREGROUND)

10 秒

10-20 秒,具体取决于进程是否 CPU 饥饿

后台优先级 Intent

(未设置 FLAG_RECEIVER_FOREGROUND)

60 秒

60-120 秒,具体取决于进程是否 CPU 饥饿

要判断是否设置了 FLAG_RECEIVER_FOREGROUND 标志,请在 ANR 主题中查找“flg=”并检查是否存在 0x10000000。如果设置了此位,则 Intent 已设置 FLAG_RECEIVER_FOREGROUND,因此超时时间更短。

广播超时时间短(10-20 秒)的 ANR 主题示例

Broadcast of Intent { act=android.inent.action.SCREEN_ON flg=0x50200010 }

广播超时时间长(60-120 秒)的 ANR 主题示例

Broadcast of Intent { act=android.intent.action.TIME_SET flg=0x25200010 }

广播时间如何测量

广播持续时间测量从 system_server 向应用分发广播时开始,并在应用完成处理广播时结束。如果应用进程尚未运行,它还需要在 ANR 超时时间段内进行冷启动。因此,应用启动缓慢 会导致广播接收器 ANR。

下图说明了广播接收器 ANR 时间线与某些应用进程保持一致。

图 3. 广播接收器 ANR 时间线。

当接收器完成处理广播时,ANR 超时测量结束:何时结束取决于它是同步接收器还是异步接收器。

  • 对于同步接收器,测量在 onReceive() 返回时停止。
  • 对于异步接收器,测量在调用 PendingResult.finish() 时停止。
图 4. 同步和异步接收器的 ANR 超时测量端点。

常见原因

以下是广播接收器 ANR 的一些常见原因和建议的修复方法。

原因 适用于 发生了什么 建议的修复方法
应用启动缓慢 所有接收器 应用花费太长时间进行冷启动。 优化应用启动缓慢的问题。
onReceive() 未调度 所有接收器 广播接收器线程忙于执行其他工作,无法启动 onReceive() 方法。 不要在接收器线程上执行长时间运行的任务(或将接收器移至专用线程)。
onReceive() 缓慢 所有接收器,但主要是同步接收器 onReceive() 方法已启动,但被阻塞或缓慢,因此未及时完成。 优化缓慢的接收器代码。
异步接收器任务未调度 goAsync() 接收器 onReceive() 方法尝试在阻塞的工作线程池上执行工作,因此工作从未启动。 优化缓慢或阻塞的调用,或对广播工作线程与其他长时间运行的任务使用不同的线程。
工作线程缓慢或阻塞 goAsync() 接收器 在处理广播时,工作线程池中的某个位置存在阻塞或缓慢的操作。因此,PendingResult.finish 未及时调用。 优化缓慢的 async 接收器代码。
忘记调用 PendingResult.finish goAsync() 接收器 代码路径中缺少对 finish() 的调用。 确保始终调用 finish()

如何调试

根据集群签名和 ANR 报告,您可以找到接收器运行的线程,然后找到缺少或运行缓慢的特定代码。

以下流程图显示了如何确定广播接收器 ANR 的原因。

图 5. 如何调试广播接收器 ANR。

查找接收器代码

Google Play Console 在 ANR 签名中显示接收器类和广播 Intent。查找以下内容

  • cmp=<receiver class>
  • act=<broadcast_intent>

以下是一个广播接收器 ANR 签名的示例

com.example.app.MyClass.myMethod
Broadcast of Intent { act=android.accounts.LOGIN_ACCOUNTS_CHANGED
cmp=com.example.app/com.example.app.MyAccountReceiver }

查找运行 onReceive() 方法的线程

如果您使用 Context.registerReceiver 指定自定义处理程序,则它是运行此处理程序的线程。否则,它是主线程。

示例:异步接收器任务未调度

本节将逐步介绍如何调试广播接收器 ANR 的示例。

假设 ANR 签名如下所示

com.example.app.MyClass.myMethod
Broadcast of Intent {
act=android.accounts.LOG_ACCOUNTS_CHANGED cmp=com.example.app/com.example.app.MyReceiver }

根据签名,广播 Intent 似乎是 android.accounts.LOG_ACCOUNTS_CHANGED,接收器类是 com.example.app.MyReceiver

从接收器代码中,您可以确定线程池“BG Thread [0,1,2,3]”执行处理此广播的主要工作。查看堆栈转储,您可以看到所有四个后台 (BG) 线程都具有相同的模式:它们运行阻塞调用 getDataSync。由于所有 BG 线程都很繁忙,因此广播无法及时处理,这导致了 ANR。

BG Thread #0 (tid=26) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture:563)
at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture:68)
at com.example.app.getDataSync(<MyClass>:152)

...

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.libraries.concurrent.AndroidExecutorsModule.lambda$withStrictMode$5(AndroidExecutorsModule:451)
at com.google.android.libraries.concurrent.AndroidExecutorsModule$$ExternalSyntheticLambda8.run(AndroidExecutorsModule:1)
at java.lang.Thread.run(Thread.java:1012)
at com.google.android.libraries.concurrent.ManagedPriorityThread.run(ManagedPriorityThread:34)

有几种方法可以解决此问题

  • 找出 getDataSync 缓慢的原因并进行优化。
  • 不要在所有四个 BG 线程上运行 getDataSync

  • 更一般地,确保 BG 线程池没有被长时间运行的操作所饱和。
  • goAsync 工作线程任务使用一个专用的线程池。
  • 使用无界线程池代替有界 BG 线程池。

示例:应用启动缓慢

应用启动缓慢会导致几种类型的 ANR,特别是广播接收器和执行服务 ANR。如果在主线程堆栈中看到 ActivityThread.handleBindApplication,则 ANR 的原因可能是应用启动缓慢。

执行服务超时

当应用的主线程没有及时启动服务时,就会发生执行服务 ANR。具体来说,服务在超时时间内没有完成执行 onCreate()onStartCommand()onBind()

默认超时时间:前台服务为 20 秒;后台服务为 200 秒。ANR 超时时间包括应用冷启动(如果需要)以及对 onCreate()onBind()onStartCommand() 的调用。

为了避免执行服务 ANR,请遵循以下一般最佳实践

  • 确保应用启动速度快,因为如果启动应用以运行服务组件,则会在 ANR 超时时间内计算启动时间。
  • 确保服务的 onCreate()onStartCommand()onBind() 方法执行速度快。
  • 避免从其他组件在主线程上运行任何缓慢或阻塞操作;这些操作可能会阻止服务快速启动。

常见原因

下表列出了执行服务 ANR 的常见原因和建议的修复方法。

原因 问题 建议的修复方法
应用启动缓慢 应用执行冷启动花费的时间过长。 优化应用启动缓慢的问题。
缓慢的 onCreate()onStartCommand()onBind() 服务组件的 onCreate()onStartCommand()onBind() 方法在主线程上执行花费的时间过长。 优化缓慢的代码。尽可能将缓慢的操作移出关键路径。
未调度(在 onStart() 之前主线程被阻塞) 在服务能够启动之前,应用的主线程被另一个组件阻塞。 将其他组件的工作移出主线程。优化其他组件的阻塞代码。

如何调试

从 Google Play Console 或 Firebase Crashlytics 中的集群签名和 ANR 报告中,您可以根据主线程正在执行的操作,通常确定 ANR 的原因。

以下流程图描述了如何调试执行服务 ANR。

图 6. 如何调试执行服务 ANR。

如果您已确定执行服务 ANR 可采取措施,请按照以下步骤帮助解决问题

  1. 在 ANR 签名中找到服务组件类。在 Google Play Console 中,服务组件类显示在 ANR 签名中。在以下 ANR 详细信息示例中,它是 com.example.app/MyService

    com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly
    Executing service com.example.app/com.example.app.MyService
    
  2. 通过检查主线程中的以下重要函数调用,确定缓慢或阻塞操作是应用启动、服务组件还是其他地方的一部分。

    主线程堆栈中的函数调用 含义
    android.app.ActivityThread.handleBindApplication 应用正在启动,因此 ANR 是由应用启动缓慢引起的。

    <ServiceClass>.onCreate()

    [...]

    android.app.ActivityThread.handleCreateService

    服务正在创建,因此 ANR 可能是由 onCreate() 代码缓慢引起的。

    <ServiceClass>.onBind()

    [...]

    android.app.ActivityThread.handleBindService

    服务正在绑定,因此 ANR 可能是由 onBind() 代码缓慢引起的。

    <ServiceClass>.onStartCommand()

    [...]

    android.app.ActivityThread.handleServiceArgs

    服务正在启动,因此 ANR 可能是由 onStartCommand() 代码缓慢引起的。

    例如,如果 MyService 类中的 onStartCommand() 方法缓慢,则主线程将如下所示

    at com.example.app.MyService.onStartCommand(FooService.java:25)
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4820)
    at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(unavailable:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2289)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8176)
    at java.lang.reflect.Method.invoke(Native method:0)
    

    如果您看不到任何重要函数调用,则还有其他几种可能性

    • 服务正在运行或关闭,这意味着堆栈获取过晚。在这种情况下,您可以将 ANR 忽略为误报。
    • 其他应用组件正在运行,例如广播接收器。在这种情况下,主线程可能在此组件中被阻塞,从而阻止服务启动。
  3. 如果您确实看到了关键函数调用并且可以大致确定 ANR 发生的位置,请检查主线程的其余堆栈以查找缓慢的操作并对其进行优化或将其移出关键路径。

有关服务的更多信息,请参阅以下页面

内容提供程序无响应

当远程内容提供程序花费的时间超过超时时间才能响应查询并被杀死时,就会发生内容提供程序 ANR。

默认超时时间:由内容提供程序使用 ContentProviderClient.setDetectNotResponding 指定。ANR 超时时间包括远程内容提供程序查询运行的总时间,包括如果远程应用尚未运行则冷启动远程应用。

为了避免内容提供程序 ANR,请遵循以下最佳实践

  • 确保应用启动速度快,因为如果启动应用以运行内容提供程序,则会在 ANR 超时时间内计算启动时间。
  • 确保内容提供程序查询速度快。
  • 不要执行大量可能阻塞所有应用绑定程序线程的并发阻塞绑定程序调用。

常见原因

下表列出了内容提供程序 ANR 的常见原因和建议的修复方法。

原因 发生了什么 信号 建议的修复方法
内容提供程序查询缓慢 内容提供程序执行花费的时间过长或被阻塞。 绑定程序线程中存在 android.content.ContentProvider$Transport.query 帧。 优化内容提供程序查询。找出是什么阻塞了绑定程序线程。
应用启动缓慢 内容提供程序的应用启动花费的时间过长。 主线程中存在 ActivityThread.handleBindApplication 帧。 优化应用启动。
绑定程序线程耗尽——所有绑定程序线程都处于繁忙状态 所有绑定程序线程都忙于处理其他同步请求,因此无法运行内容提供程序绑定程序调用。 应用未启动,所有绑定程序线程都处于繁忙状态,并且内容提供程序未运行。 减少绑定程序线程上的负载。也就是说,减少同步传出的绑定程序调用次数,或者在处理传入调用时减少工作量。

如何调试

要使用 Google Play Console 或 Firebase Crashlytics 中的集群签名和 ANR 报告调试内容提供程序 ANR,请查看主线程和绑定程序线程正在执行的操作。

以下流程图描述了如何调试内容提供程序 ANR

图 7. 如何调试内容提供程序 ANR。

以下代码片段显示了当绑定程序线程因内容提供程序查询缓慢而被阻塞时的样子。在这种情况下,内容提供程序查询在打开数据库时等待锁。

binder:11300_2 (tid=13) Blocked

Waiting for osm (0x01ab5df9) held by at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers:182)
at com.example.app.MyClass.blockingGetOpenDatabase(FooClass:171)
[...]
at com.example.app.MyContentProvider.query(MyContentProvider.java:915)
at android.content.ContentProvider$Transport.query(ContentProvider.java:292)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:107)
at android.os.Binder.execTransactInternal(Binder.java:1339)
at android.os.Binder.execTransact(Binder.java:1275)

以下代码片段显示了当主线程因应用启动缓慢而被阻塞时的样子。在这种情况下,应用启动缓慢是由于在 Dagger 初始化期间锁争用引起的。

main (tid=1) Blocked

[...]
at dagger.internal.DoubleCheck.get(DoubleCheck:51)
- locked 0x0e33cd2c (a qsn)at dagger.internal.SetFactory.get(SetFactory:126)
at com.myapp.Bar_Factory.get(Bar_Factory:38)
[...]
at com.example.app.MyApplication.onCreate(DocsApplication:203)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6991)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication(unavailable:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2235)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8170)
at java.lang.reflect.Method.invoke(Native method:0)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

作业响应缓慢

当应用响应 JobService.onStartJob()JobService.onStopJob() 花费的时间过长,或者使用 JobService.setNotification() 提供通知花费的时间过长时,就会发生作业响应缓慢 ANR。这表明应用的主线程被阻塞执行其他操作。

如果是 JobService.onStartJob()JobService.onStopJob() 出现问题,请检查主线程上发生了什么。如果是 JobService.setNotification() 出现问题,请确保尽快调用它。在提供通知之前不要执行大量工作。

神秘 ANR

有时不清楚 ANR 发生的原因,或者集群签名和 ANR 报告中没有足够的信息来调试它。在这些情况下,您仍然可以采取一些步骤来确定 ANR 是否可采取措施。

消息队列空闲或 nativePollOnce

如果在堆栈中看到 android.os.MessageQueue.nativePollOnce 帧,通常表示疑似无响应的线程实际上处于空闲状态并等待循环程序消息。在 Google Play Console 中,ANR 详细信息如下所示

Native method - android.os.MessageQueue.nativePollOnce
Executing service com.example.app/com.example.app.MyService

例如,如果主线程处于空闲状态,则堆栈如下所示

"main" tid=1 NativeMain threadIdle

#00  pc 0x00000000000d8b38  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
#01  pc 0x0000000000019d88  /system/lib64/libutils.so (android::Looper::pollInner(int)+184)
#02  pc 0x0000000000019c68  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112)
#03  pc 0x000000000011409c  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce (Native method)
at android.os.MessageQueue.next (MessageQueue.java:339)  at android.os.Looper.loop (Looper.java:208)
at android.app.ActivityThread.main (ActivityThread.java:8192)
at java.lang.reflect.Method.invoke (Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:626)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1015)

疑似无响应的线程处于空闲状态的原因有很多

  • 堆栈转储延迟。在 ANR 触发和转储堆栈之间的一小段时间内,线程恢复了。Android 13 上 Pixel 的延迟约为 100 毫秒,但可能超过 1 秒。Android 14 上 Pixel 的延迟通常低于 10 毫秒。
  • 线程错误归属。用于构建 ANR 签名的线程不是导致 ANR 的实际无响应线程。在这种情况下,请尝试确定 ANR 是否属于以下类型
  • 系统范围内的错误。由于系统负载过重或系统服务器中的错误,进程未被调度。

无堆栈帧

一些 ANR 报告不包含 ANR 的堆栈,这意味着在生成 ANR 报告时堆栈转储失败。堆栈帧丢失可能有多个原因

  • 获取堆栈花费的时间过长并超时。
  • 在获取堆栈之前,进程已死亡或被杀死。
[...]

--- CriticalEventLog ---
capacity: 20
timestamp_ms: 1666030897753
window_ms: 300000

libdebuggerd_client: failed to read status response from tombstoned: timeout reached?

----- Waiting Channels: pid 7068 at 2022-10-18 02:21:37.<US_SOCIAL_SECURITY_NUMBER>+0800 -----

[...]

无法根据集群签名或 ANR 报告对没有堆栈帧的 ANR 采取措施。要进行调试,请查看应用的其他集群,因为如果错误足够大,通常会有其自己的集群,其中存在堆栈帧。另一种选择是查看 Perfetto 跟踪

已知问题

出于在 ANR 触发之前完成广播处理的目的,在应用进程中保留计时器可能无法正常工作,因为系统以异步方式监视 ANR。