ANR های رایج Unity Games

Unity ANR به دلایل مختلفی اتفاق می افتد. بیشتر ANR های رایج به دلیل استفاده نادرست از اجزای Android و Unity و عدم ارتباط آنها ایجاد می شوند.

WebView

WebView یک کلاس اندروید است که صفحات وب را نمایش می دهد. SDK های شخص ثالث (مانند تبلیغات) از WebView برای نمایش محتوای وب پویا در فعالیت هایی غیر از UnityPlayerActivity استفاده می کنند. ANR زمانی رخ می دهد که SDK های شخص ثالث از WebView سوء استفاده می کنند.

ردیابی پشته

ردیابی پشته اولین راه‌حل شما برای درک علت 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)

شکل 1. ردیابی پشته ANR ناشی از انتظار futex .

علت

تاکنون علت اصلی این موضوع نامشخص است. برخی از علل بالقوه ممکن است شامل موارد زیر باشد:

  • اجرای بد تبلیغات
  • یک نسخه قدیمی از WebView زیرا ممکن است کاربر تصمیم گرفته باشد برنامه را به طور خودکار به روز نکند.
  • استفاده زیاد از منابع سیستم (CPU، GPU و غیره) که ممکن است نیاز به پروفایل سازی زیادی داشته باشد.
  • کامپایل Shader خراب می شود، که می تواند نشان دهد که محتوا دارای یک سایه زن ناسازگار است یا اینکه کاربر یک نسخه WebView قدیمی را نصب کرده است.

راه حل

  • برای محدود کردن نوع محتوایی که باعث می‌شود WebView رشته اصلی را مسدود کند، هر زمان که یک صفحه وب بارگیری، نمایش یا بسته شد، گزارش‌ها را به بازی خود اضافه کنید.
    • می توانید از خدمات گزارش Backtrace یا Crashlytics استفاده کنید.
    • سپس، پس از تجزیه و تحلیل داده ها و یافتن مشکل، سعی کنید ارائه دهندگان تبلیغات متخلف را غیرفعال کنید.
    • گزارش‌های حافظه را اضافه کنید تا مطمئن شوید مشکل مربوط به حافظه نیست.
  • به کاربر هشدار دهید تا WebView از Google Play به روز کند . از Android 5.0 (سطح API 21) و بالاتر، WebView به APK منتقل شده است. بنابراین، می توان آن را به طور جداگانه از پلتفرم اندروید به روز کرد. برای اینکه ببینید چه نسخه‌ای از WebView در دستگاه استفاده می‌شود، به تنظیمات > برنامه‌ها > WebView سیستم Android بروید و به نسخه در پایین صفحه نگاه کنید.
صفحه اطلاعات برنامه که نسخه های WebView را نشان می دهد.
شکل 1. نسخه WebView را بررسی کنید.

مکث وحدت

هنگامی که UnityPlayerActivity یک فراخوانی onPause() دریافت می کند، زنجیره عملیات زیر شروع می شود:

  1. UnityPlayerActivity به موتور زمان اجرا Unity اطلاع می دهد که فعالیت متوقف شده است.
  2. یونیتی هر MonoBehaviour را که رویداد OnApplicationPause را اجرا می کند فراخوانی می کند.
  3. یونیتی اجزا و ماژول های خود مانند پخش صدا، رندر، حلقه بازی و انیمیشن را متوقف می کند.
  4. برای اطمینان از همگام سازی Unity Android Player (UAP) و موتور، UAP 4 ثانیه منتظر می ماند تا موتور متوقف شود.
  5. اگر این عملیات بیش از 5 ثانیه طول بکشد، سیستم یک ANR را راه اندازی می کند.

ردیابی پشته

"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. ANR ناشی از سمافوری است که هرگز منتشر نشده است.

راه حل

اطمینان حاصل کنید که اجرای کد بازی سی شارپ شما در طول یک رویداد توقف یا رزومه خیلی طول نمی کشد.

  • مشخصات بازی خود را مشخص کنید و بررسی کنید که آیا OnApplicationPause یک عملیات گران قیمت است یا خیر. می توانید از Stopwatch استفاده کنید.
  • از عملیات I/O یا درخواست های شبکه همزمان خودداری کنید.
  • با استفاده از Task عملیات را به Thread دیگری منتقل کنید. Unity 2023.1 از یک مدل برنامه نویسی ناهمزمان ساده با استفاده از C# async و کلمات کلیدی await پشتیبانی می کند.

UnitySendMessage مسدود شد

افزونه ها و SDK های جاوا یونیتی داده ها را با استفاده از JNI به لایه بازی C# ارسال می کنند. با این حال، این ارتباط ممکن است به دلیل یک روال همگام سازی بومی مانند mutex، رشته اصلی را مسدود کند و باعث ایجاد ANR به دلیل اختلاف قفل شود.

ردیابی پشته

ANR در شکل 4 به دلیل یک عملیات طولانی در کد C# که توسط یک افزونه جاوا نامیده می شود ایجاد شده است. موتور Unity از یک mutex Non-Priority Inheritance برای اطمینان از اجرای صحیح استفاده می کند.

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)

شکل 4. ANR ناشی از جدال قفل.

علت

مشکل این است که هنگام از سرگیری برنامه چندین پیام ارسال می شود. پیام ها در صف قرار می گیرند زیرا در حالی که بازی در پس زمینه است ارسال نمی شوند. هنگامی که برنامه از سر گرفته می شود، همه پیام ها به طور همزمان ارسال می شوند.

در طول یک دوره مکث، شما معمولا اطلاعات بازی خود را در سرور ذخیره می کنید. به عنوان مثال، شما موقعیت یک بازیکن را در بازی ضبط می کنید تا زمانی که بازی از سر گرفته شود، بازیکن بتواند به همان مکان بازگردد.

این حجم کاری، همراه با سایر کدهای شخص ثالث که بار کاری خاص خود را ایجاد می کند، می تواند منابع دستگاه، به ویژه رشته اصلی را بارگذاری کند. رشته اصلی رابط کاربری یک برنامه را اجرا می کند و اغلب محل اصلی ANR ها است. بنابراین، هر بار کاری اضافه شده بر روی رشته اصلی، پتانسیل یک ANR را افزایش می دهد.

راه حل

در طول مکث برنامه، مطمئن شوید که تمام اقدامات کد شما ضروری است یا سعی کنید وضعیت کاربر را در حافظه دستگاه محلی خود ذخیره کنید. و البته ببینید که آیا می توانید این اقدامات را خارج از دوره مکث نیز انجام دهید.

چند رویکرد :

  • عملیات سی شارپ که یک پیام را مدیریت می کند به رشته ای غیر از رشته اصلی منتقل کنید.
    • اگر کد شما به زمینه اصلی Unity وابسته نیست، به جای پیام Task برای ارتباط استفاده کنید.
  • هنگامی که بازی متوقف می شود، چندین پیام از افزونه خود ارسال نکنید.
    • در حالی که بازی در پس‌زمینه است، موتور نمی‌تواند پیام ارسال کند.
    • فقط در صورتی آخرین وضعیت داده را به بازی خود ارسال کنید که بر عملکرد بازی شما تأثیری نداشته باشد.

Referrer را نصب کنید

Play Install Referrer یک رشته منحصر به فرد است که هر زمان که کاربر روی تبلیغ کلیک می کند به فروشگاه Play ارسال می شود. این یک شناسه ردیابی تبلیغات مخصوص اندروید است. پس از نصب، برنامه ارجاع‌دهنده نصب را به شریک انتساب می‌فرستد که منبع را با نصب مطابقت می‌دهد (تبدیل را نسبت می‌دهد).

ردیابی پشته

شکل 5 یک ردیابی پشته ANR از یک بازی را نشان می دهد که از Facebook SDK برای بازیابی انتساب نصب استفاده می کند.

شکل 5. گزارش Android Vitals حاوی تماس Binder.

علت

ANR ناشی از تماس آهسته کلاسور بود. با این حال، بدون دسترسی به کد منبع SDK نمی توان علت اصلی را تعیین کرد.

راه حل

حل این نوع مشکل مستلزم برقراری ارتباط با توسعه‌دهنده SDK یا جستجوی آنلاین زیاد برای یک راه‌حل بالقوه، بررسی اینکه آیا نسخه جدیدتر SDK ANR را برای دیگران حل می‌کند یا حتی آزمایش با یک استراتژی عرضه کوچک است.

Google یک صفحه فهرست SDK ارائه می‌کند که داده‌های استفاده از برنامه‌های Google Play را با اطلاعات جمع‌آوری‌شده از طریق تشخیص کد ترکیب می‌کند تا ویژگی‌ها و سیگنال‌هایی را ارائه دهد که به شما کمک می‌کند تصمیم بگیرید که آیا یک SDK را بپذیرید، نگه دارید یا از برنامه خود حذف کنید.

منابع اضافی

برای کسب اطلاعات بیشتر در مورد ANR، به منابع زیر مراجعه کنید: