Основные понятия и реализация Jetpack Compose
Если поток UI приложения Android заблокирован слишком долго, возникает ошибка "Приложение не отвечает" (ANR). Если приложение работает в активном режиме, система показывает пользователю диалоговое окно, как на рисунке 1. В диалоговом окне ANR пользователю предлагается принудительно закрыть приложение.
Ошибки ANR возникают, когда основной поток приложения, отвечающий за обновление интерфейса, не может обработать действия пользователя или отрисовать экран. Это мешает работе с приложением. Подробнее об основном потоке приложения…
Ошибка ANR возникает в следующих случаях:
- Превышено время ожидания ввода. Приложение не реагирует на ввод (например, нажатие клавиши или касание экрана) в течение пяти секунд.
- Выполнение сервиса. Если сервис, объявленный в приложении, не может завершить выполнение методов
Service.onCreate()иService.onStartCommand()/Service.onBind()в течение нескольких секунд. **Service.startForeground()не вызывается**. Если приложение используетContext.startForegroundService()для запуска новой активной службы, но она не вызываетstartForeground()в течение пяти секунд.- Трансляция намерения. Если выполнение
BroadcastReceiverне завершилось в течение заданного времени. Если в приложении есть действия на переднем плане, время ожидания составляет 10 секунд. **JobSchedulerвзаимодействия**. ЕслиJobServiceне возвращается изJobService.onStartJob()илиJobService.onStopJob()в течение нескольких секунд или если запускается задача, инициированная пользователем, и ваше приложение не вызываетJobService.setNotification()в течение нескольких секунд после вызоваJobService.onStartJob(). В приложениях для Android 13 и более ранних версий ошибки ANR не регистрируются и не передаются в приложение. В приложениях для Android 14 и более поздних версий ошибки ANR регистрируются и передаются в приложение.
Если в вашем приложении возникают ошибки ANR, воспользуйтесь рекомендациями из этой статьи, чтобы выявить и устранить проблему.
Устраните проблемы
Определив проблему, вы можете воспользоваться советами из этого раздела, чтобы устранить распространенные неполадки.
Медленный код в основном потоке
Определите, в каких местах кода основной поток приложения занят более пяти секунд. Попробуйте найти подозрительные сценарии использования в приложении и воспроизвести ошибку ANR.
Например, на рисунке 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 Framework есть классы, которые помогают перенести задачу в рабочий поток. Подробнее о рабочих потоках…
Операция ввода-вывода в основном потоке
Выполнение операций ввода-вывода в основном потоке – распространенная причина медленной работы основного потока, которая может приводить к ошибкам ANR. В Compose разработчики часто случайно запускают чтение с диска (например, SharedPreferences или вызовы базы данных), пытаясь получить начальное состояние.
Выполняйте длительные операции ввода-вывода отдельно от уровня пользовательского интерфейса. Используйте withContext(Dispatchers.IO) в ViewModel или, что ещё лучше, используйте репозиторий на уровне данных. Рекомендуется перенести все операции ввода-вывода в рабочий поток, как показано в предыдущем разделе.
Примеры операций ввода-вывода: операции с сетью и хранилищем. Подробную информацию можно найти в статьях Выполнение сетевых операций и Сохранение данных.
Конфликт блокировок
В некоторых случаях работа, которая приводит к ошибке ANR, выполняется не в основном потоке приложения. Если рабочий поток блокирует ресурс, который нужен основному потоку для завершения работы, может возникнуть ошибка ANR.
Например, на рисунке 3 показана временная шкала Traceview, на которой большая часть работы выполняется в рабочем потоке.

Рисунок 3. Временная шкала Traceview, на которой показана работа, выполняемая в рабочем потоке
Если же ошибки ANR по-прежнему возникают, проверьте статус основного потока в Android Device Monitor. Обычно основной поток находится в состоянии RUNNABLE, если он готов обновить интерфейс и в целом реагирует на действия пользователя.
Если же основной поток не может возобновить выполнение, он находится в состоянии BLOCKED и не может реагировать на события. В Android Device Monitor статус будет показан как Monitor или Wait (см. рис. 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]);
}
}
}
Другой пример – основной поток приложения, ожидающий результата от рабочего потока, как показано в следующем коде. Обратите внимание, что использование 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()
}
}
}
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в течение значительного времени. - Широковещательный приемник вызывает
goAsync, но не вызываетfinishдля объектаPendingResult.
В методе onReceive класса BroadcastReceiver должны выполняться только короткие операции. Однако если вашему приложению требуется более сложная обработка в результате оповещения, вам следует отложить задачу до ViewModel (используя возможности сопрограмм, областей видимости и диспетчеров Kotlin), если ожидается, что задача займет не более нескольких секунд, любого типа хранилища состояния или до WorkManager для задач, которые, как ожидается, займут больше нескольких секунд.
Чтобы определить, выполняет ли широковещательный приемник продолжительные операции в основном потоке приложения, можно использовать такие инструменты, как Traceview. Например, на рисунке 6 показана временная шкала широковещательного приемника, который обрабатывает сообщение в основном потоке примерно 100 секунд.

Рисунок 5. Хронология Traceview, показывающая работу BroadcastReceiver в основном потоке
Это может быть вызвано выполнением продолжительных операций в методе onReceive() класса BroadcastReceiver, как показано в следующем примере:
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(), чтобы сообщить системе, что ему нужно больше времени на обработку сообщения. Однако вам нужно вызвать finish() для объекта PendingResult. В следующем примере показано, как вызвать 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 остается прежним.