أخطاء ANR الشائعة في ألعاب Unity

تحدث أخطاء ANR في Unity لأسباب متنوعة. تحدث معظم أخطاء ANR بسبب سوء استخدام مكونات Android وUnity وعدم توافقها.

WebView

WebView هي فئة Android تعرض صفحات الويب. تستخدم حِزم تطوير البرامج (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 لأنّ المستخدم ربما اختار عدم تحديث التطبيق تلقائيًا.
  • الاستخدام العالي لموارد النظام (وحدة المعالجة المركزية ووحدة معالجة الرسومات وما إلى ذلك)، ما قد يتطلب الكثير من عمليات التحليل
  • تعطُّل تجميع Shader، ما قد يشير إلى أنّ المحتوى يتضمّن Shader غير متوافق أو أنّ المستخدم ثبّت إصدارًا قديمًا من WebView.

الحل

  • لتضييق نطاق نوع المحتوى الذي يتسبّب في حظر WebView سلسلة التعليمات الرئيسية، أضِف سجلّات إلى لعبتك كلما تم تحميل صفحة ويب أو عرضها أو إغلاقها.
    • يمكنك استخدام خدمات إعداد التقارير Backtrace أو Crashlytics.
    • بعد ذلك، وبعد تحليل البيانات والعثور على المشكلة، جرِّب إيقاف مقدّمي الإعلانات المخالفين.
    • أدرِج سجلّات الذاكرة للتأكّد من أنّ المشكلة لا تتعلّق بالذاكرة.
  • تنبيه المستخدم لتحديث WebView من Google Play بدءًا من الإصدار 5.0 من نظام التشغيل Android (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث، تم نقل WebView إلى حزمة APK. لذلك، يمكن تحديثها بشكل منفصل عن منصة Android. لمعرفة إصدار WebView المستخدَم على أحد الأجهزة، انتقِل إلى الإعدادات > التطبيقات > Android System WebView واطّلِع على الإصدار في أسفل الصفحة.
شاشة معلومات التطبيق التي تعرض إصدارات WebView
الشكل 1. تحقَّق من WebView الإصدار.

إيقاف Unity مؤقتًا

عندما يتلقّى UnityPlayerActivity مكالمة onPause()، تبدأ سلسلة العمليات التالية:

  1. يُعلم UnityPlayerActivity محرك وقت التشغيل في Unity بأنّ النشاط قد تم إيقافه مؤقتًا.
  2. تستدعي Unity كل MonoBehaviour التي تنفّذ الحدث OnApplicationPause.
  3. توقف Unity عن تشغيل مكوناته ووحداته، مثل تشغيل الصوت والعرض وحلقة اللعبة والحركة.
  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 ناتج عن دلالة لم يتم إطلاقها مطلقًا.

الحل

تأكَّد من أنّ رمز لعبة C# لا يستغرق وقتًا طويلاً لإنهاء التنفيذ أثناء حدث إيقاف مؤقت أو استئناف.

  • تحديد ملف تعريف لعبتك والتحقّق مما إذا كانت OnApplicationPause عملية مكلفة. يمكنك استخدام Stopwatch.
  • تجنَّب عمليات الإدخال/الإخراج أو طلبات الشبكة المتزامنة.
  • انقل العمليات إلى Thread آخر باستخدام Task. يتوافق الإصدار 2023.1 من Unity مع نموذج برمجة غير متزامن مبسط باستخدام الكلمتَين الرئيسيتَين async وawait في لغة C#.

تم حظر UnitySendMessage

ترسل حِزم SDK ومكوّنات Java الإضافية في Unity البيانات إلى طبقة الألعاب C# باستخدام JNI. ومع ذلك، قد يحظر هذا التواصل سلسلة التعليمات الرئيسية بسبب سلسلة إجراءات مزامنة أصلية، مثل دالة الاستبعاد المتبادل، ما يؤدي إلى حدوث خطأ ANR بسبب تعارض القفل.

تتبع التكديس

حدث خطأ ANR في الشكل 4 بسبب عملية طويلة في رمز C# تم استدعاؤه بواسطة إضافة Java. يستخدم محرّك Unity Non-Priority Inheritance mutex لضمان التنفيذ الصحيح.

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.

الحل

أثناء إيقاف التطبيق مؤقتًا، تأكَّد من أنّ جميع إجراءات الرمز البرمجي ضرورية، أو حاوِل حفظ حالة المستخدم في ذاكرة الجهاز المحلية. وبالطبع، يمكنك معرفة ما إذا كان بإمكانك أيضًا إكمال هذه الإجراءات خارج فترة الإيقاف المؤقت.

بعض الطرق:

  • انقل عملية C# التي تعالج رسالة إلى سلسلة محادثات أخرى غير سلسلة المحادثات الرئيسية.
    • إذا لم يكن الرمز البرمجي يعتمد على سياق سلسلة التعليمات الرئيسية في Unity، استخدِم Task للتواصل بدلاً من الرسالة.
  • لا ترسِل رسائل متعددة من المكوّن الإضافي عندما تكون اللعبة متوقفة مؤقتًا.
    • لا يمكن للمحرّك إرسال رسائل أثناء تشغيل اللعبة في الخلفية.
    • أرسِل آخر حالة بيانات إلى لعبتك فقط إذا لم يؤثّر ذلك في وظائف اللعبة.

Install Referrer

أداة إحالة مثبِّت التطبيق في Play هي سلسلة فريدة يتم إرسالها إلى "متجر Play" عندما ينقر المستخدم على إعلان. وهو معرّف تتبُّع إعلانات خاص بنظام التشغيل Android. بعد تثبيت التطبيق، يرسل التطبيق برنامج الإحالة إلى شريك تحديد المصدر، الذي يطابق المصدر مع عملية التثبيت (تحديد مصدر الإحالة الناجحة).

تتبع التكديس

يعرض الشكل 5 تتبُّع تسلسل استدعاء الدوال البرمجية لخطأ ANR من إحدى الألعاب التي تستخدم حزمة تطوير البرامج (SDK) من Facebook لاسترداد بيانات تحديد مصدر عمليات التثبيت.

الشكل 5. تقرير "مؤشرات Android الحيوية" الذي يتضمّن طلب Binder

السبب

حدث خطأ ANR بسبب طلب الصنف Binder بطيء. ومع ذلك، لا يمكن تحديد السبب الأساسي بدون الوصول إلى رمز المصدر لحزمة SDK.

الحل

يتطلّب حلّ هذا النوع من المشاكل التواصل مع مطوّر حزمة SDK أو البحث كثيرًا على الإنترنت عن حل محتمل، أو التحقّق مما إذا كان إصدار أحدث من حزمة SDK يحلّ خطأ ANR لدى الآخرين، أو حتى تجربة استراتيجية طرح صغيرة.

توفّر Google صفحة SDK Index تجمع بين بيانات الاستخدام الواردة من تطبيقات Google Play والمعلومات التي يتم جمعها من خلال رصد الرموز، وذلك لتوفير السمات والإشارات المصمّمة لمساعدتك في تحديد ما إذا كنت تريد استخدام إحدى حِزم SDK أو الاحتفاظ بها أو إزالتها من تطبيقك.

مراجع إضافية

لمزيد من المعلومات حول أخطاء ANR، يُرجى الاطّلاع على المراجع التالية: