Häufige ANR-Fehler bei Unity-Spielen

Unity-ANR-Fehler können aus verschiedenen Gründen auftreten. Die häufigsten ANR-Fehler werden durch den Missbrauch von Android- und Unity-Komponenten und deren Missverständnisse verursacht.

WebView

WebView ist eine Android-Klasse, in der Webseiten angezeigt werden. Drittanbieter-SDKs (z. B. Anzeigen) verwenden WebView, um dynamische Webinhalte in anderen Aktivitäten als UnityPlayerActivity anzuzeigen. ANR-Fehler treten auf, wenn WebView von Drittanbieter-SDKs missbraucht wird.

Stacktrace

Der Stacktrace ist Ihre erste Möglichkeit, die Ursache des ANR-Fehlers zu verstehen.

/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 eine Futex-Wartezeit verursacht wird.

Ursache

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

  • Schlechte Anzeigenimplementierung
  • Eine veraltete Version von WebView, da der Nutzer sich möglicherweise dafür entschieden hat, die App nicht automatisch zu aktualisieren.
  • Hohe Nutzung von Systemressourcen (CPU, GPU usw.), die möglicherweise ein hohes Maß an Profilerstellung erfordert.
  • Die Shader-Kompilierung stürzt ab. Dies kann darauf hindeuten, dass der Inhalt einen inkompatiblen Shader hat oder der Nutzer eine alte WebView-Version installiert hat.

Die Lösung

  • Wenn Sie eingrenzen möchten, welche Inhaltstypen dazu führen, dass WebView den Hauptthread blockiert, fügen Sie Ihrem Spiel immer dann Logs hinzu, wenn eine Webseite geladen, angezeigt oder geschlossen wird.
    • Sie können die Berichtsdienste Backtrace oder Crashlytics verwenden.
    • Wenn Sie die Daten analysiert und das Problem gefunden haben, können Sie versuchen, die betroffenen Anzeigenanbieter zu deaktivieren.
    • Fügen Sie Arbeitsspeicherprotokolle hinzu, um sicherzustellen, dass das Problem nicht mit dem Arbeitsspeicher zusammenhängt.
  • Benachrichtigen Sie den Nutzer, die WebView in Google Play zu aktualisieren. WebView wurde von Android 5.0 (API-Level 21) und höher in ein APK verschoben. Daher kann es unabhängig von der Android-Plattform aktualisiert werden. Wenn du wissen möchtest, welche Version von WebView auf einem Gerät verwendet wird, gehe zu Einstellungen > Apps > Android-System-WebView und sieh dir unten auf der Seite die Version an.
App-Infobildschirm mit den WebView-Versionen
Abbildung 1. Prüfen Sie die WebView-Version.

Unity-Pause

Wenn UnityPlayerActivity einen onPause()-Aufruf empfängt, wird die folgende Vorgangskette gestartet:

  1. UnityPlayerActivity benachrichtigt die Unity-Laufzeit-Engine, dass die Aktivität angehalten wurde.
  2. Unity ruft alle MonoBehaviour auf, in denen das Ereignis OnApplicationPause implementiert ist.
  3. Unity stoppt seine Komponenten und Module wie die Tonwiedergabe, das Rendering, die Spielschleife und die Animation.
  4. Damit sowohl Unity Android Player (UAP) als auch die Suchmaschine synchronisiert werden, wartet das UAP 4 Sekunden, bis die Suchmaschine abgeschaltet wird.
  5. Wenn dieser Vorgang länger als 5 Sekunden dauert, löst das System einen ANR-Fehler 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-Fehler aufgrund einer nie veröffentlichten Semaphore.

Die Lösung

Achten Sie darauf, dass die Ausführung des C#-Spielcodes während eines Pausen- oder Fortsetzungsereignisses nicht zu lange dauert.

  • Erstellen Sie ein Profil für Ihr Spiel und prüfen Sie, ob OnApplicationPause ein teurer Vorgang ist. Sie können einen Stopwatch verwenden.
  • Vermeiden Sie E/A-Vorgänge oder synchrone Netzwerkanfragen.
  • Verschieben Sie die Vorgänge mithilfe von Task in ein anderes Thread. Unity 2023.1 unterstützt ein vereinfachtes asynchrones Programmiermodell mit den C#-Keywords async und await.

UnitySendMessage blockiert

Java Unity-Plug-ins und -SDKs senden Daten mithilfe von JNI an die C#-Spieleebene. Diese Kommunikation kann jedoch den Hauptthread aufgrund einer nativen Synchronisierungsroutine wie einem Mutex blockieren, was zu einem ANR-Fehler aufgrund von Sperrkonflikten führt.

Stacktrace

Der ANR-Fehler in Abbildung 4 wurde durch einen langen Vorgang im C#-Code verursacht, der von einem Java-Plug-in aufgerufen wird. Die Unity-Engine verwendet einen Vererbungs-Mutex ohne Priorität, 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-Fehler aufgrund eines Sperrkonflikts.

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 Anwendung fortgesetzt wird.

Während einer Pause speichern Sie in der Regel die Informationen zu Ihrem Spiel auf dem Server. Zum Beispiel erfassen Sie die Position eines Spielers im Spiel, damit der Spieler wieder dorthin zurückkehren kann, wenn das Spiel fortgesetzt wird.

Diese Arbeitslast kann in Kombination mit anderem Drittanbietercode, der eine eigene Arbeitslast erstellt, die Ressourcen des Geräts, insbesondere den Hauptthread, überlasten. Der Hauptthread führt die Benutzeroberfläche einer Anwendung aus und ist oft der Hauptort von ANR-Fehlern. Jede zusätzliche Arbeitslast im Hauptthread erhöht also das Risiko eines ANR-Fehlers.

Die Lösung

Prüfe, ob während einer App-Pause alle Codeaktionen erforderlich sind, oder versuche, den Status des Nutzers im lokalen Gerätespeicher zu speichern. Und natürlich prüfen Sie, ob Sie diese Aktionen auch 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 Kontext des Hauptthreads von Unity abhängt, verwenden Sie Task für die Kommunikation anstelle von Nachrichten.
  • Senden Sie nicht mehrere Nachrichten über Ihr Plug-in, wenn das Spiel pausiert ist.
    • Während das Spiel im Hintergrund läuft, kann die Engine keine Nachrichten senden.
    • Senden Sie den letzten Datenstatus nur dann an Ihr Spiel, wenn dies keine Auswirkungen auf die Funktionalität Ihres Spiels hat.

Verweis-URL installieren

Die Play Install Referrer ist ein eindeutiger String, der jedes Mal an den Play Store gesendet wird, wenn ein Nutzer auf eine Anzeige klickt. Es ist eine Android-spezifische ID für das Anzeigen-Tracking. Nach der Installation sendet die App die Installationsreferenz an den Attributionspartner, der die Quelle mit der Installation (und der Conversion zuordnet) gleicht.

Stacktrace

Abbildung 5 zeigt einen ANR-Stacktrace eines Spiels, das das Facebook SDK zum Abrufen der Attribution der Installationen verwendet.

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

Ursache

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

Die Lösung

Um diese Art von Problem zu lösen, müssen Sie mit dem SDK-Entwickler kommunizieren oder online nach einer potenziellen Lösung suchen, prüfen, ob eine neuere Version des SDK den ANR-Fehler für andere behebt, oder sogar mit einer kleinen Rollout-Strategie experimentieren.

Google bietet eine SDK-Index-Seite, auf der Nutzungsdaten aus Google Play-Apps mit Informationen kombiniert werden, die durch die Codeerkennung erfasst wurden. Sie enthalten Attribute und Signale, die Ihnen bei der Entscheidung helfen, ob Sie ein SDK verwenden, behalten oder aus Ihrer App entfernen möchten.

Zusätzliche Ressourcen

Weitere Informationen zu ANR-Fehlern finden Sie in den folgenden Ressourcen: