Концепции и реализация Jetpack Compose
Когда поток пользовательского интерфейса Android-приложения блокируется слишком долго, возникает ошибка «Приложение не отвечает» (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 (Anti-Resources — неработающие приложения), вы можете воспользоваться рекомендациями в этой статье для диагностики и устранения проблемы.
Устраните проблемы
После того, как вы определили проблему, вы можете использовать советы из этого раздела для устранения распространенных неполадок.
Медленная работа кода в основном потоке.
Определите места в вашем коде, где основной поток приложения занят более 5 секунд. Найдите подозрительные сценарии использования в вашем приложении и попробуйте воспроизвести ошибку ANR.
Например, на рисунке 2 показана временная шкала Traceview, где основной поток занят более 5 секунд.

Рисунок 2. Временная шкала TraceView, показывающая загруженный основной поток.
На рисунке 2 показано, что большая часть проблемного кода находится в обработчике onClick(View) , как показано в следующем примере кода:
Котлин
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 или, что еще лучше, используйте `Repository` на уровне данных . Рекомендуется перенести все операции ввода-вывода в рабочий поток, как показано в предыдущем разделе.
Примерами операций ввода-вывода являются сетевые операции и операции хранения данных. Для получения дополнительной информации см. разделы «Выполнение сетевых операций» и «Сохранение данных» .
Конфликт блокировок
В некоторых сценариях работа, вызывающая ошибку ANR, выполняется не непосредственно в основном потоке приложения. Если рабочий поток удерживает блокировку ресурса, необходимого основному потоку для завершения своей работы, то может произойти ошибка ANR.
Например, на рисунке 3 показана временная шкала Traceview, где большая часть работы выполняется в рабочем потоке.

Рисунок 3. Временная шкала TraceView, показывающая выполнение работы в рабочем потоке.
Но если пользователи по-прежнему сталкиваются с ошибками ANR, следует проверить состояние основного потока в мониторе устройств Android. Обычно основной поток находится в состоянии RUNNABLE , если он готов к обновлению пользовательского интерфейса и в целом отзывчив.
Но если основной поток не может возобновить выполнение, то он находится в состоянии BLOCKED и не может реагировать на события. В мониторе устройств Android это состояние отображается как 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)
...
Анализ трассировки поможет вам найти код, блокирующий основной поток. Следующий код отвечает за удержание блокировки, которая блокирует основной поток в предыдущей трассировке:
Котлин
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 возникает, когда приложению требуется слишком много времени для обработки широковещательного сообщения.
Ангионервная регургитация возникает в следующих случаях:
- Приёмник широковещательной рассылки не завершил выполнение своего метода
onReceiveв течение значительного промежутка времени. - Приемник широковещательной рассылки вызывает
goAsync, но не может вызватьfinishдля объектаPendingResult.
В методе onReceive объекта BroadcastReceiver ваше приложение должно выполнять только короткие операции. Однако, если вашему приложению требуется более сложная обработка в результате широковещательного сообщения, следует отложить задачу до ViewModel (используя возможности корутин, областей видимости и диспетчеров Kotlin), если ожидается, что задача займет не более нескольких секунд, до любого типа хранилища состояния или до WorkManager для задач, ожидаемых на выполнение дольше нескольких секунд.
С помощью таких инструментов, как Traceview, можно определить, выполняет ли ваш широковещательный приемник длительные операции в основном потоке приложения. Например, на рисунке 6 показана временная шкала широковещательного приемника, обрабатывающего сообщение в основном потоке в течение приблизительно 100 секунд.

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