الشكل 1: مربّع حوار خطأ ANR الذي يظهر للمستخدم
توضّح هذه المقالة كيف يحدّد نظام Android ما إذا كان أحد التطبيقات لا يستجيب، وتوضّح كيفية الحفاظ على استجابة تطبيقك.
مهما كانت جودة الرموز البرمجية التي تكتبها، من المحتمل أن يظل تطبيقك بطيئًا أو يتوقف عن العمل أو يتجمّد لفترات طويلة أو يستغرق وقتًا طويلاً لمعالجة البيانات التي يُدخلها المستخدمون. إذا كان تطبيقك في المقدّمة ولا يستجيب، سيظهر للمستخدم مربّع حوار "التطبيق لا يستجيب" (ANR)، كما هو موضّح في الشكل 1. يسمح مربّع حوار ANR للمستخدم بفرض إغلاق التطبيق. وإذا لم يكن التطبيق في المقدّمة، يتم إيقافه بدون إشعار. من المهم تصميم تطبيقك بحيث يكون مستجيبًا لتقليل مربّعات حوار خطأ ANR.
الأحداث التي تشغّل خطأ ANR
بوجهٍ عام، يعرض النظام خطأ ANR إذا تعذّر على التطبيق الاستجابة لبيانات أدخلها المستخدم في سلسلة التعليمات الرئيسية، المعروفة أيضًا باسم سلسلة واجهة المستخدم، ما يمنع النظام من معالجة أحداث بيانات أدخلها المستخدم الواردة.
على سبيل المثال، يمكن أن يحدث خطأ ANR إذا أجرى التطبيق عملية إدخال/إخراج حظر، مثل الوصول إلى الشبكة، في سلسلة واجهة المستخدم. مثال آخر هو عندما يقضي التطبيق وقتًا طويلاً في إنشاء بنية معقّدة في الذاكرة أو حساب الخطوة التالية في لعبة على سلسلة واجهة المستخدم.
في Android، تتم مراقبة استجابة التطبيق من خلال خدمات النظام ActivityManager و
WindowManager. يعرض Android مربّع حوار خطأ ANR لتطبيق معيّن عندما يرصد إحدى الحالات التالية:
- عدم الردّ على حدث إدخال، مثل أحداث الضغط على المفاتيح أو النقر على الشاشة، في غضون 5 ثوانٍ.
- عدم اكتمال تنفيذ
BroadcastReceiverفي غضون 10 إلى 20 ثانية، بالنسبة إلى الأهداف التي تعمل في المقدّمة. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة مهلة "مستقبِل البث".
تجنُّب أخطاء ANR
في ما يلي نصائح عامة لتجنُّب أخطاء ANR. لمزيد من التفاصيل حول تشخيص أنواع مختلفة من أخطاء ANR وتصحيحها، يُرجى الاطّلاع على الصفحات الأخرى في هذا القسم.
يجب أن تظل سلسلة التعليمات الرئيسية غير محظورة في جميع الأوقات، واستخدِم سلاسل التعليمات بشكل استراتيجي.
لا تُجرِ عمليات حظر أو عمليات تستغرق وقتًا طويلاً في سلسلة التعليمات الرئيسية للتطبيق. بدلاً من ذلك، استخدِم كوروتينات Kotlin لتخفيف الحِمل على أجهزة توزيع المهام في الخلفية (مثل
Dispatchers.IOأوDispatchers.Default). استخدِم آليات مثلviewModelScopeلإطلاق مهام الخلفية هذه بأمان، أوLaunchedEffectلتشغيلها استجابةً لتغييرات حالة Compose.حاوِل تقليل أي نزاع على الدوال بين سلسلة التعليمات الرئيسية وسلاسل التعليمات الأخرى.
قلِّل أي عمل غير مرتبط بواجهة المستخدم في سلسلة التعليمات الرئيسية، مثل معالجة عمليات البث أو تشغيل الخدمات. يجب أن تنفّذ أي طريقة أو دالة تعمل في سلسلة تعليمات واجهة المستخدم أقل قدر ممكن من العمل. على وجه الخصوص، يجب أن تنفّذ الأنشطة أقل قدر ممكن من العمل لإعدادها في طرق مراحل النشاط الرئيسية، مثل
onCreateوonResume. لا تُجرِ عمليات الإدخال/الإخراج أو العمليات الحسابية المعقّدة التي تحظر سلسلة التعليمات مباشرةً داخل دالة قابلة للإنشاء. يؤدي ذلك إلى حظر سلسلة واجهة المستخدم أثناء التكوين وإعادة التكوين. لمزيد من المعلومات حول الحلول المتاحة لجدولة العمل في سلسلة تعليمات الخلفية والتواصل مع واجهة المستخدم، يُرجى الاطّلاع على نظرة عامة على مهام الخلفية.يُرجى توخّي الحذر عند مشاركة مجموعات سلاسل التعليمات بين المكوّنات. لا تستخدِم سلاسل التعليمات نفسها للعمليات التي قد تحظر سلسلة التعليمات لفترة طويلة والمهام الحساسة من حيث التوقيت، مثل تلقّي عمليات البث.
يجب أن يكون بدء تشغيل التطبيق سريعًا. قلِّل العمليات البطيئة أو التي تحظر سلسلة التعليمات في رمز بدء تشغيل التطبيق ، مثل الطرق التي يتم تشغيلها أثناء إعداد عملية إدخال التبعية (مثل Hilt) أو المكوّنات التي يتم تهيئتها باستخدام مكتبة Jetpack App Startup. يمكنك تحسين بدء تشغيل التطبيق بشكل أكبر باستخدام الملفات الشخصية للمرجع وملفات تعريف بدء التشغيل وR8.
إذا كنت تستخدم
BroadcastReceiver، ننصحك بتشغيل مستقبِلات البث في سلسلة تعليمات غير الرئيسية باستخدامContext.registerReceiver. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة أخطاء ANR في BroadcastReceiver.- إذا كنت تستخدم
goAsync، تأكَّد من استدعاءPendingResult.finishبسرعة قبل انتهاء مهلة خطأ ANR.
- إذا كنت تستخدم
أخطاء ANR في BroadcastReceiver
BroadcastReceiver يتم تقييد وقت تنفيذ لأنّ مستقبِلات البث
مصمّمة لتنفيذ كميات صغيرة ومحدّدة من العمل في الخلفية، مثل
حفظ إعداد أو تسجيل Notification. لذلك، كما هو الحال مع الطرق الأخرى التي يتم استدعاؤها في سلسلة واجهة المستخدم، يجب أن تتجنّب التطبيقات العمليات أو العمليات الحسابية التي قد تستغرق وقتًا طويلاً في مستقبِل البث. بدلاً من تنفيذ المهام التي تستغرق وقتًا طويلاً من خلال سلسلة واجهة المستخدم، نفِّذها في الخلفية لتنفيذها لاحقًا. لمزيد من المعلومات حول الحلول الممكنة، يُرجى الاطّلاع على نظرة عامة على مهام الخلفية.
تحدث مشكلة شائعة أخرى في عناصر BroadcastReceiver عندما يتم تنفيذها بشكل متكرّر جدًا. يمكن أن يؤدي التنفيذ المتكرّر في الخلفية إلى تقليل مقدار الذاكرة المتاحة للتطبيقات الأخرى. لمزيد من المعلومات حول كيفية تفعيل عناصر
BroadcastReceiver وإيقافها بكفاءة، يُرجى الاطّلاع على نظرة عامة على عمليات البث.
تعزيز الاستجابة
بوجهٍ عام، يتجاوز الحدّ الذي يشعر المستخدمون بعده ببطء التطبيق 100 إلى 200 ملّي ثانية. في ما يلي نصائح إضافية لجعل تطبيقك يبدو مستجيبًا للمستخدمين:
إذا كان تطبيقك يُجري عملاً في الخلفية استجابةً لبيانات أدخلها المستخدم، عليك إظهار أنّ العمل قيد التقدّم، مثلاً باستخدام
CircularProgressIndicatorأوLinearProgressIndicatorفي واجهة المستخدم.بالنسبة إلى الألعاب تحديدًا، عليك إجراء العمليات الحسابية للخطوات في كوروتين أو سلسلة الوحدات العاملة (worker thread) في الخلفية.
إذا كان تطبيقك يتضمّن مرحلة إعداد أولية تستغرق وقتًا طويلاً، ننصحك بعرض شاشة البداية أو عرض الإنشاء الأولي بأسرع وقت ممكن. أشِر إلى أنّ عملية التحميل قيد التقدّم واملأ حالة واجهة المستخدم بشكل غير متزامن. في كلتا الحالتَين، ننصحك بالإشارة بطريقة ما إلى أنّ العمل قيد التقدّم، حتى لا يظنّ المستخدم أنّ التطبيق متجمّد.
استخدِم أدوات الأداء، مثل Perfetto وCPU Profiler، لتحديد المشاكل في استجابة تطبيقك.