Распространенные ошибки ANR игр Unity

Ошибки ANR в Unity возникают по разным причинам. Чаще всего они вызваны неправильным использованием компонентов Android и Unity, а также их несовместимостью.

WebView

WebView — это класс Android, предназначенный для отображения веб-страниц. Сторонние SDK (например, рекламные) используют WebView для отображения динамического веб-контента в активностях, отличных от UnityPlayerActivity . Ошибки ANR возникают при неправильном использовании WebView сторонними SDK.

Трассировка стека

Трассировка стека — это ваш первый способ понять причину возникновения ANR.

/data/app/~~p-0ksfCD6bF6Sdq6kpVePg==/com.google.android.webview-5YQZOqKbbqp-uoLY6WYnTw==/base.apk!libmonochrome.so
  at J.N.Mhc_M_H$ (Native method)
  at org.chromium.components.viz.service.frame_sinks.ExternalBeginFrameSourceAndroid.doFrame (chromium-TrichromeWebViewGoogle.aab-stable-579013831:60)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1054)
  at android.view.Choreographer.doCallbacks (Choreographer.java:878)
  at android.view.Choreographer.doFrame (Choreographer.java:807)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1041)
  at android.os.Handler.handleCallback (Handler.java:938)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:223)
  at android.app.ActivityThread.main (ActivityThread.java:7721)
  at java.lang.reflect.Method.invoke (Native method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:592)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:952)

Рисунок 1. Трассировка стека ANR, вызванная ожиданием фьютекса .

Причина

На данный момент истинная причина этой проблемы неясна. Возможные причины:

  • Плохая реализация рекламы.
  • Устаревшая версия WebView , поскольку пользователь мог отказаться от автоматического обновления приложения.
  • Высокое использование системных ресурсов (ЦП, ГП и т. д.), что может потребовать тщательного профилирования.
  • Происходит сбой компиляции шейдера , что может указывать на то, что контент имеет несовместимый шейдер или что у пользователя установлена старая версия WebView .

Решение

  • Чтобы сузить круг типов контента, из-за которых WebView блокирует основной поток, добавьте в игру журналы каждой загрузки, отображения или закрытия веб-страницы.
    • Вы можете воспользоваться службами отчетности Backtrace или Crashlytics .
    • Затем, проанализировав данные и обнаружив проблему, попробуйте отключить нарушающих работу поставщиков рекламы.
    • Приложите журналы памяти, чтобы убедиться, что проблема не связана с памятью.
  • Предупредите пользователя о необходимости обновления WebView из Google Play . Начиная с Android 5.0 (API уровня 21) и выше, WebView перешёл в формат APK. Поэтому его можно обновлять отдельно от платформы Android. Чтобы узнать, какая версия WebView используется на устройстве, перейдите в раздел «Настройки�� > «Приложения» > «Системный WebView Android» и посмотрите версию внизу страницы.
Экран информации о приложении, на котором показаны версии WebView.
Рисунок 1. Проверьте версию WebView .

Пауза единства

Когда UnityPlayerActivity получает вызов onPause() , запускается следующая цепочка операций:

  1. UnityPlayerActivity уведомляет движок среды выполнения Unity о том, что действие приостановлено.
  2. Unity вызывает каждый MonoBehaviour , реализующий событие OnApplicationPause .
  3. Unity останавливает свои компоненты и модули, такие как воспроизведение звука, рендеринг, игровой цикл и анимацию.
  4. Чтобы убедиться, что Unity Android Player (UAP) и движок синхронизированы, UAP ждет 4 секунды, пока движок остановится.
  5. Если эта операция занимает более 5 секунд, система активирует ошибку ANR.

Трассировка стека

"main" tid=1 Timed Waiting
jdk.internal.misc.Unsafe.park (Native method)
java.util.concurrent.locks.LockSupport.parkNanos (LockSupport.java:234)
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedNanos (AbstractQueuedSynchronizer.java:1079)
java.util.concurrent.locks.AbstractQueuedSynchronizer.tryAcquireSharedNanos (AbstractQueuedSynchronizer.java:1369)
java.util.concurrent.Semaphore.tryAcquire (Semaphore.java:415)
com.unity3d.player.UnityPlayer.pauseUnity (UnityPlayer.java:833)
com.unity3d.player.UnityPlayer.pause (UnityPlayer.java:796)
com.unity3d.player.UnityPlayerActivity.onPause (UnityPlayerActivity.java:117)
android.app.Activity.performPause (Activity.java:8517)
android.app.Instrumentation.callActivityOnPause (Instrumentation.java:1618)
android.app.ActivityThread.performPauseActivityIfNeeded (ActivityThread.java:5061)
android.app.ActivityThread.performPauseActivity (ActivityThread.java:5022)
android.app.ActivityThread.handlePauseActivity (ActivityThread.java:4974)
android.app.servertransaction.PauseActivityItem.execute (PauseActivityItem.java:48)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:179)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:97)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2303)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:201)
android.os.Looper.loop (Looper.java:288)
android.app.ActivityThread.main (ActivityThread.java:7884)
java.lang.reflect.Method.invoke (Native method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:936)

Рисунок 3. ANR, вызванная семафором, который никогда не освобождается.

Решение

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

  • Профилируйте свою игру и проверьте, является ли OnApplicationPause дорогостоящей операцией. Можно использовать Stopwatch .
  • ��збегайте операций ввода-вывода или синхронных сетевых запросов.
  • Пер��н��с��те ��пераци�� в другой Thread с помощью Task . Unity 2023.1 поддерживает упрощенную модель асинхронного программирования с использованием ключевых слов C# async и await .

UnitySendMessage заблокирован

Плагины и SDK Java Unity отправляют данные в игровой слой C# с помощью JNI . Однако это взаимодействие может блокировать основной поток из-за встроенной процедуры синхронизации, например, мьютекса, что может привести к ошибке ANR из-за конфликта блокировок.

Трассировка стека

Ошибка ANR на рисунке 4 была вызвана длительной операцией в коде C#, вызванной плагином Java. Движок Unity использует мьютекс Non-Priority Inheritance для обеспечения корректного выполнения.

libc.so NonPI::MutexLockWithTimeout(pthread_mutex_internal_t*, bool, timespec const*) + 604
com.unity3d.player.UnityPlayer.nativeUnitySendMessage (Native method)
com.unity3d.player.UnityPlayer.UnitySendMessage (UnityPlayer.java:665)

Рисунок 4. Ошибка ANR, вызванная конфликтом блокировок.

Причина

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

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

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

Решение

Во время приостановки приложения убедитесь, что все действия в коде необходимы, или попробуйте сохранить состояние пользователя в локальной памяти устройства. И, конечно же, проверьте, сможете ли вы выполнить эти действия и вне периода приостановки.

Несколько подходов :

  • Переместите операцию C#, обрабатывающую сообщение , в поток, отличный от основного потока.
    • Если ваш код не зависит от контекста основного потока Unity, используйте для связи Task вместо message.
  • Не отправляйте несколько сообщений из плагина, когда игра приостановлена.
    • Движок не может отправлять сообщения, пока игра находится в фоновом режиме.
    • Отправляйте в игру последнее состояние данных только в том случае, если это не повлияет на функциональность игры.

Установить реферер

Реферер установки Play — это уникальная строка, отправляемая в Play Store каждый раз, когда пользователь нажимает на объявление. Это идентификатор отслеживания рекламы, специфичный для Android. После установки приложение отправляет реферер установки партнёру по атрибуции, который сопоставляет источник с установкой (атрибуция конверсии).

Трассировка стека

На рисунке 5 показана трассировка стека ANR из игры, которая использует Facebook SDK для получения атрибуции установки.

Рисунок 5. Отчет Android Vitals, содержащий вызов Binder.

Причина

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

Решение

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

Google предоставляет страницу индекса SDK , которая объединяет данные об использовании приложений Google Play с информацией, собранной с помощью обнаружения кода, чтобы предоставить атрибуты и сигналы, призванные помочь вам решить, следует ли внедр��ть, сохранят�� ��ли у��а��ять SDK из вашего приложения.

Дополнительные ресурсы

Чтобы узнать больше об ANR, обратитесь к следующим ресурсам:

  • Отладка ANR — разработка игр для Android
  • ANR — Качество приложения