本文档介绍了如何识别 ANR 堆栈转储中的无响应线程。无响应的线程因 ANR 类型而异,如下表所示。
ANR 类型 | 无响应的线程 |
---|---|
输入调度 | 主线程 |
输入调度未聚焦于窗口 | 主线程。此类 ANR 通常不是由阻塞的线程导致的。 |
广播接收器(同步) | 正在运行 onReceive() 的线程。除非使用 Context.registerReceiver 指定了非主线程上的自定义处理程序,否则为主线程。 |
广播接收器(异步) | 检查代码,看看在调用 goAsync 后,哪个线程或线程池负责处理广播。 |
执行服务超时 | 主线程 |
前台服务启动 | 主线程 |
content provider 没有响应 | 是以下任一情况:
|
对 onStartJob 或 onStopJob 没有响应 |
主线程 |
有时,线程会由于其他线程或进程中的根本原因而无响应。线程可能会因等待以下事件而无响应:
- 其他线程持有的锁。
- 对其他进程的 binder 调用速度缓慢。
线程无响应的常见原因
以下是导致线程无响应的常见原因。
binder 调用速度缓慢
虽然大多数 binder 调用速度都很快,但长尾 binder 调用可能会非常慢。如果设备已加载或 binder 回复线程速度缓慢(例如出现锁争用、传入的 binder 调用很多或硬件抽象层 [HAL]超时等情况),更可能会发生这种情况。
您可以通过尽可能将同步 binder 调用移至后台线程中来解决此问题。如果调用必须在主线程中进行,请找出调用速度缓慢的原因。最好的方法是查看 Perfetto 跟踪记录。
在堆栈中查找 BinderProxy.transactNative
或 Binderproxy.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)
)渲染许多界面元素。
被其他组件堵塞
如果另一个组件(如广播接收器)堵塞主线程超过五秒,则可能会导致输入调度 ANR 和严重卡顿。
避免在应用组件的主线程上执行任何繁重工作。尽可能在其他线程上运行广播接收器。