查找无响应线程

本文档介绍了如何识别 ANR 堆栈转储中无响应的线程。无响应的线程因 ANR 类型而异,如下表所示。

ANR 类型 无响应线程
输入分派 主线程
输入分派无焦点窗口 主线程。此类 ANR 通常不是由阻塞线程引起的。
广播接收器(同步) 运行 onReceive() 的线程。这是主线程,除非使用 Context.registerReceiver 指定了非主线程上的自定义处理程序。
广播接收器(异步) 检查代码,查看调用 goAsync 后,哪个线程或线程池负责处理广播。
正在执行的服务超时 主线程
前台服务启动 主线程
内容提供程序无响应 如果出现以下情况,则为
  • 如果 ANR 是由内容提供程序查询缓慢引起的,则是 Binder 线程。
  • 如果 ANR 是由应用启动时间过长引起的,则是主线程。
onStartJobonStopJob 无响应 主线程

有时线程无响应是由于不同线程或进程中的根本原因。线程可能因等待以下内容而无响应:

  • 由其他线程持有的锁。
  • 对不同进程的慢速 Binder 调用。

无响应线程的常见原因

以下是无响应线程的常见原因。

慢速 Binder 调用

虽然大多数 Binder 调用都很快,但长尾效应可能会非常慢。如果设备负载过重或 Binder 回复线程速度慢(例如由于锁竞争、大量传入的 Binder 调用或 硬件抽象层 (HAL) 超时),则更有可能发生这种情况。

您可以通过尽可能将同步 Binder 调用移到后台线程来解决此问题。如果调用必须发生在主线程上,请找出调用缓慢的原因。执行此操作的最佳方法是使用 Perfetto 跟踪。

在堆栈中查找 BinderProxy.transactNativeBinderproxy.transact。这表示正在进行 Binder 调用。在这两行之后,您可以看到所调用的 Binder API。在以下示例中,调用的是 IAccessibilityManager.addClient

main tid=123

...
android.os.BinderProxy.transactNative (Native method)
android.os.BinderProxy.transact (BinderProxy.java:568)
android.view.accessibility.IAccessibilityManager$Stub$Proxy.addClient (IAccessibilityManager.java:599)
...

许多连续的 Binder 调用

在紧密循环中执行许多连续的 Binder 调用可能会长时间阻塞线程。

阻塞 I/O

切勿在主线程上执行阻塞 I/O。这是一种反模式。

锁竞争

如果线程在获取锁时被阻塞,则可能导致 ANR。

以下示例显示主线程在尝试获取锁时被阻塞

main (tid=1) Blocked

Waiting for com.example.android.apps.foo.BarCache (0x07d657b7) held by
ptz-rcs-28-EDITOR_REMOTE_VIDEO_DOWNLOAD
[...]
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:5412)
[...]

阻塞线程正在发出 HTTP 请求以下载视频

ptz-rcs-28-EDITOR_REMOTE_VIDEO_DOWNLOAD (tid=110) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:715)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1047)
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:230)
at com.example.android.apps.foo.HttpRequest.execute(HttpRequest:136)
at com.example.android.apps.foo$Task$VideoLoadTask.downloadVideoToFile(RequestExecutor:711)
[...]

开销大的帧

在单个帧中渲染过多内容可能会导致主线程在帧的持续时间内无响应,例如:

  • 渲染许多不必要的屏幕外项。
  • 在渲染许多 UI 元素时使用低效算法,例如 O(n^2)

被其他组件阻塞

如果其他组件(例如广播接收器)阻塞主线程超过五秒,则可能导致输入分派 ANR 和严重的卡顿。

避免在应用组件的主线程上执行任何繁重工作。尽可能在不同的线程上运行广播接收器。