تحدث أخطاء 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
الإصدار.إيقاف Unity مؤقتًا
عندما يتلقّى UnityPlayerActivity
مكالمة onPause()
، تبدأ سلسلة العمليات التالية:
- يُعلم
UnityPlayerActivity
محرك وقت التشغيل في Unity بأنّ النشاط قد تم إيقافه مؤقتًا. - تستدعي Unity كل
MonoBehaviour
التي تنفّذ الحدثOnApplicationPause
. - توقف Unity عن تشغيل مكوناته ووحداته، مثل تشغيل الصوت والعرض وحلقة اللعبة والحركة.
- للتأكّد من مزامنة كلّ من
Unity Android Player
(UAP) والمحرّك، ينتظر UAP لمدة 4 ثوانٍ حتى يتوقف المحرّك. - إذا استغرقت هذه العملية أكثر من 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
للتواصل بدلاً من الرسالة.
- إذا لم يكن الرمز البرمجي يعتمد على سياق سلسلة التعليمات الرئيسية في Unity، استخدِم
- لا ترسِل رسائل متعددة من المكوّن الإضافي عندما تكون اللعبة متوقفة مؤقتًا.
- لا يمكن للمحرّك إرسال رسائل أثناء تشغيل اللعبة في الخلفية.
- أرسِل آخر حالة بيانات إلى لعبتك فقط إذا لم يؤثّر ذلك في وظائف اللعبة.
Install Referrer
أداة إحالة مثبِّت التطبيق في Play هي سلسلة فريدة يتم إرسالها إلى "متجر Play" عندما ينقر المستخدم على إعلان. وهو معرّف تتبُّع إعلانات خاص بنظام التشغيل Android. بعد تثبيت التطبيق، يرسل التطبيق برنامج الإحالة إلى شريك تحديد المصدر، الذي يطابق المصدر مع عملية التثبيت (تحديد مصدر الإحالة الناجحة).
تتبع التكديس
يعرض الشكل 5 تتبُّع تسلسل استدعاء الدوال البرمجية لخطأ ANR من إحدى الألعاب التي تستخدم حزمة تطوير البرامج (SDK) من Facebook لاسترداد بيانات تحديد مصدر عمليات التثبيت.

السبب
حدث خطأ ANR بسبب طلب الصنف Binder بطيء. ومع ذلك، لا يمكن تحديد السبب الأساسي بدون الوصول إلى رمز المصدر لحزمة SDK.
الحل
يتطلّب حلّ هذا النوع من المشاكل التواصل مع مطوّر حزمة SDK أو البحث كثيرًا على الإنترنت عن حل محتمل، أو التحقّق مما إذا كان إصدار أحدث من حزمة SDK يحلّ خطأ ANR لدى الآخرين، أو حتى تجربة استراتيجية طرح صغيرة.
توفّر Google صفحة SDK Index تجمع بين بيانات الاستخدام الواردة من تطبيقات Google Play والمعلومات التي يتم جمعها من خلال رصد الرموز، وذلك لتوفير السمات والإشارات المصمّمة لمساعدتك في تحديد ما إذا كنت تريد استخدام إحدى حِزم SDK أو الاحتفاظ بها أو إزالتها من تطبيقك.
مراجع إضافية
لمزيد من المعلومات حول أخطاء ANR، يُرجى الاطّلاع على المراجع التالية:
- تصحيح أخطاء ANR — تطوير ألعاب Android
- أخطاء ANR — جودة التطبيق