أخطاء ANR

عندما يتم حظر سلسلة واجهة المستخدم في تطبيق Android لفترة طويلة جدًا، يظهر خطأ "التطبيق لا يستجيب" (ANR). إذا كان التطبيق في المقدّمة، يعرض النظام مربّع حوار للمستخدم، كما هو موضّح في الشكل 1. ويمنح مربّع حوار ANR المستخدم فرصة فرض إغلاق التطبيق.

مربّع حوار خطأ ANR الذي يظهر للمستخدم
الشكل 1: مربّع حوار ANR الذي يظهر للمستخدم

تُعدّ أخطاء ANR مشكلة لأنّ سلسلة التعليمات الرئيسية للتطبيق، والمسؤولة عن تحديث واجهة المستخدم، لا يمكنها معالجة أحداث إدخال المستخدم أو رسمها، ما يؤدي إلى شعور المستخدم بالإحباط. لمزيد من المعلومات حول سلسلة التعليمات الرئيسية للتطبيق، يُرجى الاطّلاع على نظرة عامة على العمليات وسلاسل التعليمات.

يتم تشغيل خطأ ANR لتطبيقك عند حدوث أحد الشروط التالية:

  • انتهت مهلة إرسال الإدخال: إذا لم يستجب تطبيقك لحدث إدخال (مثل الضغط على مفتاح أو لمس الشاشة) في غضون 5 ثوانٍ.
  • تنفيذ الخدمة: إذا لم تتمكّن خدمة معلَن عنها في تطبيقك من إنهاء تنفيذ Service.onCreate وService.onStartCommand/Service.onBind في غضون بضع ثوانٍ.
  • لم يتم استدعاء Service.startForeground: إذا كان تطبيقك يستخدم Context.startForegroundService لبدء خدمة جديدة في المقدّمة ولكن الخدمة لا تستدعي startForeground في غضون 5 ثوانٍ.
  • بثّ النية: إذا لم ينتهِ تنفيذ BroadcastReceiver خلال فترة زمنية محدّدة. إذا كان التطبيق يتضمّن أي نشاط في المقدمة، تكون مهلة الانتظار هذه 5 ثوانٍ.
  • تفاعلات JobScheduler: إذا لم يتم عرض JobService من JobService.onStartJob أو JobService.onStopJob في غضون بضع ثوانٍ، أو إذا بدأت مهمة بدأها المستخدم ولم يستدعِ تطبيقك JobService.setNotification في غضون بضع ثوانٍ بعد استدعاء JobService.onStartJob. بالنسبة إلى التطبيقات التي تستهدف الإصدار 13 من نظام التشغيل Android والإصدارات الأقدم، تكون أخطاء ANR صامتة ولا يتم إبلاغ التطبيق بها. بالنسبة إلى التطبيقات التي تستهدف الإصدار 14 من نظام التشغيل Android والإصدارات الأحدث، تكون أخطاء ANR صريحة ويتم إبلاغ التطبيق بها.

إذا كان تطبيقك يواجه أخطاء ANR، يمكنك استخدام الإرشادات الواردة في هذا المستند لتشخيص المشكلة وحلّها.

تشخيص أخطاء ANR

هناك بعض الأنماط الشائعة التي يجب البحث عنها عند تشخيص أخطاء ANR:

  • ينفّذ التطبيق عمليات بطيئة تتضمّن عمليات متعلّقة بوحدات الإدخال والإخراج في سلسلة التعليمات الرئيسية.
  • يُجري التطبيق عملية حسابية طويلة على سلسلة التعليمات الرئيسية.
  • تُجري سلسلة التعليمات الرئيسية طلبًا متزامنًا للصنف Binder لعملية أخرى، وتستغرق هذه العملية الأخرى وقتًا طويلاً للعودة.
  • تم حظر سلسلة التعليمات الرئيسية في انتظار سلسلة تعليمات متزامنة لعملية طويلة تحدث في سلسلة تعليمات أخرى.
  • تكون سلسلة التعليمات الرئيسية في حالة توقّف تام مع سلسلة تعليمات أخرى، إما في العملية التي تجريها أو من خلال استدعاء Binder. لا تنتظر سلسلة التعليمات الرئيسية انتهاء عملية طويلة فحسب، بل إنّها في حالة توقُّف تام.

يمكن أن تساعدك الأساليب التالية في تحديد سبب أخطاء ANR.

HealthStats

يوفّر HealthStats مقاييس حول حالة التطبيق من خلال التقاط إجمالي وقت المستخدم والنظام ووقت وحدة المعالجة المركزية والشبكة وإحصاءات الراديو ووقت تشغيل/إيقاف الشاشة ومنبهات التنبيه. يمكن أن يساعدك ذلك في قياس الاستخدام الإجمالي لوحدة المعالجة المركزية (CPU) واستنزاف البطارية.

تصحيح الأخطاء

تساعدك أداة Debug في فحص تطبيقات Android أثناء تطويرها، بما في ذلك تتبُّع عدد عمليات التخصيص لتحديد حالات التقطّع والبطء في التطبيقات. يمكنك أيضًا استخدام Debug للحصول على عدّادات الذاكرة الأصلية وذاكرة وقت التشغيل، ومقاييس الذاكرة التي يمكن أن تساعدك في تحديد استهلاك الذاكرة لعملية معيّنة.

ApplicationExitInfo

تتوفّر واجهة برمجة التطبيقات ApplicationExitInfo على الإصدار 11 من نظام التشغيل Android (المستوى 30 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث، وتوفّر معلومات عن سبب الخروج من التطبيق. ويشمل ذلك أخطاء ANR وانخفاض مساحة الذاكرة وتعطُّل التطبيقات والاستخدام المفرط لوحدة المعالجة المركزية (CPU) ومقاطعة المستخدمين ومقاطعة النظام وتغييرات أذونات التشغيل.

الوضع المقيَّد

يساعدك استخدام StrictMode في العثور على عمليات الإدخال/الإخراج العرضية في سلسلة التعليمات الرئيسية أثناء تطوير تطبيقك. يمكنك استخدام StrictMode على مستوى التطبيق أو النشاط.

تفعيل مربّعات حوار ANR في الخلفية

يعرض Android مربّعات حوار ANR للتطبيقات التي تستغرق وقتًا طويلاً في معالجة رسالة البث فقط في حال تفعيل خيار عرض جميع أخطاء ANR في خيارات المطوّرين على الجهاز. لهذا السبب، لا تظهر مربّعات حوار ANR في الخلفية دائمًا للمستخدم، حتى عندما يواجه التطبيق مشاكل في الأداء.

المشاكل التي تؤدي إلى إعادة الإنشاء

استخدِم محلّل "استوديو Android" وأداة فحص التنسيق لتتبُّع مشاكل إعادة الإنشاء. لمزيد من المعلومات، يُرجى الاطّلاع على أداء Jetpack Compose.

سحب ملف تتبُّع

يخزِّن Android معلومات التتبُّع عند حدوث خطأ ANR. في إصدارات نظام التشغيل القديمة، يتوفّر ملف /data/anr/traces.txt واحد على الجهاز. في إصدارات نظام التشغيل الأحدث، تتوفّر ملفات /data/anr/anr_* متعددة. يمكنك الوصول إلى تتبُّعات أخطاء ANR من جهاز أو محاكي باستخدام Android Debug Bridge (adb) كمستخدم جذر:

adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>

يمكنك الحصول على تقرير خطأ من جهاز فعلي باستخدام خيار المطوّر "إعداد تقرير بالأخطاء" على الجهاز أو الأمر adb bugreport على جهاز التطوير. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة تسجيل تقارير الأخطاء وقراءتها.

حلّ المشاكل

بعد تحديد المشكلة، يمكنك استخدام النصائح الواردة في هذا القسم لحلّ المشاكل الشائعة.

رمز بطيء في سلسلة التعليمات الرئيسية

حدِّد المواضع في الرمز البرمجي التي يكون فيها سلسلة التعليمات الرئيسية للتطبيق مشغولة لأكثر من 5 ثوانٍ. ابحث عن حالات الاستخدام المريبة في تطبيقك وحاوِل إعادة إنشاء خطأ ANR.

من المشاكل الشائعة تنفيذ مهمة طويلة مباشرةً في دالة مركّبة:

@Composable
fun BadList(rawStrings: List<String>) {
    // Math or sorting inside the composable runs on EVERY recomposition pass!
    val heavilyProcessedList = rawStrings
        .filter { it.isNotBlank() }
        .map { it.uppercase().reversed() }
        .map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
    LazyColumn { items(sortedList) { Text(it) } }
}

// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    // UI simply renders state; no heavy processing allowed here
    LazyColumn { items(uiState.sortedData) { Text(it) } }
}

عمليات الإدخال والإخراج في سلسلة التعليمات الرئيسية

يُعدّ تنفيذ عمليات الإدخال والإخراج في سلسلة التعليمات الرئيسية سببًا شائعًا لبطء العمليات في سلسلة التعليمات الرئيسية، ما قد يؤدي إلى حدوث أخطاء ANR. في Compose، غالبًا ما يشغّل المطوّرون عن طريق الخطأ عمليات قراءة القرص (مثل SharedPreferences أو طلبات قاعدة البيانات) أثناء محاولة اشتقاق الحالة الأولية.

نفِّذ عمليات الإدخال/الإخراج الطويلة بعيدًا عن طبقة واجهة المستخدم. استخدِم withContext(Dispatchers.IO) في ViewModel أو الأفضل من ذلك، استخدِم Repository في طبقة البيانات.

أقفال ميتة

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

حالات التوقف التام هي ظاهرة تمت دراستها جيدًا في علوم الكمبيوتر، وهناك خوارزميات لمنع حالات التوقف التام يمكنك استخدامها لتجنب حالات التوقف التام.

لمزيد من المعلومات، يُرجى الاطّلاع على الجمود وخوارزميات منع الجمود على Wikipedia.

عند استخدام Kotlin وCompose، يمكنك استبدال الأقفال الأساسية بأقفال استبعاد متبادل (Mutex.withLock) غير محظورة لمنع حظر سلسلة واجهة المستخدم من خلال تعليق سياق التنفيذ بدلاً من تجميد سلسلة واجهة المستخدم. على سبيل المثال:

import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

// Modern non-blocking concurrency state architecture
class SecureDataRepository {
    private val mutex = Mutex()

    suspend fun safeUIAccess() {
        // If locked, the main thread suspends seamlessly, preventing an ANR
        mutex.withLock {
            performSafeOperation()
        }
    }
}

مستقبِلات البث البطيئة

يمكن للتطبيقات الاستجابة لرسائل البث، مثل تفعيل وضع الطيران أو إيقافه أو تغيير حالة الاتصال، من خلال أجهزة استقبال البث. يحدث خطأ ANR عندما يستغرق التطبيق وقتًا طويلاً لمعالجة رسالة الإعلان على جميع الأجهزة.

يحدث خطأ ANR في الحالات التالية:

  • لم ينتهِ مستقبل البث من تنفيذ طريقة onReceive خلال فترة زمنية معقولة.
  • يستدعي مستقبل البث goAsync ويتعذّر عليه استدعاء finish في الكائن PendingResult.

يجب أن ينفّذ تطبيقك عمليات قصيرة فقط في طريقة onReceive لـ BroadcastReceiver. ومع ذلك، إذا كان تطبيقك يتطلب معالجة أكثر تعقيدًا نتيجة لرسالة بث، فيجب عليك تأجيل المهمة إلى ViewModel (الاستفادة من قوة الكوروتين والنطاقات والمرسلين في Kotlin) إذا كان من المتوقع أن تستغرق المهمة بضع ثوانٍ على الأكثر، أو أي نوع من عناصر الاحتفاظ بالحالة، أو إلى WorkManager للمهام التي من المتوقع أن تستغرق أكثر من بضع ثوانٍ.

GameActivity

تساعد مكتبة GameActivity في تقليل أخطاء ANR في دراسات الحالة للألعاب والتطبيقات المكتوبة بلغة C أو C++. وإذا استبدلت نشاطك الحالي الناتج من رموز برمجية أصلية بـ GameActivity، يمكنك تقليل حظر سلسلة واجهة المستخدم ومنع حدوث بعض أخطاء ANR.

لمزيد من المعلومات حول أخطاء ANR، يُرجى الاطّلاع على مقالة الحفاظ على استجابة تطبيقك. لمزيد من المعلومات حول سلاسل المحادثات، يُرجى الاطّلاع على تحسين الأداء من خلال سلاسل المحادثات.

مراجع إضافية

محتوى المشاهدات