Errores de ANR comunes de los juegos de Unity

Los errores ANR de Unity ocurren por varios motivos. Los errores de ANR más comunes se deben al uso inadecuado de los componentes de Android y Unity, y a la falta de comunicación entre ellos.

WebView

WebView es una clase de Android que muestra páginas web. Los SDKs de terceros (como los de anuncios) usan WebView para mostrar contenido web dinámico en actividades que no sean UnityPlayerActivity. Los errores de ANR se producen cuando los SDKs de terceros usan de forma incorrecta WebView.

Seguimiento de pila

El seguimiento de pila es el primer recurso que tienes para comprender la causa del error 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)

Figura 1: Seguimiento de pila de ANR causado por una espera de futex

Causa

Hasta el momento, no está clara la causa raíz de este problema. Estas son algunas causas posibles:

  • Implementación de anuncios inapropiada
  • Una versión desactualizada de WebView, ya que es posible que el usuario haya optado por no actualizar la app automáticamente.
  • Uso alto de recursos del sistema (CPU, GPU, etcétera), lo que puede requerir una gran cantidad de generación de perfiles
  • Se producen fallas en la compilación de sombreadores, lo que podría indicar que el contenido tiene un sombreador incompatible o que el usuario tiene instalada una versión anterior de WebView.

Solución

  • Para determinar qué tipo de contenido está provocando que WebView bloquee el subproceso principal, agrega registros a tu juego cada vez que se cargue, muestre o cierre una página web.
    • Puedes usar los servicios de informes de Backtrace o Crashlytics.
    • Luego, después de analizar los datos y encontrar el problema, intenta inhabilitar los proveedores de anuncios problemáticos.
    • Incluye registros de memoria para asegurarte de que el problema no esté relacionado con la memoria.
  • Alerta al usuario para que actualice WebView desde Google Play. A partir de Android 5.0 (nivel de API 21) y versiones posteriores, WebView se trasladó a un APK. Por lo tanto, se puede actualizar por separado de la plataforma de Android. Para ver qué versión de WebView se usa en un dispositivo, ve a Configuración > Apps > Android System WebView y busca la versión en la parte inferior de la página.
Pantalla de información de la app que muestra las versiones de WebView.
Figura 1. Verifica la versión de WebView.

Pausa de Unity

Cuando UnityPlayerActivity recibe una llamada a onPause(), se inicia la siguiente cadena de operaciones:

  1. UnityPlayerActivity notifica al motor de tiempo de ejecución de Unity que la actividad se pausó.
  2. Unity llama a cada MonoBehaviour que implementa el evento OnApplicationPause.
  3. Unity detiene sus componentes y módulos, como la reproducción de sonido, la renderización, el bucle de juego y la animación.
  4. Para asegurarse de que tanto Unity Android Player (UAP) como el motor estén sincronizados, el UAP espera 4 segundos a que se detenga el motor.
  5. Si esa operación tarda más de 5 segundos, el sistema activa un ANR.

Seguimiento de pila

"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)

Figura 3: La ANR se debe a un semáforo que nunca se libera.

Solución

Asegúrate de que el código del juego en C# no tarde demasiado en finalizar la ejecución durante un evento de pausa o reanudación.

  • Crea un perfil de tu juego y verifica si OnApplicationPause es una operación costosa. Puedes usar un objeto Stopwatch.
  • Evita las operaciones de E/S o las solicitudes de red síncronas.
  • Mueve las operaciones a otro Thread con Task. Unity 2023.1 admite un modelo de programación asíncrona simplificado con las palabras clave async y await de C#.

Se bloqueó UnitySendMessage

Los complementos y SDKs de Java Unity envían datos a la capa de juego de C# con JNI. Sin embargo, esta comunicación podría bloquear el subproceso principal debido a una rutina de sincronización nativa, como una exclusión mutua, lo que provocaría un error de ANR debido a la contención de bloqueo.

Seguimiento de pila

El ANR de la figura 4 se debió a una operación prolongada en el código C# llamado por un complemento de Java. El motor de Unity usa un mutex de herencia sin prioridad para garantizar una ejecución correcta.

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)

Figura 4 ANR causada por una contención de bloqueo.

Causa

El problema es que se envían varios mensajes cuando se reanuda la aplicación. Los mensajes se ponen en cola porque no se pueden enviar mientras el juego se ejecuta en segundo plano. Todos los mensajes se envían simultáneamente cuando se reanuda la app.

Durante un período de pausa, generalmente almacenas la información del juego en el servidor. Por ejemplo, registras la posición de un jugador en el juego para que pueda volver al mismo lugar cuando se reanude.

Esta carga de trabajo, combinada con otro código de terceros que crea su propia carga de trabajo, puede sobrecargar los recursos del dispositivo, en particular el subproceso principal. El subproceso principal ejecuta la interfaz de usuario de una app y, a menudo, es la ubicación principal de los errores de ANR. Por lo tanto, cualquier carga de trabajo adicional en el subproceso principal aumenta la posibilidad de que se produzca un error de ANR.

Solución

Durante una pausa de la aplicación, asegúrate de que todas las acciones de código sean necesarias o intenta guardar el estado del usuario en la memoria local del dispositivo. Y, por supuesto, ver si también puedes completar estas acciones fuera del período de pausa.

Algunos enfoques:

  • Mueve la operación en C# que controla un mensaje a un subproceso que no sea el principal.
    • Si tu código no depende del contexto del subproceso principal de Unity, usa Task para la comunicación en lugar de un mensaje.
  • No envíes varios mensajes desde tu complemento cuando el juego esté en pausa.
    • El motor no puede enviar mensajes mientras el juego se ejecuta en segundo plano.
    • Solo envía el último estado de los datos a tu juego si eso no afecta su funcionalidad.

Referente de instalación

Play Install Referrer es una cadena única que se envía a Play Store cada vez que un usuario hace clic en un anuncio. Es un identificador de seguimiento de anuncios específico para Android. Una vez instalada, la app envía el referente de instalación al socio de atribución, que correlaciona la fuente con la instalación (atribuyendo la conversión).

Seguimiento de pila

En la figura 5, se muestra un registro de seguimiento de la pila de un ANR de un juego que usa el SDK de Facebook para recuperar la atribución de la instalación.

Figura 5: Informe de Android Vitals que contiene una llamada de Binder.

Causa

El error de ANR se debió a una llamada a Binder lenta. Sin embargo, no se puede determinar la causa raíz sin acceso al código fuente del SDK.

Solución

Resolver este tipo de problemas implica comunicarse con el desarrollador del SDK o buscar mucho en línea una posible solución, verificar si una versión más reciente del SDK resuelve el error de ANR para otros o incluso experimentar con una pequeña estrategia de lanzamiento.

Google proporciona una página del Índice SDK que combina los datos de uso de las apps de Google Play con la información recopilada a través de la detección de código para proporcionar indicadores y atributos diseñados con el objetivo de ayudarte a decidir entre adoptar, mantener o quitar un SDK de tu app.

Recursos adicionales

Para obtener más información sobre los ANR, consulta los siguientes recursos: