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

Ошибки Unity возникают по разным причинам. Наиболее распространенные ошибки ANR вызваны неправильным использованием компонентов Android и Unity и их неправильным взаимодействием.

Веб-представление

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

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

Трассировка стека — это ваш первый способ понять причину ошибки 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 используется на устройстве, перейдите в «Настройки» > «Приложения» > «Android System WebView» и посмотрите версию внизу страницы.
Экран информации о приложении, показывающий версии 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 использует мьютекс неприоритетного наследования для обеспечения правильного выполнения.

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 для связи вместо сообщения.
  • Не отправляйте несколько сообщений из вашего плагина, когда игра приостановлена.
    • Движок не может отправлять сообщения, пока игра находится в фоновом режиме.
    • Отправляйте в игру последнее состояние данных только в том случае, если это не влияет на функциональность вашей игры.

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

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

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

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

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

Причина

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

Решение

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

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

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

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