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

Когда поток пользовательского интерфейса приложения Android блокируется слишком долго, система отправляет ошибку «Приложение не отвечает» (ANR). На этой странице описаны различные типы ошибок ANR, способы их диагностики и рекомендации по их устранению. Все указанные диапазоны времени ожидания по умолчанию относятся к устройствам AOSP и Pixel; эти значения могут различаться в зависимости от производителя.

Следует помнить, что при определении причины возникновения ошибок ANR полезно различать системные проблемы и проблемы, связанные с приложением.

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

  • Временные сбои в работе системного сервера приводят к замедлению обычно быстрых вызовов Binder.
  • Проблемы с системным сервером и высокая нагрузка на устройства приводят к тому, что потоки приложения не планируются к выполнению.

Если у вас есть такая возможность, хороший способ отличить системные проблемы от проблем с приложениями — использовать трассировку Perfetto :

  • Чтобы проверить, запланирован ли основной поток приложения, посмотрите состояние потока в Perfetto: запущен он или готов к запуску.
  • Обратитесь к обсуждениям system_server по таким вопросам, как конфликты блокировок.
  • Если вызовы Binder выполняются медленно, просмотрите ветку обсуждений (если она есть), чтобы понять причину задержки.

Таймаут отправки входных данных

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

Время ожидания по умолчанию : 5 секунд.

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

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

  • Не выполняйте блокирующие или длительные операции в основном потоке. Рекомендуется использовать StrictMode для перехвата случайных действий в основном потоке.
  • Свести к минимуму конфликты блокировок между основным потоком и другими потоками.
  • Сведите к минимуму работу в основном потоке, не связанную с пользовательским интерфейсом, например, при обработке широковещательных сообщений или запуске служб.

Распространенные причины

Ниже приведены некоторые распространенные причины и рекомендуемые способы устранения ошибок ANR при отправке входных данных.

Причина Что происходит Предложенные решения
Медленный вызов связующего элемента Основной поток выполняет длительный синхронный вызов связывателя. Если вы являетесь владельцем API, перенесите вызов из основного потока или попробуйте оптимизировать его.
Много последовательных звонков по поводу папок Основной поток выполняет множество последовательных синхронных вызовов связывателя. Не выполняйте вызовы binder в плотном цикле.
Блокирующий ввод-вывод Основной поток выполняет блокирующие вызовы ввода-вывода, такие как доступ к базе данных или сети. Перенесите все блокирующие операции ввода-вывода из основного потока.
Конфликт блокировок Основной поток заблокирован в ожидании получения блокировки. Уменьшить конкуренцию за блокировки между основным потоком и другими потоками. Оптимизировать медленный код в другом потоке.
Дорогая рама Отрисовка слишком большого количества объектов в одном кадре приводит к сильным рывкам изображения. Уменьшите объем работы по отрисовке кадра. Не используйте алгоритмы . Используйте эффективные компоненты для таких задач, как прокрутка или постраничная навигация — например, библиотеку Jetpack Paging .
Заблокировано другим компонентом Другой компонент, например, широковещательный приемник, работает и блокирует основной поток. По возможности вынесите задачи, не связанные с пользовательским интерфейсом, из основного потока. Запускайте приемники широковещательных сообщений в отдельном потоке.
Зависание графического процессора Зависание графического процессора — это системная или аппаратная проблема, которая приводит к блокировке рендеринга и, следовательно, к ошибке ANR (Antid Dispatch). К сожалению, обычно исправить проблему на стороне приложения не удаётся. Если возможно, обратитесь в службу поддержки по аппаратному обеспечению для устранения неполадок.

Как отлаживать

Начните отладку с анализа сигнатуры кластера ANR в Google Play Console или Firebase Crashlytics . Обычно кластер содержит кадры, наиболее часто вызывающие ANR.

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

Рисунок 1. Как отлаживать ошибку ANR при отправке входных данных.

Play Vitals может обнаруживать и помогать в отладке некоторых из этих распространенных причин ANR. Например, если Vitals обнаруживает, что ANR произошел из-за конфликта блокировок, он может кратко описать проблему и предложить рекомендуемое решение в разделе « Аналитика ANR».

Рисунок 2. Воспроизведение результатов определения ANR (опасности переднего нерва).

Нет сфокусированного окна

В то время как события, такие как касание, отправляются непосредственно в соответствующее окно на основе проверки попадания, событиям, таким как нажатие клавиш, требуется цель. Эта цель называется сфокусированным окном . На каждом дисплее может быть только одно сфокусированное окно, и обычно это окно, с которым пользователь в данный момент взаимодействует. Если сфокусированное окно не найдено, input генерирует ANR no-focused-window . ANR no-focused-window — это тип ANR обработки ввода.

Время ожидания по умолчанию : 5 секунд.

Распространенные причины

Проблемы с шумоподавлением при отсутствии фокусированного окна обычно вызваны одной из следующих причин:

  • Приложение выполняет слишком много работы и слишком медленно отрисовывает первый кадр.
  • Главное окно недоступно для фокусировки. Если окно помечено флагом FLAG_NOT_FOCUSABLE , пользователь не может отправлять в него события нажатия клавиш или кнопок.

Котлин

override fun onCreate(savedInstanceState: Bundle) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)
  window.addFlags(WindowManager.LayoutParams.FLAG_FLAG_NOT_FOCUSABLE)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}

Таймаут приемника широковещательной передачи

Ошибка ANR (Access Observation Receiving) возникает, когда приемник широковещательной рассылки не обрабатывает ее вовремя. Для синхронных приемников, или приемников, которые не вызывают goAsync() , таймаут означает, что onReceive() не завершился вовремя. Для асинхронных приемников, или приемников, которые вызывают goAsync() , таймаут означает, что PendingResult.finish() не был вызван вовремя.

В этих обсуждениях часто возникают ошибки ANR на широковещательных приемниках:

  • Основная тема обсуждения, если проблема связана с медленным запуском приложения.
  • Если проблема в медленном выполнении кода onReceive() , то это происходит в потоке, работающем с широковещательным приемником.
  • Если проблема в медленном выполнении кода широковещательной рассылки goAsync() , запустите рабочие потоки.

Чтобы избежать ошибок ANR на широковещательном приемнике, следуйте этим рекомендациям:

  • Убедитесь, что приложение запускается быстро, поскольку запуск приложения для обработки широковещательного сообщения учитывается в таймауте ANR.
  • Если используется goAsync() , убедитесь, что PendingResult.finish() вызывается быстро. На это распространяется тот же таймаут ANR, что и на синхронные широковещательные приемники.
  • Если используется goAsync() , убедитесь, что рабочие потоки не используются совместно с другими длительными или блокирующими операциями.
  • Рекомендуется использовать registerReceiver() для запуска широковещательных приемников в отдельном потоке, чтобы избежать блокировки кода пользовательского интерфейса, выполняющегося в основном потоке.

Периоды паузы

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

Тип намерения Android 13 и ниже Android 14 и выше

Намерение с приоритетом переднего плана

(Установлен флаг FLAG_RECEIVER_FOREGROUND )

10 секунд

10-20 секунд, в зависимости от того, испытывает ли процесс нехватку ресурсов процессора.

Фоновый приоритет намерения

( FLAG_RECEIVER_FOREGROUND не установлен)

60 секунд

60-120 секунд, в зависимости от того, испытывает ли процесс нехватку ресурсов процессора.

Чтобы определить, установлен ли флаг FLAG_RECEIVER_FOREGROUND , найдите "flg=" в теме ANR и проверьте наличие значения 0x10000000 . Если этот бит установлен, значит, для намерения установлен флаг FLAG_RECEIVER_FOREGROUND , и, следовательно, время ожидания будет короче.

Пример темы ANR с коротким временем ожидания трансляции (10-20 секунд):

Broadcast of Intent { act=android.inent.action.SCREEN_ON flg=0x50200010 }

Пример темы ANR с длительным таймаутом трансляции (60-120 секунд):

Broadcast of Intent { act=android.intent.action.TIME_SET flg=0x25200010 }

Как измеряется время трансляции

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

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

Рисунок 3. Временная шкала ANR приемника вещания.

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

  • Для синхронных приемников измерение прекращается после возврата из onReceive() .
  • Для асинхронных приемников измерение прекращается при вызове PendingResult.finish() .
Рисунок 4. Конечные точки измерения таймаута ANR для синхронных и асинхронных приемников.

Распространенные причины

Ниже приведены некоторые распространенные причины и рекомендуемые способы устранения проблем с ANR (антирадиационным шумоподавлением) у вещательных приемников.

Причина Применяется к Что случилось Предложенное решение
Медленный запуск приложения Все приемники Приложение слишком долго выполняло холодный запуск. Оптимизация медленного запуска приложений.
onReceive() не запланирована. Все приемники Поток, отвечающий за прием широковещательных сообщений, был занят выполнением других задач и не мог запустить метод onReceive() . Не выполняйте длительные задачи в потоке приемника (или переместите приемник в выделенный поток).
Медленный метод onReceive() Все приемники, но в основном синхронные. Метод onReceive() запустился, но был заблокирован или работал медленно, поэтому не завершился вовремя. Оптимизировать медленный код приемника.
Асинхронные задачи приемника не запланированы. goAsync() receivers Метод onReceive() попытался выполнить работу в заблокированном пуле рабочих потоков, поэтому работа так и не началась. Оптимизируйте медленные или блокирующие вызовы, или используйте разные потоки для обработки широковещательных операций и других длительных задач.
Рабочие замедляют или блокируются goAsync() receivers В процессе обработки широковещательного сообщения в пуле рабочих потоков произошла блокирующая или медленная операция. Поэтому PendingResult.finish не был вызван вовремя. Оптимизировать медленный async код обработчика событий.
Забыл вызвать PendingResult.finish goAsync() receivers В коде отсутствует вызов функции finish() . Убедитесь, что finish() вызывается всегда.

Как отлаживать

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

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

Рисунок 5. Как отлаживать ANR широковещательного приемника.

Найдите код приемника

В консоли Google Play отображается класс приемника и широковещательный интент в сигнатуре ANR. Обратите внимание на следующее:

  • cmp=<receiver class>
  • act=<broadcast_intent>

Вот пример сигнатуры ANR широковещательного приемника:

com.example.app.MyClass.myMethod
Broadcast of Intent { act=android.accounts.LOGIN_ACCOUNTS_CHANGED
cmp=com.example.app/com.example.app.MyAccountReceiver }

Найдите поток, выполняющий метод onReceive().

Если вы используете Context.registerReceiver для указания пользовательского обработчика, то это поток, выполняющий этот обработчик. В противном случае это основной поток.

Пример: асинхронные задачи-приемники не запланированы.

В этом разделе рассматривается пример отладки ANR широковещательного приемника.

Допустим, сигнатура ANR выглядит следующим образом:

com.example.app.MyClass.myMethod
Broadcast of Intent {
act=android.accounts.LOG_ACCOUNTS_CHANGED cmp=com.example.app/com.example.app.MyReceiver }

Судя по сигнатуре, похоже, что широковещательный интент — это android.accounts.LOG_ACCOUNTS_CHANGED , а класс получателя — com.example.app.MyReceiver .

Из кода приемника можно определить, что основную работу по обработке этой широковещательной рассылки выполняет пул потоков "BG Thread [0,1,2,3]". Просмотрев дампы стека, можно увидеть, что все четыре фоновых (BG) потока имеют одинаковую схему: они выполняют блокирующий вызов getDataSync . Поскольку все фоновые потоки были заняты, широковещательная рассылка не могла быть обработана вовремя, что привело к ошибке ANR.

BG Thread #0 (tid=26) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture:563)
at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture:68)
at com.example.app.getDataSync(<MyClass>:152)

...

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.libraries.concurrent.AndroidExecutorsModule.lambda$withStrictMode$5(AndroidExecutorsModule:451)
at com.google.android.libraries.concurrent.AndroidExecutorsModule$$ExternalSyntheticLambda8.run(AndroidExecutorsModule:1)
at java.lang.Thread.run(Thread.java:1012)
at com.google.android.libraries.concurrent.ManagedPriorityThread.run(ManagedPriorityThread:34)

There are several approaches to fix the issue:

  • Find out why getDataSync is slow and optimize.
  • Don't run getDataSync on all four BG threads.
  • More generally, ensure that the BG thread pool isn't saturated with long-running operations.
  • Use a dedicated thread pool for goAsync worker tasks.
  • Use an unbounded thread pool instead of the bounded BG thread pool

Example: slow app startup

A slow app startup can cause several types of ANRs, especially broadcast receiver and execute service ANRs. The cause of an ANR is likely slow app startup if you see ActivityThread.handleBindApplication in the main thread stacks.

Execute service timeout

An execute service ANR happens when the app's main thread doesn't start a service in time. Specifically, a service doesn't finish executing onCreate() and onStartCommand() or onBind() within the timeout period.

Default timeout period: 20 seconds for foreground service; 200 seconds for background service. The ANR timeout period includes the app cold start, if necessary, and calls to onCreate(), onBind(), or onStartCommand().

To avoid execute service ANRs, follow these general best practices:

  • Make sure that app startup is fast, since it's counted in the ANR timeout if the app is started to run the service component.
  • Make sure that the service's onCreate(), onStartCommand(), and onBind() methods are fast.
  • Avoid running any slow or blocking operations on the main thread from other components; these operations can prevent a service from starting quickly.

Common causes

The following table lists common causes of execute service ANRs and suggested fixes.

Cause What Suggested fix
Slow app startup The app takes too long to perform a cold start. Optimize slow app start.
Slow onCreate(), onStartCommand(), or onBind() The service component's onCreate(), onStartCommand(), or onBind() method takes too long to execute on the main thread. Optimize slow code. Move slow operations off the critical path where possible.
Not scheduled (main thread blocked before onStart()) The app's main thread is blocked by another component before the service can be started. Move other component's work off the main thread. Optimize other component's blocking code.

How to debug

From the cluster signature and ANR report in Google Play Console or Firebase Crashlytics, you can often determine the cause of the ANR based on what the main thread is doing.

The following flow chart describes how to debug an execute service ANR.

Figure 6. How to debug an execute service ANR.

If you've determined that the execute service ANR is actionable, follow these steps to help resolve the issue:

  1. Find the service component class in the ANR signature. In Google Play Console, the service component class is shown in the ANR signature. In the following example ANR details, it's com.example.app/MyService.

    com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly
    Executing service com.example.app/com.example.app.MyService
    
  2. Determine whether the slow or block operation is part of app startup, the service component, or elsewhere by checking for the following important function call(s) in the main threads.

    Function call(s) in main thread stacks What it means
    android.app.ActivityThread.handleBindApplication App was starting up, so the ANR was caused by slow app start.

    <ServiceClass>.onCreate()

    [...]

    android.app.ActivityThread.handleCreateService

    Service was being created, so the ANR was likely caused by slow onCreate() code.

    <ServiceClass>.onBind()

    [...]

    android.app.ActivityThread.handleBindService

    Service was being bound, so the ANR was likely caused by slow onBind() code.

    <ServiceClass>.onStartCommand()

    [...]

    android.app.ActivityThread.handleServiceArgs

    Service was being started, so the ANR was likely caused by slow onStartCommand() code.

    For example, if the onStartCommand() method in the MyService class is slow, the main threads will look like this:

    at com.example.app.MyService.onStartCommand(FooService.java:25)
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4820)
    at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(unavailable:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2289)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8176)
    at java.lang.reflect.Method.invoke(Native method:0)
    

    Если вы не видите ни одного из важных вызовов функций, есть еще несколько возможных причин:

    • Служба запущена или завершена, что означает, что данные стека берутся слишком поздно. В этом случае сообщение об ошибке ANR можно игнорировать как ложное срабатывание.
    • Запущен другой компонент приложения, например, широковещательный приемник. В этом случае основной поток, скорее всего, заблокирован в этом компоненте, что препятствует запуску службы.
  3. Если вы видите вызов ключевой функции и можете определить, где именно происходит ошибка ANR, проверьте стеки остальных основных потоков, чтобы найти медленную операцию и оптимизировать её или переместить за пределы критического пути.

Для получения более подробной информации об услугах, пожалуйста, посетите следующие страницы:

Поставщик контента не отвечает

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

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

Чтобы избежать ошибок ANR у поставщиков контента, следуйте этим рекомендациям:

  • Убедитесь, что приложение запускается быстро, поскольку запуск приложения для работы с поставщиком контента учитывается в таймауте ANR.
  • Убедитесь, что запросы к поставщику контента выполняются быстро.
  • Не выполняйте множество одновременных блокирующих вызовов binder, которые могут заблокировать все потоки binder приложения.

Распространенные причины

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

Причина Что происходит Сигнал Предложенное решение
Медленный запрос поставщика контента Выполнение запроса к поставщику контента занимает слишком много времени или заблокировано. Объект android.content.ContentProvider$Transport.query находится в потоке привязки. Оптимизируйте запрос к поставщику контента. Выясните, что блокирует поток связывания.
Медленный запуск приложения Приложение поставщика контента запускается слишком долго. Обработчик ActivityThread.handleBindApplication находится в основном потоке. Оптимизируйте запуск приложения.
Нити переплета исчерпаны — все нити переплета заняты Все потоки связывания заняты обработкой других синхронных запросов, поэтому вызов связывания поставщика контента не может быть выполнен. Приложение не запускается, все потоки Binder заняты, и поставщик контента не запущен. Снизьте нагрузку на потоки связывания. То есть, выполняйте меньше синхронных исходящих вызовов связывания или тратьте меньше усилий на обработку входящих вызовов.

Как отлаживать

Чтобы отладить ошибку ANR у поставщика контента, используя сигнатуру кластера и отчет об ошибке ANR в Google Play Console или Firebase Crashlytics, посмотрите, что делают основной поток и потоки связывания.

Следующая блок-схема описывает, как отлаживать ошибку ANR поставщика контента:

Рисунок 7. Как отлаживать ошибку ANR поставщика контента.

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

binder:11300_2 (tid=13) Blocked

Waiting for osm (0x01ab5df9) held by at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers:182)
at com.example.app.MyClass.blockingGetOpenDatabase(FooClass:171)
[...]
at com.example.app.MyContentProvider.query(MyContentProvider.java:915)
at android.content.ContentProvider$Transport.query(ContentProvider.java:292)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:107)
at android.os.Binder.execTransactInternal(Binder.java:1339)
at android.os.Binder.execTransact(Binder.java:1275)

Приведённый ниже фрагмент кода показывает, как выглядит основной поток, когда он заблокирован из-за медленного запуска приложения. В данном случае медленный запуск приложения происходит из-за конфликта блокировок во время инициализации Dagger.

main (tid=1) Blocked

[...]
at dagger.internal.DoubleCheck.get(DoubleCheck:51)
- locked 0x0e33cd2c (a qsn)at dagger.internal.SetFactory.get(SetFactory:126)
at com.myapp.Bar_Factory.get(Bar_Factory:38)
[...]
at com.example.app.MyApplication.onCreate(DocsApplication:203)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6991)
at android.app.ActivityThread.-$$Nest$mhandleBindApplication(unavailable:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2235)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8170)
at java.lang.reflect.Method.invoke(Native method:0)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

Медленная реакция на задание

Ошибка ANR (медленный отклик задания) возникает, когда приложение слишком долго отвечает на JobService.onStartJob() или JobService.onStopJob() , или слишком долго отправляет уведомление с помощью JobService.setNotification() . Это говорит о том, что основной поток приложения заблокирован для выполнения других задач.

Если проблема связана с JobService.onStartJob() или JobService.onStopJob() , проверьте, что происходит в основном потоке. Если проблема связана с JobService.setNotification() , убедитесь, что вызываете его как можно быстрее. Не выполняйте много работы, прежде чем отправить уведомление.

Просмотреть нарушение правил построения веток

Попробуйте способ создания композиций.
Jetpack Compose — это рекомендуемый набор инструментов для создания пользовательского интерфейса для Android.

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

  • Бесшумные зависания пользовательского интерфейса : интерфейс приложения перестает реагировать на действия пользователя.
  • Сбои : Приложение может аварийно завершить работу с ошибкой illegalStateException .
  • ANR : Система может инициировать тайм-аут ANR.

Это нарушение многопоточности является одной из основных причин появления трассировок стека ANR, в которых основной поток отображается как простаивающий (например, в сообщениях "nativePollOnce" или "main thread idle").

Утечка синхронного барьера

Представления могут быть аннулированы либо косвенно путем изменения их состояния (например, с помощью вызовов TextView.setText , View.setVisibility ), либо напрямую путем вызова View.invalidate или View.requestLayout . При аннулировании представления ViewRootImpl планирует обход для выполнения измерений, компоновки и отрисовки с целью обновления состояния пользовательского интерфейса.

  1. Планирование обходов : ViewRootImpl планирует обход — проход компоновки и отрисовки — путем отправки барьера синхронизации в MessageQueue потока пользовательского интерфейса. Этот барьер приостанавливает обычную обработку сообщений, чтобы компоновка пользовательского интерфейса могла получить приоритет.
  2. Состояние гонки : Когда несколько потоков одновременно пытаются аннулировать представление, они соревнуются в планировании этого обхода. Оба потока могут успешно установить барьер синхронизации , но фреймворк хранит токен только для одного из них.
  3. Заморозка пользовательского интерфейса : При выполнении обхода удаляется только один сохраненный барьер. Вторичные, «просочившиеся» барьеры остаются в очереди неограниченно долго, навсегда блокируя поток пользовательского интерфейса от обработки любых синхронных сообщений.

В результате пользовательский интерфейс зависает. Поскольку MessageQueue не может обрабатывать синхронные сообщения после истечения пороговых значений, основной поток переходит в состояние ожидания. Эта проблема обычно проявляется как ANR с nativePollOnce в трассировке стека.

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

  • Взаимодействовать с объектами View — включая чтение свойств, таких как width или height , или установку свойств, таких как TextView's text — следует только в том потоке, где была создана иерархия представлений. Практически всегда это основной поток или поток пользовательского интерфейса.
  • Если вы не можете гарантировать, что при доступе к представлениям вы работаете в потоке пользовательского интерфейса, считайте это небезопасным. Например, если вы используете Coroutines для фоновой работы, необходимо переключиться на основной диспетчер перед тем, как манипулировать объектами View .

lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
    val data = myRepository.getData()
    withContext(Dispatchers.Main) { // Switch context to Main
        myTextView.text = data.title
    }
}

Чтобы помочь вам следовать передовым практикам, Android предлагает несколько способов доступа к потоку пользовательского интерфейса из других потоков:

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

Как отлаживать

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

1. Инструменты для обеспечения совместимости

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

  1. Установите отлаживаемую версию вашего приложения на устройство под управлением Android 17 или более поздней версии.
  2. Откройте приложение «Настройки» на вашем устройстве и перейдите в раздел «Система» > «Дополнительно» > «Параметры разработчика» > «Изменения совместимости приложений».
  3. Выберите приложение из списка.
  4. В списке изменений найдите и включите переключатель ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS .

Также вы можете включить или выключить этот флаг с помощью ADB:

$ adb shell am compat enable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
$ adb shell am compat disable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>

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

2. API CalledFromWrongThreadListener

Также можно реализовать глобальный API View#registerCalledFromWrongThreadListener для программного обнаружения некорректного доступа к потокам.

  • Телеметрия и отслеживание : Этот обработчик событий позволяет вашему приложению получать обратные вызовы при вызове API представления из некорректного потока, что упрощает сбор телеметрии и поиск ошибок.
  • Встроенное выполнение : Слушатель вызывается в режиме inline, что позволяет перехватить и проанализировать точный трассировочный стек в момент возникновения нарушения.
android {
   //...
   compileSdk = 37
}

val listener = object : View.CalledFromWrongThreadListener {
    override fun onCalledFromWrongThread() {
        // Handle the issue, e.g. crash if this is a dev build, or log an event
        // e.g. Log.d(TAG, "CalledFromWrongThread: ${Exception().stackTraceToString()}")
        // Unregister the listener to avoid redundant notifications for the same issue
        View.unregisterCalledFromWrongThreadListener(this)
    }
}
View.registerCalledFromWrongThreadListener(listener)

nativePollOnce

Если в стеке ANR вы видите кадр "nativePollOnce" или "message queue idle", это часто указывает на то, что предполагаемый не отвечающий поток на самом деле простаивал и ожидал сообщений от looper. В Google Play Console подробная информация об ANR выглядит следующим образом:

Native method - android.os.MessageQueue.nativePollOnce
Executing service com.example.app/com.example.app.MyService

Например, если основной поток простаивает, стеки выглядят следующим образом:

"main" tid=1 NativeMain threadIdle

#00  pc 0x00000000000d8b38  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
#01  pc 0x0000000000019d88  /system/lib64/libutils.so (android::Looper::pollInner(int)+184)
#02  pc 0x0000000000019c68  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112)
#03  pc 0x000000000011409c  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
at android.os.MessageQueue.nativePollOnce (Native method)
at android.os.MessageQueue.next (MessageQueue.java:339)  at android.os.Looper.loop (Looper.java:208)
at android.app.ActivityThread.main (ActivityThread.java:8192)
at java.lang.reflect.Method.invoke (Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:626)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1015)

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

  • Проблема общесистемного характера . Процесс не был запланирован из-за высокой системной нагрузки или проблемы на системном сервере.
  • Позднее сохранение дампа стека . Поток восстановился в течение короткого промежутка времени между срабатыванием ANR и сохранением дампа стека. Задержка на устройствах Pixel под управлением Android 13 составляет около 100 мс, но может превышать 1 секунду. Задержка на устройствах Pixel под управлением Android 14 обычно составляет менее 10 мс.
  • Неправильное указание источника потока. Поток, использованный для построения сигнатуры ANR, не являлся тем самым не отвечающим потоком, который вызвал ошибку ANR.
  • Нарушение многопоточности представления . Если ваше приложение изменяет представление в фоновом потоке, это может вызвать состояние гонки во внутренних механизмах представления, которое блокирует выполнение задач потока пользовательского интерфейса.

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

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

Ниже приведены рекомендуемые шаги для анализа ошибок ANR, возникающих при использовании nativePollOnce.

  1. Высокая системная нагрузка: оцените общее давление на ресурсы устройства, например, нехватку ресурсов ЦП, памяти или ввода-вывода, как основную причину зависания.
  2. Неправильное распределение потоков: Проверка рабочих и связующих потоков на предмет взаимоблокировок, конфликтов блокировок, затрагивающих основной поток, или зависания фоновых потоков, обрабатывающих асинхронные компоненты (например, goAsync() ).
  3. Нарушения многопоточности при просмотре: Сканируйте фоновые потоки на предмет недопустимых изменений иерархии представлений пользовательского интерфейса, которые могут привести к появлению барьера синхронизации в очереди сообщений и навсегда заблокировать все синхронные сообщения.

Нет стековых кадров

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

  • Получение данных из стека занимает слишком много времени и приводит к таймауту.
  • Процесс прекратился или был остановлен до того, как были вывезены штабели.
[...]

--- CriticalEventLog ---
capacity: 20
timestamp_ms: 1666030897753
window_ms: 300000

libdebuggerd_client: failed to read status response from tombstoned: timeout reached?

----- Waiting Channels: pid 7068 at 2022-10-18 02:21:37.<US_SOCIAL_SECURITY_NUMBER>+0800 -----

[...]

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

Известные проблемы

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