ANR

如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框,如图 1 所示。ANR 对话框会为用户提供强制退出应用的选项。

图 1. 向用户显示的 ANR 对话框

图 1. 向用户显示的 ANR 对话框

ANR 是一个问题,因为负责更新界面的应用主线程无法处理用户输入事件或绘制操作,这会引起用户的不满。如需详细了解应用的主线程,请参阅进程和线程

出现以下任何情况时,系统都会针对您的应用触发 ANR:

  • 当您的 activity 位于前台时,您的应用在 5 秒钟内未响应输入事件或 BroadcastReceiver(如按键或屏幕轻触事件)。
  • 虽然前台没有 activity,但您的 BroadcastReceiver 用了相当长的时间仍未执行完毕。

如果您的应用遇到 ANR 错误,您可以使用本文中的指南来诊断和解决问题。

检测和诊断问题

Android 提供了一些方式,以便在您的应用有问题时让您知道,并帮助您进行诊断。如果您已发布应用,Android Vitals 可以在您的应用出现问题时提醒您,并且有一些诊断工具可帮助您发现问题。

Android Vitals

当您的应用出现 ANR 错误的次数过多时,Android Vitals 可通过 Play 管理中心提醒您,因此有助于改进应用性能。当应用出现以下情况时,Android Vitals 会认为 ANR 次数超出了正常范围:

  • 至少有 0.47% 的每日工作时段出现了至少一次 ANR。
  • 至少有 0.24% 的每日工作时段出现了至少两次 ANR。

每日工作时段是指应用在一天内被使用的时间。

如需了解 Google Play 如何收集 Android Vitals 数据,请参阅 Play 管理中心文档。

诊断 ANR

诊断 ANR 时需要考虑以下几种常见模式:

  1. 应用在主线程上非常缓慢地执行涉及 I/O 的操作。
  2. 应用在主线程上进行长时间的计算。
  3. 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。
  4. 主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。
  5. 主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。如需了解详情,请参阅维基百科上的死锁

以下方法可帮助您找出是以上哪种原因造成了 ANR。

严格模式

使用 StrictMode 有助于您在开发应用时发现主线程上的意外 I/O 操作。您可以在应用级别或 activity 级别使用 StrictMode

启用后台 ANR 对话框

只有在设备的开发者选项中启用了显示所有 ANR 时,Android 才会针对花费过长时间处理广播消息的应用显示 ANR 对话框。因此,系统并不会始终向用户显示后台 ANR 对话框,但应用仍可能会遇到性能问题。

TraceView

您可以使用 TraceView 在查看用例时获取正在运行的应用的跟踪信息,并找出主线程繁忙的位置。如需了解如何使用 TraceView,请参阅使用 TraceView 和 dmtracedump 分析性能

拉取跟踪信息文件

Android 会在遇到 ANR 时存储跟踪信息。在较低的操作系统版本中,设备上只有一个 /data/anr/traces.txt 文件。在较新的操作系统版本中,有多个 /data/anr/anr_* 文件。您可以使用 Android 调试桥 (adb) 作为根,从设备或模拟器中获取 ANR 跟踪信息:

adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>

您可以使用设备上的“生成错误报告”开发者选项或开发机器上的 adb bugreport 命令,从实体设备获取 bug 报告。如需了解详情,请参阅获取和阅读 bug 报告

解决问题

找出问题后,您可以参考本节中的提示解决常见问题。

主线程上执行速度缓慢的代码

在您的代码中找出应用的主线程忙碌时间超过 5 秒的位置。在您的应用中查找可疑用例并尝试重现 ANR。

例如,图 2 显示的 TraceView 时间轴中,主线程的忙碌时间超过了 5 秒。

图 2. TraceView 时间轴中显示了一个忙碌的主线程

图 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 框架中包含有助于将任务移至工作器线程的类。如需了解详情,请参阅工作器线程

主线程上的 IO

在主线程上执行 IO 操作是导致主线程上操作速度缓慢的常见原因,主线程上操作速度缓慢会导致 ANR。建议将所有 IO 操作移至工作器线程,如上一部分所示。

IO 操作示例包括网络和存储操作。如需了解详情,请参阅执行网络操作保存数据

锁争用

在某些情况下,导致 ANR 的工作并不是直接在应用的主线程上执行。如果某工作器线程持有对某项资源的锁,而该资源是主线程完成其工作所必需的,这种情况下就可能会发生 ANR。

例如,图 4 显示的 TraceView 时间轴中,大部分工作是在工作器线程上执行的。

图 4. TraceView 时间轴中显示了工作器线程上正在执行的工作

图 4. TraceView 时间轴中显示了工作器线程上正在执行的工作

但如果用户仍然会遇到 ANR,您应该在 Android Device Monitor 中查看主线程的状态。通常情况下,如果主线程已准备好更新界面并且总体上响应速度较快,则处于 RUNNABLE 状态。

但如果主线程无法继续执行,则处于 BLOCKED 状态,并且无法响应事件。该状态在 Android Device Monitor 中会显示为“监控”或“等待”,如图 5 所示。

图 5. 处于“监控”状态的主线程

图 5. 处于“监控”状态的主线程

以下跟踪信息显示了因等待资源而处于阻塞状态的应用主线程:

...
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();
       }
   }
}

还有一些其他情况会阻塞主线程,包括使用 LockSemaphore 以及资源池(如数据库连接池)或其他互斥(互斥锁)机制的线程。

您应总体上评估应用对资源持有的锁,但如果您想避免 ANR,则应查看对主线程所需资源持有的锁。

请确保将持有锁的时间降到最少,或者最好从一开始就评估应用是否需要持有锁。如果您使用锁来确定何时根据工作器线程的处理情况来更新界面,请使用 onProgressUpdate()onPostExecute() 之类的机制在工作器线程和主线程之间进行通信。

死锁

线程进入等待状态时会发生死锁,因为所需资源由另一个线程持有,而该线程也在等待第一个线程持有的资源。如果应用的主线程处于这种情况,很可能会发生 ANR。

计算机科学领域对死锁现象进行了充分研究,目前有一些死锁预防算法可用于避免死锁。

如需了解详情,请参阅维基百科上的死锁死锁预防算法

执行速度缓慢的广播接收器

应用可以通过广播接收器响应广播消息,例如启用或停用飞行模式或更改连接状态。如果应用处理广播消息的用时过长,就会发生 ANR。

以下情况下会发生 ANR:

您的应用应只在 BroadcastReceiveronReceive() 方法中执行短操作。不过,如果您的应用因广播消息而需要进行更复杂的处理,您应将该任务推迟到 IntentService

您可以使用 TraceView 等工具来识别广播接收器是否在应用的主线程上执行长时间运行的操作。例如,图 6 显示了某广播接收器的时间轴,该接收器在主线程上处理消息用时大约 100 秒。

图 6. TraceView 时间轴中显示了主线程上的 BroadcastReceiver 工作

图 6. TraceView 时间轴中显示了主线程上的 BroadcastReceiver 工作

如果对 BroadcastReceiveronReceive() 方法执行长时间运行的操作,可能会导致此行为,如以下示例所示:

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 时间轴中显示了推迟到工作器线程的工作。

图 7. TraceView 时间轴中显示了在工作器线程上处理的广播消息

图 7. 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 超时仍然适用。

如需详细了解 ANR,请参阅让您的应用随时能迅速响应。如需详细了解线程,请参阅线程性能