ANR courants dans les jeux Unity

Les ANR Unity se produisent pour différentes raisons. La plupart des ANR sont causées par une mauvaise utilisation des composants Android et Unity, et par une mauvaise communication entre eux.

WebView

WebView est une classe Android qui affiche des pages Web. Les SDK tiers (comme les annonces) utilisent WebView pour afficher du contenu Web dynamique dans des activités autres que UnityPlayerActivity. Les ANR se produisent lorsque des SDK tiers utilisent WebView de manière abusive.

Trace de la pile

La trace de pile est votre premier recours pour comprendre la cause de l'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)

Figure 1 : Trace de la pile ANR causée par une attente futex.

Cause

Pour le moment, la cause première de ce problème n'est pas claire. Voici quelques causes potentielles :

  • Mauvaise implémentation des annonces.
  • Une version obsolète de WebView, car l'utilisateur peut avoir choisi de ne pas mettre à jour l'application automatiquement.
  • Utilisation élevée des ressources système (CPU, GPU, etc.), qui peut nécessiter beaucoup de profilage.
  • Les plantages de la compilation des nuanceurs, qui peuvent indiquer que le contenu comporte un nuanceur incompatible ou que l'utilisateur a installé une ancienne version de WebView.

Solution

  • Pour identifier le type de contenu qui provoque le blocage du thread principal par WebView, ajoutez des journaux à votre jeu chaque fois qu'une page Web est chargée, affichée ou fermée.
    • Vous pouvez utiliser les services de création de rapports Backtrace ou Crashlytics.
    • Ensuite, après avoir analysé les données et identifié le problème, essayez de désactiver les fournisseurs d'annonces concernés.
    • Incluez les journaux de mémoire pour vous assurer que le problème n'est pas lié à la mémoire.
  • Alertez l'utilisateur pour qu'il mette à jour WebView depuis Google Play. À partir d'Android 5.0 (niveau d'API 21) et versions ultérieures, WebView a été déplacé vers un APK. Il peut donc être mis à jour indépendamment de la plate-forme Android. Pour connaître la version de WebView utilisée sur un appareil, accédez à Paramètres > Applications > WebView du système Android, puis consultez la version en bas de la page.
Écran "Infos sur l'appli" affichant les versions de WebView.
Figure 1. Vérifiez la version de WebView.

Mise en pause Unity

Lorsque UnityPlayerActivity reçoit un appel onPause(), la chaîne d'opérations suivante démarre :

  1. UnityPlayerActivity indique au moteur d'exécution Unity que l'activité a été mise en pause.
  2. Unity appelle chaque MonoBehaviour qui implémente l'événement OnApplicationPause.
  3. Unity arrête ses composants et modules, tels que la lecture du son, le rendu, la boucle de jeu et l'animation.
  4. Pour s'assurer que Unity Android Player (UAP) et le moteur sont synchronisés, l'UAP attend quatre secondes que le moteur s'arrête.
  5. Si cette opération prend plus de cinq secondes, le système déclenche une erreur ANR.

Trace de la pile

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

Figure 3. ANR causée par un sémaphore qui n'est jamais libéré.

Solution

Assurez-vous que le code de votre jeu en C# ne met pas trop de temps à s'exécuter lors d'un événement de pause ou de reprise.

  • Profilez votre jeu et vérifiez si OnApplicationPause est une opération coûteuse. Vous pouvez utiliser un Stopwatch.
  • Évitez les opérations d'E/S ou les requêtes réseau synchrones.
  • Déplacez les opérations vers un autre Thread à l'aide de Task. Unity 2023.1 est compatible avec un modèle de programmation asynchrone simplifié utilisant les mots clés C# async et await.

UnitySendMessage bloqué

Les plug-ins et SDK Java Unity envoient des données à la couche de jeu C# à l'aide de JNI. Toutefois, cette communication peut bloquer le thread principal en raison d'une routine de synchronisation native telle qu'un mutex, ce qui provoque un ANR en raison d'un conflit de verrouillage.

Trace de la pile

L'ANR de la figure 4 a été causée par une opération de longue durée dans le code C# appelé par un plug-in Java. Le moteur Unity utilise un mutex sans héritage de priorité pour garantir une exécution correcte.

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)

Figure 4. Erreur ANR causée par un conflit de verrouillage.

Cause

Le problème est que plusieurs messages sont envoyés lorsque l'application est reprise. Les messages sont mis en file d'attente, car ils ne peuvent pas être envoyés lorsque le jeu est en arrière-plan. Les messages sont tous envoyés simultanément lorsque l'application reprend.

Pendant une période de pause, vous stockez généralement les informations de votre jeu sur le serveur. Par exemple, vous enregistrez la position d'un joueur dans le jeu afin qu'il puisse revenir au même endroit lorsque le jeu reprend.

Cette charge de travail, combinée à d'autres codes tiers créant leur propre charge de travail, peut surcharger les ressources de l'appareil, en particulier le thread principal. Le thread principal exécute l'interface utilisateur d'une application et est souvent le principal emplacement des erreurs ANR. Par conséquent, toute charge de travail ajoutée sur le thread principal augmente le risque d'ANR.

Solution

Lorsque vous mettez une application en pause, assurez-vous que toutes les actions de code sont nécessaires ou essayez d'enregistrer l'état de l'utilisateur dans la mémoire locale de votre appareil. Et bien sûr, vérifiez si vous pouvez également effectuer ces actions en dehors de la période de suspension.

Voici quelques approches :

  • Déplacez l'opération C# qui gère un message vers un thread autre que le thread principal.
    • Si votre code ne dépend pas du contexte du thread principal d'Unity, utilisez Task pour la communication au lieu du message.
  • N'envoyez pas plusieurs messages depuis votre plug-in lorsque le jeu est en pause.
    • Le moteur ne peut pas envoyer de messages lorsque le jeu est en arrière-plan.
    • N'envoyez le dernier état des données à votre jeu que si cela n'a pas d'incidence sur ses fonctionnalités.

Install Referrer

Play Install Referrer est une chaîne unique envoyée au Play Store chaque fois qu'un utilisateur clique sur une annonce. Il s'agit d'un identifiant de suivi des annonces spécifique à Android. Une fois l'application installée, elle envoie le referrer d'installation au partenaire d'attribution, qui fait correspondre la source à l'installation (en attribuant la conversion).

Trace de la pile

La figure 5 montre une trace de pile ANR provenant d'un jeu qui utilise le SDK Facebook pour récupérer l'attribution de l'installation.

Figure 5 : Rapport Android Vitals contenant un appel Binder.

Cause

L'erreur ANR a été causée par un appel de liaison lent. Toutefois, il est impossible de déterminer la cause première sans avoir accès au code source du SDK.

Solution

Pour résoudre ce type de problème, vous devez communiquer avec le développeur du SDK ou effectuer de nombreuses recherches en ligne pour trouver une solution potentielle, vérifier si une version plus récente du SDK résout l'ANR pour d'autres utilisateurs, ou même tester une stratégie de déploiement à petite échelle.

Google fournit une page SDK Index qui associe les données d'utilisation des applications Google Play aux informations recueillies via la détection de code afin de fournir des attributs et des signaux conçus pour vous aider à décider si vous voulez adopter, conserver ou supprimer un SDK au sein de votre application.

Ressources supplémentaires

Pour en savoir plus sur les ANR, consultez les ressources suivantes :