Unity Oyunlarında Yaygın ANR'ler

Unity ANR'leri çeşitli nedenlerle oluşur. En yaygın ANR'ler, Android ve Unity bileşenlerinin yanlış kullanımından ve aralarındaki yanlış iletişimden kaynaklanır.

Web Görünümü

WebView, web sayfalarını görüntüleyen bir Android sınıfıdır. Üçüncü taraf SDK'ları (ör. reklamlar), WebView kullanarak UnityPlayerActivity dışındaki etkinliklerde dinamik web içeriği gösterir. ANR'ler, üçüncü taraf SDK'ları WebView öğesini yanlış kullandığında meydana gelir.

Yığın izi

ANR'nin nedenini anlamak için ilk başvuracağınız yer yığın izidir.

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

Şekil 1. Futex beklemesinden kaynaklanan ANR yığın izlemesi.

Neden

Şu ana kadar bu sorunun temel nedeni netleşmemiştir. Olası nedenlerden bazıları şunlardır:

  • Kötü reklam uygulaması.
  • Kullanıcı, uygulamayı otomatik olarak güncellememeyi tercih etmiş olabileceğinden WebView'nın eski bir sürümü
  • Sistem kaynaklarının (CPU, GPU vb.) yüksek kullanımı, bu da çok fazla profil oluşturmayı gerektirebilir.
  • Shader derlemesi kilitleniyor. Bu durum, içerikte uyumsuz bir shader olduğu veya kullanıcının eski bir WebView sürümü yüklü olduğu anlamına gelebilir.

Çözüm

  • WebView'nın ana iş parçacığını engellemesine hangi içerik türünün neden olduğunu daraltmak için bir web sayfası her yüklendiğinde, görüntülendiğinde veya kapatıldığında oyununuza günlükler ekleyin.
    • Backtrace veya Crashlytics raporlama hizmetlerini kullanabilirsiniz.
    • Ardından, verileri analiz edip sorunu bulduktan sonra sorunlu reklam sağlayıcıları devre dışı bırakmayı deneyin.
    • Sorunun bellekle ilgili olmadığından emin olmak için bellek günlüklerini ekleyin.
  • Kullanıcıyı Google Play'den WebView uygulamasını güncellemesi konusunda uyarın. Android 5.0 (API düzeyi 21) ve sonraki sürümlerde WebView, APK'ya taşındı. Bu nedenle, Android platformundan ayrı olarak güncellenebilir. Bir cihazda hangi WebView sürümünün kullanıldığını görmek için Ayarlar > Uygulamalar > Android System WebView'a gidin ve sayfanın alt kısmındaki sürüme bakın.
WebView sürümlerinin gösterildiği uygulama bilgileri ekranı.
1. Şekil. WebView sürümünü kontrol edin.

Unity'de duraklatma

UnityPlayerActivity, onPause() araması aldığında aşağıdaki işlem zinciri başlar:

  1. UnityPlayerActivity, etkinliğin duraklatıldığını Unity çalışma zamanı motoruna bildirir.
  2. Unity, MonoBehaviour etkinliğini uygulayan her OnApplicationPause öğesini çağırır.
  3. Unity, ses oynatma, oluşturma, oyun döngüsü ve animasyon gibi bileşenlerini ve modüllerini durdurur.
  4. Hem Unity Android Player (UAP) hem de motorun senkronize olduğundan emin olmak için UAP, motorun durmasını 4 saniye bekler.
  5. Bu işlem 5 saniyeden uzun sürerse sistem ANR'yi tetikler.

Yığın izi

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

3.Şekil Hiç serbest bırakılmayan bir semaforun neden olduğu ANR.

Çözüm

C# oyun kodunuzun, duraklatma veya devam ettirme etkinliği sırasında yürütmeyi tamamlamasının çok uzun sürmediğinden emin olun.

  • Oyununuzun profilini oluşturun ve OnApplicationPause işleminin maliyetli olup olmadığını kontrol edin. Stopwatch kullanabilirsiniz.
  • G/Ç işlemlerinden veya senkron ağ isteklerinden kaçının.
  • Task simgesini kullanarak işlemleri başka bir Thread'ye taşıyın. Unity 2023.1, C# async ve await anahtar kelimelerini kullanarak basitleştirilmiş bir eşzamansız programlama modelini destekler.

UnitySendMessage engellendi

Java Unity eklentileri ve SDK'ları, JNI kullanarak C# oyun katmanına veri gönderir. Ancak bu iletişim, mutex gibi yerel bir senkronizasyon rutini nedeniyle ana iş parçacığını engelleyebilir ve kilit çekişmesi nedeniyle ANR'ye neden olabilir.

Yığın izi

Şekil 4'teki ANR, bir Java eklentisi tarafından çağrılan C# kodundaki uzun bir işlemden kaynaklanmıştır. Unity motoru, doğru yürütmeyi sağlamak için Non-Priority Inheritance mutex kullanır.

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)

Şekil 4. Kilit çekişmesinden kaynaklanan ANR.

Neden

Sorun, uygulama devam ettirildiğinde birden fazla mesaj gönderilmesidir. Oyun arka plandayken gönderilemedikleri için mesajlar sıraya alınır. Uygulama devam ettirildiğinde tüm mesajlar aynı anda gönderilir.

Duraklatma döneminde genellikle oyununuzun bilgilerini sunucuda saklarsınız. Örneğin, oyunun devam etmesi durumunda oyuncunun aynı yere dönebilmesi için oyuncunun oyundaki konumunu kaydedersiniz.

Bu iş yükü, kendi iş yükünü oluşturan diğer üçüncü taraf kodlarıyla birlikte cihazın kaynaklarını, özellikle de ana iş parçacığını aşırı yükleyebilir. Ana iş parçacığı, uygulamanın kullanıcı arayüzünü çalıştırır ve genellikle ANR'lerin ana konumudur. Bu nedenle, ana iş parçacığına eklenen her iş yükü ANR olasılığını artırır.

Çözüm

Uygulama duraklatıldığında tüm kod işlemlerinizin gerekli olduğundan emin olun veya kullanıcının durumunu yerel cihaz belleğinize kaydetmeyi deneyin. Ayrıca, bu işlemleri duraklatma dönemi dışında da tamamlayıp tamamlayamayacağınızı kontrol edin.

Birkaç yaklaşım:

  • Mesajı işleyen C# işlemini ana iş parçacığı dışındaki bir iş parçacığına taşıyın.
    • Kodunuz Unity'nin ana iş parçacığı bağlamına bağlı değilse iletişim için mesaj yerine Task kullanın.
  • Oyun duraklatıldığında eklentinizden birden fazla mesaj göndermeyin.
    • Oyun arka plandayken motor mesaj gönderemez.
    • Yalnızca oyun işlevselliğinizi etkilemiyorsa son veri durumunu oyununuza gönderin.

Yükleme yönlendireni

Play Install Referrer, bir kullanıcı bir reklamı her tıkladığında Play Store'a gönderilen benzersiz bir dizedir. Android'e özgü bir reklam izleme tanımlayıcısıdır. Yüklendikten sonra uygulama, yükleme yönlendireni ilişkilendirme iş ortağına gönderir. Bu iş ortağı, kaynağı yüklemeyle eşleştirir (dönüşümü ilişkilendirir).

Yığın izi

Şekil 5'te, yükleme ilişkilendirmesini almak için Facebook SDK'sını kullanan bir oyundan alınan ANR yığın izi gösterilmektedir.

5. şekil. Binder çağrısı içeren Android Vitals raporu.

Neden

ANR, yavaş bir bağlayıcı çağrısından kaynaklanıyordu. Ancak SDK kaynak koduna erişim olmadan temel neden belirlenemez.

Çözüm

Bu tür sorunları çözmek için SDK geliştiricisiyle iletişim kurmak veya olası bir çözüm için internette çok fazla arama yapmak, SDK'nın daha yeni bir sürümünün ANR'yi diğer kullanıcılar için çözüp çözmediğini kontrol etmek, hatta küçük bir dağıtım stratejisiyle denemeler yapmak gerekir.

Google, Google Play uygulamalarındaki kullanım verilerini kod algılama yoluyla toplanan bilgilerle birleştirerek SDK Dizini sayfası sunar. Bu sayfa, SDK'ları uygulamanıza ekleme, kaldırma veya kullanmaya devam etme konusunda karar verirken yararlanabileceğiniz özellikler ve sinyaller sağlar.

Ek kaynaklar

ANR'ler hakkında daha fazla bilgi edinmek için aşağıdaki kaynaklara göz atın: