如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框,如图 1 所示。ANR 对话框会为用户提供强制退出应用的选项。
ANR 是一个问题,因为负责更新界面的应用主线程无法处理用户输入事件或绘制操作,这会引起用户的不满。如需详细了解应用的主线程,请参阅进程和线程概览。
出现以下任何情况时,系统都会针对您的应用触发 ANR:
- 输入调度超时:如果您的应用在 5 秒内未响应输入事件(例如按键或屏幕触摸)。
- 执行服务:如果应用声明的服务无法在几秒内完成
Service.onCreate()和Service.onStartCommand()/Service.onBind()执行。 **Service.startForeground()未调用**: 如果您的应用使用Context.startForegroundService()在前台启动新服务,但该服务在 5 秒内未调用startForeground()。- intent 广播:如果
BroadcastReceiver在设定的一段时间内没有执行完毕。如果应用有任何前台 activity,此超时期限为 5 秒。 **JobScheduler互动**:如果JobService未在几秒钟内从JobService.onStartJob()或JobService.onStopJob()返回,或者如果用户发起的作业启动,而您的应用在调用JobService.onStartJob()后的几秒内未调用JobService.setNotification()。对于以 Android 13 及更低版本为目标平台的应用,ANR 会保持静默状态,且不会报告给应用。对于以 Android 14 及更高版本为目标平台的应用,ANR 会保持活动状态,并会报告给应用。
如果您的应用遇到 ANR 错误,您可以使用本文中的指南来诊断和解决问题。
解决问题
找出问题后,您可以参考本节中的提示解决常见问题。
主线程上执行速度缓慢的代码
在您的代码中找出应用的主线程忙碌时间超过 5 秒的位置。在您的应用中查找可疑用例并尝试重现 ANR。
例如,图 2 显示的 TraceView 时间轴中,主线程的忙碌时间超过了 5 秒。

图 2. TraceView 时间轴中显示了一个忙碌的主线程
图 2 显示了大多数违规代码发生在 onClick(View) 处理程序中,如以下代码示例所示:
Kotlin
override fun onClick(v: View) {
// This task runs on the main thread.
BubbleSort.sort(data)
}
Java
@Override
public void onClick(View view) {
// This task runs on the main thread.
BubbleSort.sort(data);
}
在这种情况下,您应该将主线程中运行的工作移至工作器线程。Android 框架中包含有助于将任务移至工作器线程的类。如需了解详情,请参阅工作器线程。
主线程上的 I/O
在主线程上执行 I/O 操作是导致主线程上操作速度缓慢的常见原因,主线程上操作速度缓慢会导致 ANR。在 Compose 中,开发者在尝试派生初始状态时,经常会意外触发磁盘读取(例如 SharedPreferences 或数据库调用)。
在界面层之外执行长时间运行的 I/O 操作。在 ViewModel 中使用 withContext(Dispatchers.IO),或者最好在数据层上使用代码库。建议将所有 IO 操作移至工作线程,如上一部分所示。
IO 操作示例包括网络和存储操作。如需了解详情,请参阅执行网络操作和保存数据。
锁争用
在某些情况下,导致 ANR 的工作并不是直接在应用的主线程上执行。如果某工作器线程持有对某项资源的锁,而该资源是主线程完成其工作所必需的,这种情况下就可能会发生 ANR。
例如,图 3 显示的 TraceView 时间轴中,大部分工作是在工作器线程上执行的。

图 3. TraceView 时间轴中显示了工作器线程上正在执行的工作
但如果用户仍然会遇到 ANR,您应该在 Android Device Monitor 中查看主线程的状态。通常情况下,如果主线程已准备好更新界面并且总体上响应速度较快,则处于 RUNNABLE 状态。
但如果主线程无法继续执行,则处于 BLOCKED 状态,并且无法响应事件。该状态在 Android Device Monitor 中会显示为“监控”或“等待”,如图 5 所示。

图 4. 处于“监控”状态的主线程
以下跟踪信息显示了因等待资源而处于阻塞状态的应用主线程:
...
AsyncTask #2" prio=5 tid=18 Runnable
| group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
| sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
| state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
| stack=0x94a7e000-0x94a80000 stackSize=1038KB
| held mutexes= "mutator lock"(shared held)
at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
- locked <0x083105ee> (a java.lang.Boolean)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
...
查看跟踪信息有助于找到阻塞主线程的代码。以下代码持有的锁导致了前面的跟踪信息中的主线程阻塞:
Kotlin
override fun onClick(v: View) {
// The worker thread holds a lock on lockedResource
LockTask().execute(data)
synchronized(lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
class LockTask : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? =
synchronized(lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0])
}
}
Java
@Override
public void onClick(View v) {
// The worker thread holds a lock on lockedResource
new LockTask().execute(data);
synchronized (lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
public class LockTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0]);
}
}
}
另一个示例是应用的主线程在等待来自某工作器线程的结果,如以下代码所示。请注意,不建议在 Kotlin 中使用 wait() 和 notify(),Kotlin 有自己的并发操作处理机制。使用 Kotlin 时,应尽量使用 Kotlin 专用机制。
Kotlin
fun onClick(v: View) {
val lock = java.lang.Object()
val waitTask = WaitTask(lock)
synchronized(lock) {
try {
waitTask.execute(data)
// Wait for this worker thread's notification
lock.wait()
} catch (e: InterruptedException) {
}
}
}
internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? {
synchronized(lock) {
BubbleSort.sort(params[0])
// Finished, notify the main thread
lock.notify()
}
}
}
Java
public void onClick(View v) {
WaitTask waitTask = new WaitTask();
synchronized (waitTask) {
try {
waitTask.execute(data);
// Wait for this worker thread’s notification
waitTask.wait();
} catch (InterruptedException e) {}
}
}
class WaitTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (this) {
BubbleSort.sort(params[0]);
// Finished, notify the main thread
notify();
}
}
}
还有一些其他情况会阻塞主线程,包括使用 Lock、Semaphore 以及资源池(如数据库连接池)或其他互斥(互斥锁)机制的线程。
您应总体上评估应用对资源持有的锁,但如果您想避免 ANR,则应查看对主线程所需资源持有的锁。
请确保将持有锁的时间降到最少,或者最好从一开始就评估应用是否需要持有锁。如果您使用锁来确定何时根据工作器线程的处理情况来更新界面,请使用 onProgressUpdate() 和 onPostExecute() 之类的机制在工作器线程和主线程之间进行通信。
执行速度缓慢的广播接收器
应用可以通过广播接收器响应广播消息,例如启用或停用飞行模式或更改连接状态。如果应用处理广播消息的用时过长,就会发生 ANR。
以下情况下会发生 ANR:
- 广播接收器用了相当长的时间仍未执行完
onReceive方法。 - 广播接收器对
PendingResult对象调用了goAsync,但未能调用finish。
您的应用应只在 BroadcastReceiver 的 onReceive 方法中执行短操作。不过,如果您的应用因广播消息而需要进行更复杂的处理,您应将该任务推迟到 ViewModel(利用 Kotlin 协程、作用域和调度器的强大功能),前提是该任务预计最多需要几秒钟的时间;或者推迟到任何类型的状态容器 (state holder),或者推迟到 WorkManager,前提是该任务预计需要几秒钟以上的时间。
您可以使用 TraceView 等工具来识别广播接收器是否在应用的主线程上执行长时间运行的操作。例如,图 6 显示了某广播接收器的时间轴,该接收器在主线程上处理消息用时大约 100 秒。

图 5. Traceview 时间轴中显示了主线程上的 BroadcastReceiver 工作
如果对 BroadcastReceiver 的 onReceive() 方法执行长时间运行的操作,可能会导致此行为,如以下示例所示:
Kotlin
override fun onReceive(context: Context, intent: Intent) {
// This is a long-running operation
BubbleSort.sort(data)
}
Java
@Override
public void onReceive(Context context, Intent intent) {
// This is a long-running operation
BubbleSort.sort(data);
}
在此类情况下,建议将长时间运行的操作移至 IntentService,因为它使用工作器线程来执行其工作。以下代码显示了如何使用 IntentService 处理长时间运行的操作:
Kotlin
override fun onReceive(context: Context, intent: Intent) {
Intent(context, MyIntentService::class.java).also { intentService ->
// The task now runs on a worker thread.
context.startService(intentService)
}
}
class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
BubbleSort.sort(data)
}
}
Java
@Override
public void onReceive(Context context, Intent intent) {
// The task now runs on a worker thread.
Intent intentService = new Intent(context, MyIntentService.class);
context.startService(intentService);
}
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(@Nullable Intent intent) {
BubbleSort.sort(data);
}
}
由于使用 IntentService,长时间运行的操作将在工作器线程(而非主线程)上执行。图 7 在 TraceView 时间轴中显示了推迟到工作器线程的工作。

图 6. TraceView 时间轴中显示了在工作器线程上处理的广播消息
您的广播接收器可以使用 goAsync() 向系统表明它需要更多时间来处理消息。不过,您应该对 PendingResult 对象调用 finish()。以下示例展示了如何调用 finish() 让系统回收广播接收器并避免 ANR:
Kotlin
val pendingResult = goAsync()
object : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? {
// This is a long-running operation
BubbleSort.sort(params[0])
pendingResult.finish()
return 0L
}
}.execute(data)
Java
final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
// This is a long-running operation
BubbleSort.sort(params[0]);
pendingResult.finish();
}
}.execute(data);
不过,如果广播在后台运行,将代码从执行速度缓慢的广播接收器移至另一个线程并使用 goAsync() 将无法解决 ANR 问题。ANR 超时仍然适用。