查找无响应线程

本文档介绍了如何在 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)
[...]

昂贵的帧

在一帧中渲染过多内容会导致主线程在渲染期间无响应,例如以下情况

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

被其他组件阻塞

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

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