ANR

Когда поток пользовательского интерфейса приложения Android блокируется слишком долго, возникает ошибка «Приложение не отвечает» (ANR). Если приложение находится на переднем плане, система отображает пользователю диалоговое окно, как показано на рисунке 1. Диалоговое окно ANR дает пользователю возможность принудительно закрыть приложение.

Рисунок 1. Диалоговое окно ANR, отображаемое пользователю

Рисунок 1. Диалоговое окно ANR, отображаемое пользователю

Ошибки ANR представляют собой проблему, поскольку основной поток приложения, отвечающий за обновление пользовательского интерфейса, не может обрабатывать события пользовательского ввода или рисовать, что вызывает разочарование пользователя. Дополнительные сведения об основном потоке приложения см. в разделе Процессы и потоки .

Ошибка ANR срабатывает для вашего приложения при возникновении одного из следующих условий:

  • Тайм-аут отправки ввода: если ваше приложение не отреагировало на событие ввода (например, нажатие клавиши или касание экрана) в течение 5 секунд.
  • Выполнение службы: если служба, объявленная вашим приложением, не может завершить выполнение Service.onCreate() и Service.onStartCommand() / Service.onBind() в течение нескольких секунд.
  • Service.startForeground() не вызывается: если ваше приложение использует Context.startForegroundService() для запуска новой службы на переднем плане, но затем служба не вызывает startForeground() в течение 5 секунд.
  • Трансляция намерения: если BroadcastReceiver не завершил выполнение в течение заданного периода времени. Если в приложении есть какие-либо действия на переднем плане, этот тайм-аут составляет 5 секунд.
  • Взаимодействие с JobScheduler: если JobService не возвращается из JobService.onStartJob() или JobService.onStopJob() в течение нескольких секунд или если запускается инициированное пользователем задание , а ваше приложение не вызывает JobService.setNotification() в течение нескольких секунд. после вызова JobService.onStartJob() . Для приложений, предназначенных для Android 13 и более ранних версий, ошибки ANR не отображаются и не передаются приложению. Для приложений, ориентированных на Android 14 и более поздних версий, ошибки ANR являются явными и передаются приложению.

Если в вашем приложении возникают ошибки ANR, вы можете использовать рекомендации из этой статьи для диагностики и устранения проблемы.

Обнаружить проблему

Если вы уже опубликовали свое приложение, вы можете использовать Android Vitals, чтобы просмотреть информацию об ошибках ANR для вашего приложения. Вы можете использовать другие инструменты для обнаружения ошибок ANR в полевых условиях, но учтите, что инструменты 3P не могут сообщать об ошибках ANR в более старых версиях Android (Android 10 и ниже), в отличие от Android Vitals.

Android Vitals

Android Vitals может помочь вам отслеживать и улучшать показатель ANR вашего приложения. Android Vitals измеряет несколько показателей ANR:

  • Показатель ANR: процент ваших ежедневных активных пользователей, у которых возникал любой тип ANR.
  • Доля ошибок ANR по мнению пользователей: процент активных пользователей в день, которые столкнулись хотя бы с одним сообщением ANR по мнению пользователей . В настоящее время только сообщения ANR типа Input dispatching timed out считаются воспринимаемыми пользователем.
  • Доля множественных ошибок ANR: процент активных пользователей в день, у которых возникло как минимум два ошибки ANR.

Ежедневно активный пользователь — это уникальный пользователь, который использует ваше приложение в течение одного дня на одном устройстве, возможно, в течение нескольких сеансов. Если пользователь использует ваше приложение более чем на одном устройстве в течение одного дня, каждое устройство будет способствовать увеличению количества активных пользователей за этот день. Если несколько пользователей используют одно и то же устройство в течение одного дня, это считается одним активным пользователем.

Показатель ANR, воспринимаемый пользователем, является ключевым , поскольку он влияет на видимость вашего приложения в Google Play. Это важно, поскольку ошибки ANR, которые он подсчитывает, всегда возникают, когда пользователь взаимодействует с приложением, что приводит к наибольшему количеству сбоев.

Компания Play определила два порога плохого поведения по этому показателю:

  • Общий порог плохого поведения: по крайней мере 0,47 % ежедневных активных пользователей сталкиваются с воспринимаемым пользователем ANR на всех моделях устройств.
  • Порог плохого поведения для каждого устройства: по крайней мере 8% пользователей в день сталкиваются с оценкой ANR для одной модели устройства .

Если ваше приложение превышает общий порог плохого поведения, оно, скорее всего, будет менее заметно на всех устройствах. Если ваше приложение превышает порог плохого поведения для каждого устройства на некоторых устройствах, оно, скорее всего, будет менее заметно на этих устройствах, и на вашей странице в магазине может появиться предупреждение.

Android Vitals может предупреждать вас через Play Console , когда в вашем приложении наблюдается чрезмерное количество ошибок ANR.

Информацию о том, как Google Play собирает данные Android Vitals, можно найти в документации Play Console .

Диагностика ошибок ANR

Существует несколько общих закономерностей, на которые следует обращать внимание при диагностике ANR:

  • Приложение выполняет медленные операции ввода-вывода в основном потоке.
  • Приложение выполняет длинные вычисления в основном потоке.
  • Основной поток выполняет синхронный вызов связывателя к другому процессу, и этому другому процессу требуется много времени для возврата.
  • Основной поток блокируется в ожидании синхронизированной блокировки для длительной операции, происходящей в другом потоке.
  • Основной поток находится в тупике с другим потоком либо в вашем процессе, либо через вызов связывателя. Основной поток не просто ожидает завершения длительной операции, а находится в тупиковой ситуации. Для получения дополнительной информации см. Тупик в Википедии.

Следующие методы могут помочь вам определить причину ошибок ANR.

Статистика здоровья

HealthStats предоставляет показатели работоспособности приложения, фиксируя общее время пользователя и системы, время процессора, сеть, статистику радиосвязи, время включения/выключения экрана и сигналы пробуждения. Это может помочь в измерении общего использования ЦП и разряда батареи.

Отлаживать

Debug помогает проверять приложения Android во время разработки, включая отслеживание и подсчет выделений для выявления зависаний и задержек в приложениях. Вы также можете использовать Debug для получения счетчиков времени выполнения и собственной памяти, а также показателей памяти, которые могут помочь определить объем памяти, занимаемый конкретным процессом.

ПриложениеExitInfo

ApplicationExitInfo доступен на Android 11 (уровень API 30) или выше и предоставляет информацию о причине выхода приложения. Сюда входят ошибки ANR, нехватка памяти, сбои приложений, чрезмерная загрузка ЦП, прерывания работы пользователя, системные сбои или изменения разрешений во время выполнения.

Строгий режим

Использование StrictMode помогает обнаружить случайные операции ввода-вывода в основном потоке во время разработки приложения. Вы можете использовать StrictMode на уровне приложения или действия.

Включить фоновые диалоговые окна ANR

Android отображает диалоговые окна ANR для приложений, которым требуется слишком много времени для обработки широковещательного сообщения, только если в параметрах разработчика устройства включен параметр «Показать все ANR» . По этой причине фоновые диалоговые окна ANR не всегда отображаются пользователю, но приложение по-прежнему может испытывать проблемы с производительностью.

Трассировка

Вы можете использовать Traceview, чтобы отслеживать работу вашего приложения при просмотре вариантов использования и определять места, где основной поток занят. Сведения о том, как использовать Traceview, см. в разделе Профилирование с помощью Traceview и dmtracedump .

Извлечь файл трассировки

Android сохраняет информацию трассировки при возникновении ошибки ANR. В более старых версиях ОС на устройстве имеется один файл /data/anr/traces.txt . В более новых версиях ОС имеется несколько файлов /data/anr/anr_* . Вы можете получить доступ к трассировкам ANR с устройства или эмулятора, используя Android Debug Bridge (adb) от имени пользователя root:

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

Вы можете записать отчет об ошибке с физического устройства, используя либо опцию разработчика «Создать отчет об ошибке» на устройстве, либо команду adb bugreport на компьютере разработки. Дополнительные сведения см. в разделе Сбор и чтение отчетов об ошибках .

Устраните проблемы

После того как вы определили проблему, вы можете воспользоваться советами этого раздела для устранения часто встречающихся проблем.

Медленный код в основном потоке

Определите места в вашем коде, где основной поток приложения занят более 5 секунд. Найдите подозрительные варианты использования в своем приложении и попытайтесь воспроизвести ошибку ANR.

Например, на рисунке 2 показана временная шкала Traceview, где основной поток занят более 5 секунд.

Рисунок 2. Временная шкала Traceview, показывающая занятой основной поток

Рисунок 2. Временная шкала Traceview, показывающая занятой основной поток

На рис. 2 показано, что большая часть кода-нарушителя происходит в обработчике onClick(View) , как показано в следующем примере кода:

Котлин

override fun onClick(v: View) {
    // This task runs on the main thread.
    BubbleSort.sort(data)
}

Ява

@Override
public void onClick(View view) {
    // This task runs on the main thread.
    BubbleSort.sort(data);
}

В этом случае вам следует переместить работу, выполняемую в основном потоке, в рабочий поток. В Android Framework есть классы, которые могут помочь переместить задачу в рабочий поток. Дополнительные сведения см. в разделе Рабочие потоки .

IO в основном потоке

Выполнение операций ввода-вывода в основном потоке является распространенной причиной медленных операций в основном потоке, что может привести к ошибкам ANR. Рекомендуется переместить все операции ввода-вывода в рабочий поток, как показано в предыдущем разделе.

Некоторыми примерами операций ввода-вывода являются операции сети и хранилища. Дополнительную информацию см. в разделах «Выполнение сетевых операций» и «Сохранение данных» .

Конфликт блокировки

В некоторых сценариях работа, вызывающая ошибку ANR, не выполняется непосредственно в основном потоке приложения. Если рабочий поток удерживает блокировку ресурса, который необходим основному потоку для завершения своей работы, может произойти ошибка ANR.

Например, на рисунке 3 показана временная шкала Traceview, где большая часть работы выполняется в рабочем потоке.

Рис. 3. Временная шкала Traceview, показывающая работу, выполняемую в рабочем потоке.

Рис. 3. Временная шкала Traceview, показывающая работу, выполняемую в рабочем потоке.

Но если ваши пользователи по-прежнему сталкиваются с ошибками ANR, вам следует посмотреть состояние основного потока в Android Device Monitor. Обычно основной поток находится в состоянии RUNNABLE , если он готов обновить пользовательский интерфейс и в целом реагирует.

Но если основной поток не может возобновить выполнение, значит, он находится в состоянии BLOCKED и не может реагировать на события. Состояние отображается на мониторе устройства Android как Monitor или Wait , как показано на рисунке 5.

Рисунок 4. Основной поток в статусе монитора

Рисунок 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)
...

Просмотр трассировки может помочь вам найти код, блокирующий основной поток. Следующий код отвечает за удержание блокировки, блокирующей основной поток в предыдущей трассировке:

Котлин

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])
            }
}

Ява

@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]);
       }
   }
}

Другой пример — основной поток приложения, ожидающий результата от рабочего потока, как показано в следующем коде. Обратите внимание, что использование wait() и notify() не является рекомендуемым шаблоном в 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()
        }
    }
}

Ява

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 возникает, когда приложению требуется слишком много времени для обработки широковещательного сообщения.

Ошибка ANR возникает в следующих случаях:

  • Получатель широковещательной рассылки не завершил выполнение своего метода onReceive() в течение значительного времени.
  • Получатель широковещательной рассылки вызывает goAsync() и не может вызвать finish() для объекта PendingResult .

Ваше приложение должно выполнять только короткие операции в методе onReceive() BroadcastReceiver . Однако если вашему приложению требуется более сложная обработка в результате широковещательного сообщения, вам следует передать задачу IntentService .

Вы можете использовать такие инструменты, как Traceview, чтобы определить, выполняет ли ваш приемник вещания длительные операции в основном потоке приложения. Например, на рисунке 6 показана временная шкала широковещательного приемника, который обрабатывает сообщение в основном потоке в течение примерно 100 секунд.

Рис. 5. Временная шкала Traceview, показывающая работу BroadcastReceiver в основном потоке.

Рис. 5. Временная шкала Traceview, показывающая работу BroadcastReceiver в основном потоке.

Такое поведение может быть вызвано выполнением длительных операций над методом onReceive() объекта BroadcastReceiver , как показано в следующем примере:

Котлин

override fun onReceive(context: Context, intent: Intent) {
    // This is a long-running operation
    BubbleSort.sort(data)
}

Ява

@Override
public void onReceive(Context context, Intent intent) {
    // This is a long-running operation
    BubbleSort.sort(data);
}

В подобных ситуациях рекомендуется переместить длительную операцию в IntentService , поскольку для выполнения своей работы она использует рабочий поток. Следующий код показывает, как использовать IntentService для обработки длительной операции:

Котлин

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)
    }
}

Ява

@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, показывающая широковещательное сообщение, обработанное в рабочем потоке.

Рис. 6. Временная шкала Traceview, показывающая широковещательное сообщение, обработанное в рабочем потоке.

Ваш приемник вещания может использовать goAsync() чтобы сигнализировать системе, что ей нужно больше времени для обработки сообщения. Однако вам следует вызвать finish() для объекта PendingResult . В следующем примере показано, как вызвать Finish(), чтобы позволить системе перезапустить широковещательный приемник и избежать ошибки ANR:

Котлин

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)

Ява

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 по-прежнему применяется.

Игровая активность

Библиотека GameActivity снизила количество ошибок ANR в тематических исследованиях игр и приложений, написанных на C или C++. Если вы замените существующую собственную активность на GameActivity , вы сможете уменьшить блокировку потоков пользовательского интерфейса и предотвратить возникновение некоторых ошибок ANR.

Дополнительные сведения об ошибках ANR см. в разделе Обеспечение оперативности вашего приложения . Дополнительные сведения о потоках см. в разделе Производительность потоков .

{% дословно %} {% дословно %} {% дословно %} {% дословно %}