诊断和修复 ANR

如果 Android 应用的界面线程处于阻塞状态的时间过长,系统会发送“应用无响应”(ANR) 错误。本页介绍了不同类型的 ANR、如何进行诊断并给出建议的解决方法。列出的所有默认超时时间范围均适用于 AOSP 和 Pixel 设备;这些时间可能因原始设备制造商 (OEM) 而异。

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

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

  • 系统服务器出现的暂时性问题通常会导致快速 binder 调用速度变慢。
  • 系统服务器问题和设备负载过高会导致系统无法调度应用线程。

如果可以,不妨使用 Perfetto 轨迹文件来区分系统问题和应用问题:

  • 如需了解应用的主线程是否已调度,请查看 Perfetto 中的线程状态跟踪信息,以了解应用是正在运行还是可运行。
  • 查看 system_server 线程中是否存在锁争用等问题。
  • 如果 binder 调用速度缓慢,请查看回复线程(如果存在),以了解速度缓慢的原因。

输入调度超时

当应用的主线程未及时响应输入事件(例如滑动或按键操作)时,会发生输入调度 ANR。由于在发生输入调度超时应用在前台运行,因此用户几乎总是可以看到这类超时,实施缓解措施就非常重要。

默认超时期限:5 秒。

输入调度 ANR 通常是由主线程上的问题导致的。如果主线程处于阻塞状态,正在等待获取锁,则也可能会涉及持有锁的线程。

为避免出现输入调度 ANR,请遵循以下最佳实践:

  • 请勿在主线程上执行阻塞操作或长时间运行的操作。考虑使用 StrictMode 捕获主线程上的意外 activity。
  • 尽量减少在主线程和其他线程之间发生任何锁争用。
  • 尽量减少在主线程中执行非界面工作;例如,在处理广播或运行服务时执行的工作。

常见原因

以下是导致输入调度 ANR 的一些常见原因和建议的解决方法。

原因 出现的情况 建议的解决方法
binder 调用速度缓慢 主线程进行同步 binder 长调用。 将该调用移出主线程或尝试优化该调用(如果您拥有该 API)。
连续多次进行 binder 调用 主线程连续多次进行同步 binder 调用。 请勿在紧密循环中执行 binder 调用。
阻塞 I/O 主线程进行阻塞 I/O 调用,例如数据库或网络访问。 将所有阻塞 IO 移出主线程。
锁争用 主线程处于阻塞状态,正在等待获取锁。 减少主线程和其他线程之间的锁争用。 优化其他线程中运行缓慢的代码。
耗用大量资源的帧 单个帧中的渲染工作量太大,导致严重卡顿。 减少帧渲染工作。请勿使用 n2 算法。使用高效的组件实现滚动或分页等操作,例如使用 Jetpack Paging 库
被其他组件阻塞 另一个组件(如广播接收器)正在运行并阻塞主线程。 尽可能将非界面工作移出主线程。在其他线程上运行广播接收器。
GPU 挂起 GPU 挂起是系统或硬件问题,会导致渲染被阻塞,进而导致输入调度 ANR。 遗憾的是,通常无法在应用端解决这些问题。如有可能,请与硬件团队联系来排查问题。

如何调试

若要开始调试,请先查看 Google Play 管理中心Firebase Crashlytics 中的 ANR 集群签名。此集群通常包含最可能导致 ANR 的帧。

以下流程图展示了如何确定输入超时调度 ANR 的原因。

图 1. 如何调试输入调度 ANR。

Play Vitals 可以检测并帮助调试其中部分常见的 ANR 原因。例如,如果 Vitals 检测到由于锁争用而发生了 ANR,则可以在 ANR 数据分析部分汇总问题和建议的解决方法。

图 2. Play Vitals 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。对于同步接收器或不调用 goAync() 的接收器,超时意味着 onReceive() 没有及时完成。对于异步接收器或调用 goAsync() 的接收器,超时意味着没有及时调用 PendingResult.finish()

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

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

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

  • 确保应用能够快速启动,因为如果应用开始处理广播,应用启动时间就会被计入 ANR 超时。
  • 如果使用了 goAsync(),请确保快速调用 PendingResult.finish()。这受与同步广播接收器相同的 ANR 超时约束。
  • 如果使用 goAsync(),请确保不与其他长时间运行的操作或阻塞操作共享工作器线程。
  • 请考虑使用 registerReceiver() 在非主线程中运行广播接收器,以避免阻塞在主线程中运行的界面代码。

超时期限

广播接收超时期限取决于平台版本和是否设置了前台 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 管理中心会在 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)

There are several approaches to fix the issue:

  • Find out why getDataSync is slow and optimize.
  • Don't run getDataSync on all four BG threads.
  • More generally, ensure that the BG thread pool isn't saturated with long-running operations.
  • Use a dedicated thread pool for goAsync worker tasks.
  • Use an unbounded thread pool instead of the bounded BG thread pool

Example: slow app startup

A slow app startup can cause several types of ANRs, especially broadcast receiver and execute service ANRs. The cause of an ANR is likely slow app startup if you see ActivityThread.handleBindApplication in the main thread stacks.

Execute service timeout

An execute service ANR happens when the app's main thread doesn't start a service in time. Specifically, a service doesn't finish executing onCreate() and onStartCommand() or onBind() within the timeout period.

Default timeout period: 20 seconds for foreground service; 200 seconds for background service. The ANR timeout period includes the app cold start, if necessary, and calls to onCreate(), onBind(), or onStartCommand().

To avoid execute service ANRs, follow these general best practices:

  • Make sure that app startup is fast, since it's counted in the ANR timeout if the app is started to run the service component.
  • Make sure that the service's onCreate(), onStartCommand(), and onBind() methods are fast.
  • Avoid running any slow or blocking operations on the main thread from other components; these operations can prevent a service from starting quickly.

Common causes

The following table lists common causes of execute service ANRs and suggested fixes.

Cause What Suggested fix
Slow app startup The app takes too long to perform a cold start. Optimize slow app start.
Slow onCreate(), onStartCommand(), or onBind() The service component's onCreate(), onStartCommand(), or onBind() method takes too long to execute on the main thread. Optimize slow code. Move slow operations off the critical path where possible.
Not scheduled (main thread blocked before onStart()) The app's main thread is blocked by another component before the service can be started. Move other component's work off the main thread. Optimize other component's blocking code.

How to debug

From the cluster signature and ANR report in Google Play Console or Firebase Crashlytics, you can often determine the cause of the ANR based on what the main thread is doing.

The following flow chart describes how to debug an execute service ANR.

Figure 6. How to debug an execute service ANR.

If you've determined that the execute service ANR is actionable, follow these steps to help resolve the issue:

  1. Find the service component class in the ANR signature. In Google Play Console, the service component class is shown in the ANR signature. In the following example ANR details, it's com.example.app/MyService.

    com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly
    Executing service com.example.app/com.example.app.MyService
    
  2. Determine whether the slow or block operation is part of app startup, the service component, or elsewhere by checking for the following important function call(s) in the main threads.

    Function call(s) in main thread stacks What it means
    android.app.ActivityThread.handleBindApplication App was starting up, so the ANR was caused by slow app start.

    <ServiceClass>.onCreate()

    [...]

    android.app.ActivityThread.handleCreateService

    Service was being created, so the ANR was likely caused by slow onCreate() code.

    <ServiceClass>.onBind()

    [...]

    android.app.ActivityThread.handleBindService

    Service was being bound, so the ANR was likely caused by slow onBind() code.

    <ServiceClass>.onStartCommand()

    [...]

    android.app.ActivityThread.handleServiceArgs

    Service was being started, so the ANR was likely caused by slow onStartCommand() code.

    For example, if the onStartCommand() method in the MyService class is slow, the main threads will look like this:

    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 通常发生的位置,请检查其余的主线程堆栈,找出运行缓慢的操作并对其进行优化或将其从关键路径中移除。

  4. 如需详细了解服务,请参阅以下页面:

    content provider 没有响应

    如果远程 content provider 响应查询的时间超出超时期限,并被终止,就会发生 content provider ANR。

    默认超时期限:由 content provider 使用 ContentProviderClient.setDetectNotResponding 指定。ANR 超时期限包含远程 content provider 查询运行的总时间,其中包括对尚未运行的远程应用进行冷启动的时间。

    为避免出现 content provider ANR,请遵循以下最佳实践:

    • 确保应用快速启动,因为如果应用开始运行 content provider,则应用启动时间会被计入 ANR 超时。
    • 确保 content provider 查询可以快速执行。
    • 不要执行大量并发阻塞 binder 调用,这些调用可能会阻塞应用的所有 binder 线程。

    常见原因

    下表列出了导致 content provider ANR 的常见原因,以及建议的解决方法。

    原因 出现的情况 信号 建议的解决方法
    content provider 查询速度缓慢 content provider 的执行时间太长或被阻塞。 android.content.ContentProvider$Transport.query 帧位于 binder 线程中。 优化 content provider 查询。找出阻塞 binder 线程的因素。
    应用启动缓慢 content provider 的应用启动时间过长。 ActivityThread.handleBindApplication 帧位于主线程中。 优化应用启动。
    binder 线程耗尽 - 所有 binder 线程都处于忙碌状态 所有 binder 线程都忙于处理其他同步请求,因此 content provider binder 调用无法运行。 应用无法启动,所有 binder 线程都处于忙碌状态,并且 content provider 未运行。 减少 binder 线程的负载。也就是说,减少同步传出 binder 调用的数量,或减少处理传入调用时的工作量。

    如何调试

    如需使用 Google Play 管理中心或 Firebase Crashlytics 中的集群签名和 ANR 报告调试 content provider ANR,请查看主线程和 binder 线程正在执行的操作。

    以下流程图介绍了如何调试 content provider ANR:

    图 7. 如何调试 content provider ANR。

    以下代码段展示了 binder 线程因 content provider 查询速度缓慢而被阻塞时的情况。在这种情况下,打开数据库时,content provider 查询正在等待锁。

    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,这通常表示疑似无响应的线程实际上处于空闲状态,正在等待 Looper 消息。在 Google Play 管理中心内,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 触发之前完成广播处理而在应用进程中保留计时器可能无法正常工作。