Typowe błędy ANR w grach Unity

Błędy ANR w Unity mogą występować z różnych powodów. Najczęstsze błędy ANR są spowodowane nieprawidłowym użyciem komponentów Androida i Unity oraz ich nieprawidłową komunikacją.

WebView | komponent WebView

WebView to klasa Androida, która wyświetla strony internetowe. Pakiety SDK innych firm (np. do reklam) używają WebView do wyświetlania dynamicznych treści internetowych w aktywnościach innych niż UnityPlayerActivity. Błędy ANR występują, gdy zewnętrzne pakiety SDK nieprawidłowo używają WebView.

Zrzut stosu

Ślad stosu to pierwsze miejsce, w którym możesz szukać przyczyny błędu 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)

Ilustracja 1. Zrzut stosu błędu ANR spowodowanego oczekiwaniem na futex.

Przyczyna

Główna przyczyna tego problemu jest na razie nieznana. Oto niektóre możliwe przyczyny:

  • Nieprawidłowe wdrożenie reklamy.
  • nieaktualną wersję WebView, ponieważ użytkownik mógł zrezygnować z automatycznego aktualizowania aplikacji;
  • Wysokie wykorzystanie zasobów systemowych (procesora, GPU itp.), które może wymagać wielu profili.
  • Błędy kompilacji shadera, które mogą wskazywać, że treść zawiera niezgodny shader lub że użytkownik ma zainstalowaną starą wersję WebView.

Rozwiązanie

  • Aby zawęzić zakres typów treści, które powodują blokowanie wątku głównego przez WebView, dodaj do gry logi za każdym razem, gdy strona internetowa jest wczytywana, wyświetlana lub zamykana.
    • Możesz używać usług raportowania Backtrace lub Crashlytics.
    • Następnie po przeanalizowaniu danych i znalezieniu problemu spróbuj wyłączyć dostawców reklam, którzy go powodują.
    • Dołącz dzienniki pamięci, aby mieć pewność, że problem nie jest związany z pamięcią.
  • Poinformuj użytkownika, że musi zaktualizować WebView w Google Play. Od Androida 5.0 (poziom interfejsu API 21) i nowszych WebView zostało przeniesione do pliku APK. Dlatego można ją aktualizować niezależnie od platformy Android. Aby sprawdzić, która wersja WebViewjest używana na urządzeniu, otwórz Ustawienia > Aplikacje > System Android WebView i sprawdź wersję u dołu strony.
Ekran informacji o aplikacji z wersjami komponentu WebView.
Rysunek 1. Sprawdź wersję WebView.

Wstrzymanie Unity

Gdy UnityPlayerActivity odbierze połączenie onPause(), rozpoczyna się następujący ciąg operacji:

  1. UnityPlayerActivity informuje środowisko wykonawcze Unity, że aktywność została wstrzymana.
  2. Unity wywołuje każdy obiekt MonoBehaviour, który implementuje zdarzenie OnApplicationPause.
  3. Unity zatrzymuje swoje komponenty i moduły, takie jak odtwarzanie dźwięku, renderowanie, pętla gry i animacja.
  4. Aby mieć pewność, że zarówno Unity Android Player (UAP), jak i silnik są zsynchronizowane, UAP czeka 4 sekundy, aż silnik się zatrzyma.
  5. Jeśli ta operacja trwa dłużej niż 5 sekund, system wywołuje błąd ANR.

Zrzut stosu

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

Rysunek 3. Błąd ANR spowodowany przez semafor, który nigdy nie został zwolniony.

Rozwiązanie

Upewnij się, że kod gry w C# nie wykonuje się zbyt długo podczas zdarzenia wstrzymania lub wznowienia.

  • Sprawdź profil swojej gry i zobacz, czy OnApplicationPause jest kosztowną operacją. Możesz użyć Stopwatch.
  • Unikaj operacji wejścia/wyjścia i synchronicznych żądań sieciowych.
  • Przenieś operacje do innego Thread za pomocą ikony Task. Unity 2023.1 obsługuje uproszczony model programowania asynchronicznego z użyciem słów kluczowych C# asyncawait.

Funkcja UnitySendMessage zablokowana

Wtyczki i pakiety SDK Java Unity wysyłają dane do warstwy gry C# za pomocą JNI. Jednak ta komunikacja może blokować wątek główny z powodu procedury synchronizacji natywnej, takiej jak muteks, co powoduje błąd ANR z powodu konfliktu blokad.

Zrzut stosu

Błąd ANR na rysunku 4 został spowodowany przez długą operację w kodzie C# wywoływanym przez wtyczkę Java. Silnik Unity używa muteksu dziedziczenia bez priorytetu, aby zapewnić prawidłowe wykonanie.

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)

Rysunek 4. Błąd ANR spowodowany przez rywalizację o blokadę.

Przyczyna

Problem polega na tym, że po wznowieniu działania aplikacji wysyłanych jest kilka wiadomości. Wiadomości są umieszczane w kolejce, ponieważ nie można ich wysłać, gdy gra jest w tle. Wszystkie wiadomości są wysyłane jednocześnie po wznowieniu działania aplikacji.

Podczas przerwy zwykle przechowujesz informacje o grze na serwerze. Na przykład zapisujesz pozycję gracza w grze, aby mógł on wrócić do tego samego miejsca po wznowieniu rozgrywki.

To obciążenie w połączeniu z innym kodem zewnętrznym, który tworzy własne obciążenie, może przeciążyć zasoby urządzenia, zwłaszcza wątek główny. Główny wątek obsługuje interfejs użytkownika aplikacji i jest często głównym źródłem błędów ANR. Dlatego każde dodatkowe obciążenie wątku głównego zwiększa ryzyko wystąpienia błędu ANR.

Rozwiązanie

Podczas wstrzymania aplikacji upewnij się, że wszystkie działania kodu są niezbędne, lub spróbuj zapisać stan użytkownika w pamięci lokalnej urządzenia. Sprawdź też, czy możesz wykonać te działania poza okresem wstrzymania.

Kilka podejść:

  • Przenieś operację w języku C#, która obsługuje wiadomość, do wątku innego niż wątek główny.
    • Jeśli Twój kod nie zależy od kontekstu głównego wątku Unity, do komunikacji używaj Task zamiast wiadomości.
  • Nie wysyłaj wielu wiadomości z wtyczki, gdy gra jest wstrzymana.
    • Gdy gra działa w tle, silnik nie może wysyłać wiadomości.
    • Wysyłaj do gry tylko ostatni stan danych, jeśli nie ma to wpływu na jej działanie.

Strona odsyłająca instalację

Play Install Referrer to unikalny ciąg znaków wysyłany do Sklepu Play za każdym razem, gdy użytkownik kliknie reklamę. Jest to identyfikator śledzenia reklam na Androidzie. Po zainstalowaniu aplikacja wysyła do partnera atrybucji informację o źródle instalacji, która jest dopasowywana do instalacji (przypisanie konwersji).

Zrzut stosu

Ilustracja 5 przedstawia ślad stosu błędu ANR z gry, która używa pakietu SDK Facebooka do pobierania atrybucji instalacji.

Rysunek 5. Raport Android Vitals zawierający wywołanie Binder.

Przyczyna

Błąd ANR został spowodowany przez powolne wywołanie Binder. Nie można jednak określić głównej przyczyny bez dostępu do kodu źródłowego pakietu SDK.

Rozwiązanie

Rozwiązanie tego typu problemu wymaga kontaktu z deweloperem pakietu SDK lub intensywnego wyszukiwania w internecie potencjalnego rozwiązania, sprawdzania, czy nowsza wersja pakietu SDK rozwiązuje problem z błędem ANR u innych użytkowników, a nawet eksperymentowania z strategią stopniowego wdrażania.

Google udostępnia stronę SDK Index, która łączy dane o wykorzystaniu pochodzące z aplikacji w Google Play oraz informacje zebrane za pomocą wykrywania kodu. Tworzy w ten sposób atrybuty i sygnały ułatwiające podjęcie decyzji, czy warto wdrożyć pakiet SDK, zachować go czy usunąć z aplikacji.

Dodatkowe materiały

Więcej informacji o błędach ANR znajdziesz w tych materiałach: