Häufige ANR-Fehler bei Unity-Spielen

ANRs in Unity können verschiedene Ursachen haben. Die häufigsten ANRs werden durch die falsche Verwendung von Android- und Unity-Komponenten und deren falsche Kommunikation verursacht.

WebView

WebView ist eine Android-Klasse, mit der Webseiten angezeigt werden. Drittanbieter-SDKs (z. B. für Anzeigen) verwenden WebView, um dynamische Webinhalte in anderen Aktivitäten als der UnityPlayerActivity anzuzeigen. ANRs treten auf, wenn Drittanbieter-SDKs WebView missbrauchen.

Stacktrace

Der Stacktrace ist Ihre erste Anlaufstelle, um die Ursache des ANR-Fehlers zu ermitteln.

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

Abbildung 1. ANR-Stacktrace, der durch einen futex-Wartevorgang verursacht wurde.

Ursache

Bisher ist die Ursache dieses Problems unklar. Mögliche Ursachen:

  • Schlechte Anzeigenimplementierung
  • Eine veraltete Version von WebView, da der Nutzer die automatische Aktualisierung der App möglicherweise deaktiviert hat.
  • Hohe Nutzung von Systemressourcen (CPU, GPU usw.), die möglicherweise viel Profiling erfordert.
  • Shader-Kompilierung stürzt ab. Das kann darauf hindeuten, dass der Inhalt einen inkompatiblen Shader enthält oder dass der Nutzer eine alte WebView-Version installiert hat.

Lösung

  • Um herauszufinden, welche Art von Inhalten dazu führt, dass WebView den Hauptthread blockiert, fügen Sie Ihrem Spiel Logs hinzu, wenn eine Webseite geladen, angezeigt oder geschlossen wird.
    • Sie können die Berichterstellungsdienste Backtrace oder Crashlytics verwenden.
    • Nachdem Sie die Daten analysiert und das Problem gefunden haben, deaktivieren Sie die betreffenden Anzeigenanbieter.
    • Fügen Sie Arbeitsspeicherprotokolle hinzu, um sicherzustellen, dass das Problem nicht mit dem Arbeitsspeicher zusammenhängt.
  • Den Nutzer auffordern, die WebView über Google Play zu aktualisieren. Ab Android 5.0 (API-Level 21) ist WebView in ein APK verschoben worden. Daher kann sie unabhängig von der Android-Plattform aktualisiert werden. Wenn Sie sehen möchten, welche Version von WebView auf einem Gerät verwendet wird, rufen Sie Einstellungen > Apps > Android System WebView auf und sehen Sie sich die Version unten auf der Seite an.
Der Bildschirm „App-Info“ mit den WebView-Versionen.
Abbildung 1. Prüfen Sie die WebView-Version.

Unity-Pause

Wenn UnityPlayerActivity einen onPause()-Aufruf empfängt, wird die folgende Kette von Vorgängen gestartet:

  1. UnityPlayerActivity benachrichtigt die Unity-Laufzeit-Engine, dass die Aktivität pausiert wurde.
  2. Unity ruft jedes MonoBehaviour auf, das das Ereignis OnApplicationPause implementiert.
  3. Unity stoppt seine Komponenten und Module, z. B. die Soundwiedergabe, das Rendern, die Spielschleife und die Animation.
  4. Damit sowohl Unity Android Player (UAP) als auch die Engine synchronisiert werden, wartet das UAP 4 Sekunden, bis die Engine stoppt.
  5. Wenn dieser Vorgang länger als 5 Sekunden dauert, löst das System einen ANR aus.

Stacktrace

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

Abbildung 3: ANR, der durch ein Semaphor verursacht wird, das nie freigegeben wird.

Lösung

Achten Sie darauf, dass die Ausführung Ihres C#-Spielcodes bei einem Pausen- oder Fortsetzungsereignis nicht zu lange dauert.

  • Profilieren Sie Ihr Spiel und prüfen Sie, ob OnApplicationPause ein ressourcenintensiver Vorgang ist. Sie können einen Stopwatch verwenden.
  • Vermeiden Sie E/A-Vorgänge oder synchrone Netzwerkanfragen.
  • Verschieben Sie die Vorgänge mit Task in eine andere Thread. Unity 2023.1 unterstützt ein vereinfachtes asynchrones Programmiermodell mit den C#-Schlüsselwörtern async und await.

„UnitySendMessage“ blockiert

Java-Unity-Plug-ins und ‑SDKs senden Daten über JNI an die C#-Spielebene. Diese Kommunikation kann jedoch den Hauptthread aufgrund einer nativen Synchronisierungsroutine wie einem Mutex blockieren und so einen ANR-Fehler aufgrund von Sperrenkonflikten verursachen.

Stacktrace

Die ANR in Abbildung 4 wurde durch einen langen Vorgang im C#-Code verursacht, der von einem Java-Plug-in aufgerufen wurde. Die Unity-Engine verwendet einen Mutex ohne Prioritätsvererbung, um die korrekte Ausführung zu gewährleisten.

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)

Abbildung 4: ANR verursacht durch einen Konflikt um eine Sperre.

Ursache

Das Problem besteht darin, dass beim Fortsetzen der Anwendung mehrere Nachrichten gesendet werden. Die Nachrichten werden in die Warteschlange gestellt, da sie nicht gesendet werden können, während das Spiel im Hintergrund läuft. Die Nachrichten werden alle gleichzeitig gesendet, wenn die App fortgesetzt wird.

Während einer Pause werden die Informationen Ihres Spiels in der Regel auf dem Server gespeichert. So wird beispielsweise die Position eines Spielers im Spiel aufgezeichnet, damit er nach der Fortsetzung des Spiels an dieselbe Stelle zurückkehren kann.

Diese Arbeitslast kann in Kombination mit anderem Drittanbietercode, der eine eigene Arbeitslast erstellt, die Ressourcen des Geräts überlasten, insbesondere den Hauptthread. Im Hauptthread wird die Benutzeroberfläche einer App ausgeführt. ANR-Fehler treten häufig dort auf. Jede zusätzliche Arbeitslast im Hauptthread erhöht also das Risiko für einen ANR.

Lösung

Achten Sie während einer App-Pause darauf, dass alle Ihre Codeaktionen erforderlich sind, oder versuchen Sie, den Status des Nutzers im lokalen Gerätespeicher zu speichern. Prüfen Sie natürlich auch, ob Sie diese Aktionen außerhalb des Pausenzeitraums ausführen können.

Einige Ansätze:

  • Verschieben Sie den C#-Vorgang, der eine Nachricht verarbeitet, in einen anderen Thread als den Hauptthread.
    • Wenn Ihr Code nicht vom Hauptthread-Kontext von Unity abhängt, verwenden Sie Task anstelle von „message“ für die Kommunikation.
  • Senden Sie nicht mehrere Nachrichten von Ihrem Plug-in, wenn das Spiel pausiert ist.
    • Die Engine kann keine Nachrichten senden, während das Spiel im Hintergrund läuft.
    • Senden Sie nur den letzten Datenstatus an Ihr Spiel, wenn dies die Spielfunktionalität nicht beeinträchtigt.

Install Referrer

Play Install Referrer ist ein eindeutiger String, der an den Play Store gesendet wird, wenn ein Nutzer auf eine Anzeige klickt. Es handelt sich um eine Android-spezifische Kennung für das Anzeigen-Tracking. Nach der Installation sendet die App den Installations-Referrer an den Attributionspartner, der die Quelle mit der Installation abgleicht und die Conversion zuordnet.

Stacktrace

Abbildung 5 zeigt einen ANR-Stacktrace aus einem Spiel, in dem das Facebook-SDK verwendet wird, um die Installationszuordnung abzurufen.

Abbildung 5. Android Vitals-Bericht mit einem Binder-Aufruf.

Ursache

Der ANR wurde durch einen langsamen Binder-Aufruf verursacht. Die Ursache kann jedoch nicht ohne Zugriff auf den SDK-Quellcode ermittelt werden.

Lösung

Um diese Art von Problem zu beheben, müssen Sie sich mit dem SDK-Entwickler in Verbindung setzen oder im Internet nach einer möglichen Lösung suchen. Außerdem sollten Sie prüfen, ob eine neuere Version des SDKs den ANR-Fehler für andere behebt, oder sogar eine kleine Einführung durchführen.

Google bietet eine SDK Index-Seite, auf der Nutzungsdaten aus Google Play-Apps mit per Codeerkennung erfassten Informationen kombiniert werden. So werden Ihnen Attribute und Signale geboten, mit deren Hilfe Sie entscheiden können, ob Sie ein SDK einführen, behalten oder aus Ihrer App entfernen möchten.

Zusätzliche Ressourcen

Weitere Informationen zu ANRs finden Sie in den folgenden Ressourcen: