تشخيص أخطاء ANR وإصلاحها

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

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

عندما يكون النظام في حالة سيئة، يمكن أن تتسبّب المشاكل التالية في حدوث أخطاء ANR:

  • تتسبب المشاكل المؤقتة في خادم النظام عادةً في بطء عمليات استدعاء الرابط السريع.
  • تتسبّب المشاكل في خادم النظام وارتفاع عبء الجهاز في عدم جدولة سلاسل التطبيقات.

إذا كانت هذه الميزة متاحة لك، يمكنك التمييز بين مشاكل النظام والتطبيق باستخدام عمليات تتبُّع Perfetto باتّباع الخطوات التالية:

  • يمكنك معرفة ما إذا كان قد تم تحديد موعد لتنفيذ سلسلة التعليمات البرمجية الرئيسية للتطبيق من خلال الاطّلاع على مسار حالة سلسلة التعليمات البرمجية في Perfetto لمعرفة ما إذا كانت قيد التشغيل أو قابلة للتشغيل.
  • اطّلِع على سلاسل system_server بحثًا عن مشاكل مثل تعارض القفل.
  • بالنسبة إلى طلبات Binder البطيئة، اطّلِع على سلسلة الردود، إذا كانت متوفّرة، لمعرفة سبب البطء.

انتهت مهلة إرسال البيانات

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

فترة المهلة التلقائية: 5 ثوانٍ

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

لتجنُّب أخطاء ANR الناتجة عن إرسال الإدخال، اتّبِع أفضل الممارسات التالية:

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

الأسباب الشائعة

في ما يلي بعض الأسباب الشائعة لأخطاء ANR المتعلقة بإرسال البيانات وطرق حلّها المقترَحة.

السبب الإجراء الحلول المقترَحة
طلب الصنف Binder بطيء تُجري سلسلة التعليمات الرئيسية طلبًا طويلاً ومتزامنًا من الصنف Binder. يمكنك نقل المكالمة خارج سلسلة التعليمات الرئيسية أو محاولة تحسينها إذا كنت مالك واجهة برمجة التطبيقات.
العديد من طلبات الصنف Binder المتتالية تُجري سلسلة التعليمات الرئيسية العديد من طلبات الصنف Binder المتزامنة المتتالية. تجنَّب تنفيذ طلبات ربط في حلقة ضيقة.
حظر عمليات الإدخال والإخراج تُجري سلسلة التعليمات الرئيسية طلبًا لحظر وحدات الإدخال والإخراج، مثل الوصول إلى قاعدة البيانات أو الشبكة. نقل جميع عمليات الإدخال والإخراج التي تحظر سلسلة التعليمات الرئيسية
تزايد الطلب على دالة الاستبعاد المتبادل تم حظر سلسلة التعليمات الرئيسية بسبب انتظارها الاستحواذ على دالة استبعاد متبادل. تقليل تعارض الأقفال بين سلسلة التعليمات الرئيسية وسلسلة التعليمات الأخرى تحسين الرمز البطيء في سلسلة التعليمات البرمجية الأخرى
إطار مكلف عرض الكثير من المحتوى في إطار واحد، ما يؤدي إلى حدوث تشوّش شديد تقليل العمل المطلوب لعرض الإطار لا تستخدِم خوارزميات n2. استخدِم مكوّنات فعّالة لتنفيذ إجراءات مثل التمرير أو تقسيم المحتوى إلى صفحات، مثل مكتبة Paging في Jetpack.
تم حظرها بواسطة مكوّن آخر أحد المكوّنات الأخرى، مثل أداة استقبال البث، قيد التشغيل ويحظر سلسلة التعليمات الرئيسية. نقل المهام غير المتعلّقة بواجهة المستخدم خارج السلسلة الرئيسية قدر الإمكان تشغيل أدوات معالجة عمليات البث على سلسلة محادثات مختلفة
توقُّف وحدة معالجة الرسومات تعطُّل وحدة معالجة الرسومات (GPU) هو مشكلة في النظام أو الأجهزة تؤدي إلى حظر العرض، وبالتالي حدوث خطأ ANR عند إرسال الإدخال. للأسف، لا تتوفّر عادةً أي حلول من جانب التطبيق. إذا كان ذلك ممكنًا، تواصَل مع فريق الأجهزة لتحديد المشاكل وحلّها.

كيفية تصحيح الأخطاء

ابدأ تصحيح الأخطاء من خلال الاطّلاع على توقيع مجموعة أخطاء ANR في Google Play Console أو Firebase Crashlytics. تحتوي المجموعة عادةً على أهم اللقطات التي يُشتبه في تسبّبها في خطأ ANR.

يوضّح المخطّط الانسيابي التالي كيفية تحديد سبب حدوث خطأ ANR بسبب انتهاء المهلة أثناء إرسال البيانات.

الشكل 1. كيفية تصحيح خطأ ANR في عملية إرسال الإدخال

يمكن لـ "مؤشرات Play الحيوية" رصد بعض الأسباب الشائعة لأخطاء ANR والمساعدة في تصحيحها. على سبيل المثال، إذا رصدت "المؤشرات الحيوية" حدوث خطأ ANR بسبب تعارض القفل، يمكنها تلخيص المشكلة واقتراح الحل في قسم الإحصاءات الخاص بأخطاء ANR.

الشكل 2. رصد أخطاء ANR في "مؤشرات Play الحيوية"

ما مِن نافذة مركَّز عليها

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

فترة المهلة التلقائية: 5 ثوانٍ

الأسباب الشائعة

عادةً ما تحدث أخطاء ANR بدون نافذة مركزة بسبب إحدى المشكلتين التاليتين:

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

Kotlin

override fun onCreate(savedInstanceState: Bundle) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_main)
  window.addFlags(WindowManager.LayoutParams.FLAG_FLAG_NOT_FOCUSABLE)
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}

مهلة مستقبِل البث

يحدث خطأ ANR في مستقبِل البث عندما لا يتمكّن مستقبِل البث من معالجة بث في الوقت المناسب. بالنسبة إلى أجهزة الاستقبال المتزامنة أو أجهزة الاستقبال التي لا تستدعي goAsync()، يعني انتهاء المهلة أنّ onReceive() لم يكتمل في الوقت المناسب. بالنسبة إلى المستلِمين غير المتزامنين أو المستلِمين الذين يستدعون goAsync()، يعني انتهاء المهلة أنّه لم يتم استدعاء PendingResult.finish() في الوقت المناسب.

تحدث أخطاء ANR في أداة استقبال البث غالبًا في سلاسل المحادثات التالية:

  • سلسلة التعليمات الرئيسية، إذا كانت المشكلة هي بطء التطبيق عند بدء تشغيله
  • يتم تشغيل أداة استقبال البث في سلسلة التعليمات، إذا كانت المشكلة هي بطء رمز onReceive().
  • سلاسل العاملين في البث، إذا كانت المشكلة هي بطء رمز البث goAsync().

لتجنُّب أخطاء ANR في أجهزة استقبال البث، اتّبِع أفضل الممارسات التالية:

  • احرِص على أن يكون بدء تشغيل التطبيق سريعًا، لأنّه يُحتسب ضمن مهلة خطأ ANR إذا تم بدء تشغيل التطبيق لمعالجة البث.
  • في حال استخدام goAsync()، تأكَّد من استدعاء PendingResult.finish() بسرعة. يخضع ذلك لمهلة ANR نفسها التي تخضع لها أجهزة استقبال البث المتزامن.
  • في حال استخدام goAsync()، تأكَّد من عدم مشاركة سلاسل الوحدات العاملة(worker thread) مع عمليات أخرى طويلة الأمد أو حظر.
  • ننصحك باستخدام registerReceiver() لتشغيل أدوات استقبال البث في سلسلة تعليمات غير رئيسية، وذلك لتجنُّب حظر رمز واجهة المستخدم الذي يتم تشغيله في سلسلة التعليمات الرئيسية.

فترات المهلة

تعتمد فترات المهلة لتلقّي البث على ما إذا تم ضبط علامة الغرض في المقدّمة وعلى إصدار النظام الأساسي.

نوع النية نظام التشغيل Android 13 والإصدارات الأقدم الإصدار 14 من نظام التشغيل Android والإصدارات الأحدث

الغرض من الأولوية في المقدّمة

(FLAG_RECEIVER_FOREGROUND set)

10 ثوانٍ

من 10 إلى 20 ثانية، حسب ما إذا كانت العملية تستهلك الكثير من وحدة المعالجة المركزية

Background priority intent

(لم يتم ضبط FLAG_RECEIVER_FOREGROUND)

60 ثانية

‫60-120 ثانية، حسب ما إذا كانت العملية تستهلك الكثير من وحدة المعالجة المركزية

لمعرفة ما إذا تم ضبط العلامة FLAG_RECEIVER_FOREGROUND، ابحث عن "flg=" في موضوع خطأ ANR وتحقّق من توفّر 0x10000000. إذا تم ضبط هذا البت، يعني ذلك أنّ الغرض يتضمّن FLAG_RECEIVER_FOREGROUND، وبالتالي يكون المهلة أقصر.

مثال على موضوع خطأ ANR مع مهلة قصيرة للبث (من 10 إلى 20 ثانية):

Broadcast of Intent { act=android.inent.action.SCREEN_ON flg=0x50200010 }

مثال على موضوع خطأ ANR مع مهلة بث طويلة (من 60 إلى 120 ثانية):

Broadcast of Intent { act=android.intent.action.TIME_SET flg=0x25200010 }

كيفية قياس أوقات البث

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

يوضّح الشكل التالي المخطّط الزمني لخطأ ANR في أداة استقبال البث الذي يتوافق مع بعض عمليات التطبيق.

الشكل 3. المخطط الزمني لحالة ANR لمستقبِل البث

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

  • بالنسبة إلى أجهزة الاستقبال المتزامنة، يتوقف القياس عند عرض onReceive().
  • بالنسبة إلى أجهزة الاستقبال غير المتزامنة، يتوقف القياس عند استدعاء PendingResult.finish().
الشكل 4. نقاط نهاية قياس مهلة ANR للمستقبِلات المتزامنة وغير المتزامنة

الأسباب الشائعة

في ما يلي بعض الأسباب الشائعة لحدوث أخطاء ANR في أداة استقبال البث والحلول المقترَحة لها.

السبب ينطبق على تفاصيل المشكلة الحلّ المقترَح
مشكلة بطء التطبيق عند بدء تشغيله جميع المستلِمين استغرق التطبيق وقتًا طويلاً جدًا في بدء التشغيل على البارد. تحسين سرعة بدء تشغيل التطبيق
لم تتم جدولة onReceive() جميع المستلِمين كان مؤشر ترابط مستقبل البث مشغولاً بتنفيذ مهام أخرى، ولم يتمكّن من بدء تنفيذ الطريقة onReceive(). لا تنفِّذ مهامًا تستغرق وقتًا طويلاً في سلسلة التعليمات البرمجية الخاصة بالمستلِم (أو انقل المستلِم إلى سلسلة تعليمات برمجية مخصّصة).
onReceive() بطيئة جميع أجهزة الاستقبال، ولكن بشكل أساسي تلك المتزامنة بدأت طريقة onReceive() ولكن تم حظرها أو كانت بطيئة، لذا لم تكتمل في الوقت المناسب. تحسين رمز الاستقبال البطيء
لم تتم جدولة مهام المستقبِل غير المتزامنة goAsync() أجهزة الاستقبال حاولت الطريقة onReceive() تنفيذ العمل في مجموعة سلاسل وحدات عاملة محظورة، لذا لم يبدأ العمل أبدًا. تحسين عمليات الاستدعاء البطيئة أو الحظر، أو استخدام سلاسل محادثات مختلفة للعاملين في البث مقارنةً بالمهام الأخرى التي تستغرق وقتًا طويلاً
العمال بطيئون أو محظورون goAsync() أجهزة استقبال حدثت عملية حظر أو بطيئة في مكان ما في مجموعة سلاسل الوحدات العاملة (worker thread) أثناء معالجة رسالة البث. لذلك، لم يتم استدعاء PendingResult.finish في الوقت المناسب. تحسين رمز asyncالمستلِم البطيء
نسيت الاتصال بـ PendingResult.finish goAsync() أجهزة الاستقبال مكالمة finish() غير متوفّرة في مسار الرمز البرمجي. تأكَّد من استدعاء finish() دائمًا.

كيفية تصحيح الأخطاء

استنادًا إلى توقيع المجموعة وتقرير ANR، يمكنك تحديد موقع سلسلة التعليمات التي يتم تشغيل المستقبِل عليها، ثم تحديد التعليمات البرمجية المحدّدة التي تكون مفقودة أو تعمل ببطء.

يوضّح مخطط التدفق التالي كيفية تحديد سبب حدوث خطأ ANR في أداة استقبال البث.

الشكل 5. كيفية تصحيح خطأ ANR في مستقبِل بث

العثور على رمز جهاز الاستقبال

تعرض Google Play Console فئة المتلقّي وintent البث في توقيع خطأ ANR. ابحث عن ما يلي:

  • cmp=<receiver class>
  • act=<broadcast_intent>

في ما يلي مثال على توقيع ANR لمستقبِل البث:

com.example.app.MyClass.myMethod
Broadcast of Intent { act=android.accounts.LOGIN_ACCOUNTS_CHANGED
cmp=com.example.app/com.example.app.MyAccountReceiver }

العثور على سلسلة التعليمات البرمجية التي تنفّذ طريقة onReceive()

إذا كنت تستخدم Context.registerReceiver لتحديد معالج مخصّص، سيكون هذا المعالج هو سلسلة التعليمات البرمجية التي يتم تشغيلها. بخلاف ذلك، تكون سلسلة المحادثات الرئيسية.

مثال: عدم جدولة مهام المستلِم غير المتزامن

يوضّح هذا القسم مثالاً على كيفية تصحيح خطأ ANR في أداة استقبال البث.

لنفترض أنّ توقيع خطأ ANR يظهر على النحو التالي:

com.example.app.MyClass.myMethod
Broadcast of Intent {
act=android.accounts.LOG_ACCOUNTS_CHANGED cmp=com.example.app/com.example.app.MyReceiver }

استنادًا إلى التوقيع، يبدو أنّ هدف البث هو android.accounts.LOG_ACCOUNTS_CHANGED وفئة المستلِم هي com.example.app.MyReceiver.

من رمز المستلِم، يمكنك تحديد أنّ مجموعة سلاسل التعليمات "BG Thread [0,1,2,3]" تنفّذ العمل الرئيسي لمعالجة عملية البث هذه. من خلال الاطّلاع على عمليات تفريغ الذاكرة المكدّسة، يمكنك ملاحظة أنّ جميع سلاسل التعليمات الخلفية الأربع (BG) تتّبع النمط نفسه: فهي تنفّذ عملية استدعاء حظر، getDataSync. بما أنّ جميع سلاسل المحادثات التي تعمل في الخلفية كانت مشغولة، تعذّر معالجة البث في الوقت المناسب، ما أدّى إلى حدوث خطأ ANR.

BG Thread #0 (tid=26) Waiting

at jdk.internal.misc.Unsafe.park(Native method:0)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:211)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture:563)
at com.google.common.util.concurrent.ForwardingFuture.get(ForwardingFuture:68)
at com.example.app.getDataSync(<MyClass>:152)

...

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
at com.google.android.libraries.concurrent.AndroidExecutorsModule.lambda$withStrictMode$5(AndroidExecutorsModule:451)
at com.google.android.libraries.concurrent.AndroidExecutorsModule$$ExternalSyntheticLambda8.run(AndroidExecutorsModule:1)
at java.lang.Thread.run(Thread.java:1012)
at com.google.android.libraries.concurrent.ManagedPriorityThread.run(ManagedPriorityThread:34)

There are several approaches to fix the issue:

  • Find out why getDataSync is slow and optimize.
  • Don't run getDataSync on all four BG threads.
  • More generally, ensure that the BG thread pool isn't saturated with long-running operations.
  • Use a dedicated thread pool for goAsync worker tasks.
  • Use an unbounded thread pool instead of the bounded BG thread pool

Example: slow app startup

A slow app startup can cause several types of ANRs, especially broadcast receiver and execute service ANRs. The cause of an ANR is likely slow app startup if you see ActivityThread.handleBindApplication in the main thread stacks.

Execute service timeout

An execute service ANR happens when the app's main thread doesn't start a service in time. Specifically, a service doesn't finish executing onCreate() and onStartCommand() or onBind() within the timeout period.

Default timeout period: 20 seconds for foreground service; 200 seconds for background service. The ANR timeout period includes the app cold start, if necessary, and calls to onCreate(), onBind(), or onStartCommand().

To avoid execute service ANRs, follow these general best practices:

  • Make sure that app startup is fast, since it's counted in the ANR timeout if the app is started to run the service component.
  • Make sure that the service's onCreate(), onStartCommand(), and onBind() methods are fast.
  • Avoid running any slow or blocking operations on the main thread from other components; these operations can prevent a service from starting quickly.

Common causes

The following table lists common causes of execute service ANRs and suggested fixes.

Cause What Suggested fix
Slow app startup The app takes too long to perform a cold start. Optimize slow app start.
Slow onCreate(), onStartCommand(), or onBind() The service component's onCreate(), onStartCommand(), or onBind() method takes too long to execute on the main thread. Optimize slow code. Move slow operations off the critical path where possible.
Not scheduled (main thread blocked before onStart()) The app's main thread is blocked by another component before the service can be started. Move other component's work off the main thread. Optimize other component's blocking code.

How to debug

From the cluster signature and ANR report in Google Play Console or Firebase Crashlytics, you can often determine the cause of the ANR based on what the main thread is doing.

The following flow chart describes how to debug an execute service ANR.

Figure 6. How to debug an execute service ANR.

If you've determined that the execute service ANR is actionable, follow these steps to help resolve the issue:

  1. Find the service component class in the ANR signature. In Google Play Console, the service component class is shown in the ANR signature. In the following example ANR details, it's com.example.app/MyService.

    com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly
    Executing service com.example.app/com.example.app.MyService
    
  2. Determine whether the slow or block operation is part of app startup, the service component, or elsewhere by checking for the following important function call(s) in the main threads.

    Function call(s) in main thread stacks What it means
    android.app.ActivityThread.handleBindApplication App was starting up, so the ANR was caused by slow app start.

    <ServiceClass>.onCreate()

    [...]

    android.app.ActivityThread.handleCreateService

    Service was being created, so the ANR was likely caused by slow onCreate() code.

    <ServiceClass>.onBind()

    [...]

    android.app.ActivityThread.handleBindService

    Service was being bound, so the ANR was likely caused by slow onBind() code.

    <ServiceClass>.onStartCommand()

    [...]

    android.app.ActivityThread.handleServiceArgs

    Service was being started, so the ANR was likely caused by slow onStartCommand() code.

    For example, if the onStartCommand() method in the MyService class is slow, the main threads will look like this:

    at com.example.app.MyService.onStartCommand(FooService.java:25)
    at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4820)
    at android.app.ActivityThread.-$$Nest$mhandleServiceArgs(unavailable:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2289)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8176)
    at java.lang.reflect.Method.invoke(Native method:0)
    

    إذا لم تتمكّن من رؤية أي من طلبات الدوال المهمة، هناك احتمالان آخران:

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

  4. لمزيد من المعلومات عن الخدمات، يُرجى الاطّلاع على الصفحات التالية:

    لا يستجيب مقدّم المحتوى

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

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

    لتجنُّب أخطاء ANR لموفّر المحتوى، اتّبِع أفضل الممارسات التالية:

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

    الأسباب الشائعة

    يسرد الجدول التالي الأسباب الشائعة لحدوث أخطاء ANR لدى موفّري المحتوى والحلول المقترَحة.

    السبب الإجراء إشارة الحلّ المقترَح
    طلب بحث بطيء من موفّر المحتوى يستغرق موفّر المحتوى وقتًا طويلاً جدًا لتنفيذ العملية أو يتم حظره. يقع إطار android.content.ContentProvider$Transport.query في سلسلة ربط. تحسين طلب موفّر المحتوى معرفة ما يحظر سلسلة Binder
    مشكلة بطء التطبيق عند بدء تشغيله يستغرق تطبيق مقدّم المحتوى وقتًا طويلاً للبدء. يقع إطار ActivityThread.handleBindApplication في سلسلة التعليمات الرئيسية. تحسين سرعة بدء تشغيل التطبيق
    استنفاد سلاسل ربط التطبيقات، أي أنّ جميع سلاسل ربط التطبيقات مشغولة تكون جميع سلاسل ربط البيانات مشغولة بتنفيذ طلبات متزامنة أخرى، وبالتالي لا يمكن تنفيذ طلب ربط البيانات الخاص بموفّر المحتوى. لا يتم تشغيل التطبيق، وجميع سلاسل ربط التطبيقات مشغولة، ولا يتم تشغيل موفّر المحتوى. تقليل الحمل على سلاسل ربط العمليات. أي تقليل عدد مكالمات Binder المتزامنة الصادرة أو تقليل العمل عند معالجة المكالمات الواردة.

    كيفية تصحيح الأخطاء

    لتصحيح خطأ ANR في موفّر المحتوى باستخدام توقيع المجموعة وتقرير ANR في Google Play Console أو Firebase Crashlytics، اطّلِع على ما يفعله كل من سلسلة التعليمات الرئيسية وسلاسل تعليمات Binder.

    يوضّح مخطط التدفق التالي كيفية تصحيح خطأ ANR في موفّر المحتوى:

    الشكل 7. كيفية تصحيح خطأ ANR في موفّر المحتوى

    يوضّح مقتطف الرمز التالي شكل سلسلة ربط عند حظرها بسبب طلب بحث بطيء من مقدّم المحتوى. في هذه الحالة، يكون طلب البحث المقدَّم من موفّر المحتوى بانتظار القفل عند فتح قاعدة بيانات.

    binder:11300_2 (tid=13) Blocked
    
    Waiting for osm (0x01ab5df9) held by at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers:182)
    at com.example.app.MyClass.blockingGetOpenDatabase(FooClass:171)
    [...]
    at com.example.app.MyContentProvider.query(MyContentProvider.java:915)
    at android.content.ContentProvider$Transport.query(ContentProvider.java:292)
    at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:107)
    at android.os.Binder.execTransactInternal(Binder.java:1339)
    at android.os.Binder.execTransact(Binder.java:1275)
    

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

    main (tid=1) Blocked
    
    [...]
    at dagger.internal.DoubleCheck.get(DoubleCheck:51)
    - locked 0x0e33cd2c (a qsn)at dagger.internal.SetFactory.get(SetFactory:126)
    at com.myapp.Bar_Factory.get(Bar_Factory:38)
    [...]
    at com.example.app.MyApplication.onCreate(DocsApplication:203)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6991)
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(unavailable:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2235)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8170)
    at java.lang.reflect.Method.invoke(Native method:0)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
    

    استجابة بطيئة للوظيفة

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

    إذا كانت المشكلة متعلقة بـ JobService.onStartJob() أو JobService.onStopJob()، تحقَّق مما يحدث في سلسلة التعليمات الرئيسية. إذا كانت المشكلة متعلقة بـ JobService.setNotification()، يُرجى الاتصال به في أقرب وقت ممكن. لا تُنفّذ الكثير من العمل قبل تقديم الإشعار.

    عرض مخالفة التضمين

    تجربة طريقة "إنشاء"
    ‫Jetpack Compose هي مجموعة أدوات واجهة المستخدم المقترَحة لنظام التشغيل Android.

    إذا عدّل تطبيقك طريقة عرض في سلسلة محادثات في الخلفية، يمكن أن يؤدي ذلك إلى حدوث حالة تزامن في الحالة الداخلية لتسلسل العرض الهرمي. يمكن أن يؤدي هذا الانتهاك إلى منع سلسلة التعليمات الرئيسية (واجهة المستخدم) من تنفيذ أي رسائل متزامنة، ما يؤدي إلى حدوث مشاكل خطيرة في الثبات:

    • توقّف واجهة المستخدم بدون إشعار: تتوقّف واجهة مستخدم التطبيق عن الاستجابة لمدخلات المستخدم.
    • الأعطال: قد يتعطّل التطبيق مع ظهور illegalStateException.
    • أخطاء ANR: قد يؤدي النظام إلى حدوث مهلة خطأ ANR.

    يُعدّ هذا الخطأ في سلسلة التعليمات أحد الأسباب الجذرية وراء عمليات تتبُّع تسلسُل استدعاء الدوال البرمجية لأخطاء ANR التي تبدو فيها سلسلة التعليمات الرئيسية غير نشطة (على سبيل المثال، تم رصدها في nativePollOnce أو main thread idle).

    تسريب حاجز المزامنة

    يمكن إبطال صحة طرق العرض بشكل غير مباشر من خلال تغيير حالتها (على سبيل المثال، استدعاء TextView.setText أو View.setVisibility) أو بشكل مباشر من خلال استدعاء View.invalidate أو View.requestLayout. عند إبطال صحة طريقة عرض، يجدول ViewRootImpl عملية اجتياز لتنفيذ القياس والتصميم والرسم من أجل تعديل حالة واجهة المستخدم.

    1. جدولة عمليات التنقّل: يجدول ViewRootImpl عملية تنقّل، وهي عملية عرض وتنسيق، من خلال نشر حاجز مزامنة في MessageQueue لسلسلة التعليمات الرئيسية لواجهة المستخدم. يؤدي هذا الحاجز إلى إيقاف معالجة الرسائل العادية مؤقتًا حتى يمكن إعطاء الأولوية لتصميم واجهة المستخدم.
    2. حالة السباق: عندما تحاول سلاسل متعددة إبطال صحة عرض في الوقت نفسه، تتسابق لتحديد موعد هذا الاجتياز. قد يتمكّن كلا مؤشري الترابط من إدراج حاجز مزامنة بنجاح، ولكن لا يخزّن إطار العمل الرمز المميّز إلا لأحدهما.
    3. تجميد واجهة المستخدم: عند تنفيذ عملية الانتقال، تتم إزالة الحاجز المحفوظ الفردي فقط. تظل الحواجز الثانوية "المسرَّبة" في قائمة الانتظار إلى أجل غير مسمّى، ما يؤدي إلى حظر سلسلة واجهة المستخدم نهائيًا من معالجة أي رسائل متزامنة.

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

    لتجنُّب مخالفات عرض المحتوى في سلاسل محادثات متعددة، اتّبِع أفضل الممارسات التالية:

    • تفاعَل فقط مع عناصر View، بما في ذلك قراءة الخصائص مثل width أو height أو ضبط الخصائص مثل TextView's text، وذلك في سلسلة التعليمات التي أنشأت فيها هيكلية طرق العرض. ويكون هذا الخيط دائمًا الخيط الرئيسي أو خيط واجهة المستخدم.
    • إذا لم تتمكّن من ضمان أنّك تعمل على سلسلة واجهة المستخدم عند الوصول إلى طرق العرض، افترض أنّه من غير الآمن إجراء ذلك. على سبيل المثال، إذا كنت تستخدم Coroutines للعمل في الخلفية، عليك التبديل إلى أداة توزيع المهام الرئيسية قبل معالجة عناصر View.

    lifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
        val data = myRepository.getData()
        withContext(Dispatchers.Main) { // Switch context to Main
            myTextView.text = data.title
        }
    }

    لمساعدتك في اتّباع أفضل الممارسات، يوفّر نظام التشغيل Android عدة طرق للوصول إلى سلسلة محادثات واجهة المستخدم من سلاسل المحادثات الأخرى:

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

    كيفية تصحيح الأخطاء

    يقدّم Android 17 أدوات جديدة لمساعدتك في تحديد مخالفات ربط عرض بواجهة مستخدم، وتتبُّعها، وحلّها أثناء عملية التطوير.

    1. أدوات اختبار توافق التطبيقات

    تتيح أدوات إطار عمل التوافق لمطوّري التطبيقات تفعيل تغييرات السلوك وإيقافها بشكل فردي باستخدام خيارات المطوّرين أو ADB. يمكنك تبديل سلوك انتهاك سلاسل العرض باتّباع الخطوات التالية:

    1. ثبِّت إصدارًا قابلاً للتصحيح من تطبيقك على جهاز يعمل بالإصدار 17 من نظام التشغيل Android أو إصدار أحدث.
    2. افتح تطبيق "الإعدادات" على جهازك وانتقِل إلى النظام > الإعدادات المتقدّمة > خيارات المطوّرين > تغييرات توافق التطبيقات.
    3. اختَر تطبيقك من القائمة.
    4. من قائمة التغييرات، ابحث عن مفتاح التبديل ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS وفعِّله.

    يمكنك أيضًا تفعيل العلامة أو إيقافها باستخدام ADB:

    $ adb shell am compat enable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
    $ adb shell am compat disable ENFORCE_THREAD_CHECKS_ON_VIEW_ROOT_IMPL_APIS <your.package.name>
    

    يؤدي تفعيل هذه العلامة إلى فرض طرح التطبيق استثناءً وتعطُّله كلما تم استدعاء واجهة برمجة التطبيقات View من سلسلة التعليمات الخاطئة، ما يساعدك في رصد مشاكل انتهاك سلسلة التعليمات الخاصة بعرض البيانات وإصلاحها.

    2. CalledFromWrongThreadListener API

    يمكنك أيضًا تنفيذ واجهة برمجة التطبيقات العامة View#registerCalledFromWrongThreadListener لرصد الوصول غير السليم إلى سلسلة المحادثات بشكل آلي.

    • القياس عن بُعد والتتبُّع: يتيح هذا المعالج لتطبيقك تلقّي عمليات رد الاتصال عند استدعاء View API من سلسلة محادثات غير صحيحة، ما يسهّل تسجيل بيانات القياس عن بُعد وتتبُّع الأخطاء.
    • التنفيذ المضمّن: يتم استدعاء أداة معالجة الأحداث بشكل مضمّن، ما يتيح لك تسجيل وفحص تتبُّع تسلسل استدعاء الدوال البرمجية الدقيق في لحظة حدوث المخالفة.
    android {
       //...
       compileSdk = 37
    }
    

    val listener = object : View.CalledFromWrongThreadListener {
        override fun onCalledFromWrongThread() {
            // Handle the issue, e.g. crash if this is a dev build, or log an event
            // e.g. Log.d(TAG, "CalledFromWrongThread: ${Exception().stackTraceToString()}")
            // Unregister the listener to avoid redundant notifications for the same issue
            View.unregisterCalledFromWrongThreadListener(this)
        }
    }
    View.registerCalledFromWrongThreadListener(listener)

    nativePollOnce

    إذا ظهر الإطار "nativePollOnce" أو "message queue idle" في حِزم ANR، يشير ذلك غالبًا إلى أنّ سلسلة التعليمات التي يُشتبه في عدم استجابتها كانت في الواقع غير نشطة وتنتظر رسائل looper. في Google Play Console، تظهر تفاصيل خطأ ANR على النحو التالي:

    Native method - android.os.MessageQueue.nativePollOnce
    Executing service com.example.app/com.example.app.MyService
    

    على سبيل المثال، إذا كانت سلسلة التعليمات الرئيسية غير مستخدمة من قِبل أي برنامج حاليًا، ستبدو حِزم البيانات على النحو التالي:

    "main" tid=1 NativeMain threadIdle
    
    #00  pc 0x00000000000d8b38  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+8)
    #01  pc 0x0000000000019d88  /system/lib64/libutils.so (android::Looper::pollInner(int)+184)
    #02  pc 0x0000000000019c68  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+112)
    #03  pc 0x000000000011409c  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
    at android.os.MessageQueue.nativePollOnce (Native method)
    at android.os.MessageQueue.next (MessageQueue.java:339)  at android.os.Looper.loop (Looper.java:208)
    at android.app.ActivityThread.main (ActivityThread.java:8192)
    at java.lang.reflect.Method.invoke (Native method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:626)
    at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1015)
    

    هناك عدة أسباب قد تؤدي إلى عدم نشاط سلسلة التعليمات التي يُشتبه في عدم استجابتها:

    • مشكلة على مستوى النظام لم تتم جدولة العملية بسبب الحمل الكبير على النظام أو حدوث مشكلة في خادم النظام.
    • تفريغ الحزمة المتأخر تم استرداد سلسلة المحادثات خلال الفترة القصيرة بين بدء ANR وتفريغ الحِزم. يبلغ وقت الاستجابة في هواتف Pixel التي تعمل بالإصدار Android 13 حوالي 100 ملي ثانية، ولكن قد يتجاوز ثانية واحدة. يكون وقت الاستجابة في هواتف Pixel التي تعمل بالإصدار Android 14 أقل من 10 ملي ثانية عادةً.
    • إسناد سلسلة محادثات بشكل خاطئ: لم يكن مؤشر الترابط المستخدَم لإنشاء توقيع خطأ ANR هو مؤشر الترابط الفعلي غير المستجيب الذي تسبّب في حدوث الخطأ.
    • عرض مخالفة ربط المحادثات إذا عدّل تطبيقك طريقة عرض في سلسلة محادثات في الخلفية، يمكن أن يؤدي ذلك إلى حدوث حالة تزاحم في الأجزاء الداخلية لطريقة العرض، ما يمنع تنفيذ مهام سلسلة محادثات واجهة المستخدم.

    بما أنّ الأسباب الأساسية لحدوث خطأ ANR من النوع "nativePollOnce" تختلف حسب نوع خطأ ANR، يمكن أن يساعدك تشخيص فئة خطأ ANR المحدّدة في تحديد الخطوات العملية اللازمة لحلّ المشكلة في تطبيقك.

    الفئة السبب الحلّ المقترَح
    انتهت مهلة إرسال البيانات مشكلة على مستوى النظام
    تفريغ مكدس متأخر
    ليس عليك اتّخاذ أي إجراء.
    ما مِن نافذة مركَّز عليها مشكلة على مستوى النظام
    تفريغ مكدس متأخر
    عرض انتهاك سلاسل التعليمات
    تحقَّق من قاعدة الرموز بحثًا عن انتهاكات لتنفيذ العرض.
    مهلة مستقبِل البث تفريغ مكدس متأخر
    مشكلة على مستوى النظام
    عدم تحديد مصدر السلسلة بشكل صحيح
    عرض انتهاك تعدد مؤشرات الترابط
    افحص سلاسل المحادثات ذات الصلة في تفريغ الذاكرة.
    تحقَّق من قاعدة الرموز بحثًا عن انتهاكات لتعدد مؤشرات ترابط العرض.
    مهلة تنفيذ الخدمة تفريغ مكدس متأخر
    مشكلة على مستوى النظام
    عرض انتهاك سلاسل المحادثات
    تحقَّق من قاعدة الرموز بحثًا عن انتهاكات لتنفيذ العرض.
    لا يستجيب مقدّم المحتوى Late stack dump
    مشكلة على مستوى النظام
    عدم تحديد مصدر السلسلة بشكل صحيح
    افحص سلاسل المحادثات ذات الصلة في تفريغ الذاكرة.

    في ما يلي الخطوات المقترَحة لتحليل أخطاء ANR في nativePollOnce.

    1. العبء الشديد على النظام: قيِّم الضغط العام على موارد الجهاز، مثل نقص وحدة المعالجة المركزية أو الذاكرة أو الإدخال/الإخراج على مستوى النظام، باعتباره السبب الرئيسي لعدم الاستجابة.
    2. تحديد مصدر غير صحيح لسلسلة التعليمات: تدقيق سلاسل تعليمات المنفِّذ وbinder بحثًا عن حالات توقّف تام أو تعارض في الأقفال يؤثر في سلسلة التعليمات الرئيسية أو سلاسل تعليمات الخلفية المعطّلة التي تعالج المكوّنات غير المتزامنة (مثل goAsync()).
    3. عرض انتهاكات إنشاء سلاسل المحادثات: يمكنك فحص سلاسل المحادثات في الخلفية بحثًا عن تعديلات غير قانونية على تسلسل عرض واجهة المستخدم، ما قد يؤدي إلى إيقاف حاجز المزامنة في MessageQueue وحظر جميع الرسائل المتزامنة بشكل دائم.

    ما مِن إطارات حزمة التكديس

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

    • يستغرق الحصول على حزمة البيانات وقتًا طويلاً جدًا ويحدث خطأ انتهاء المهلة.
    • تم إيقاف العملية نهائيًا أو إيقافها قبل الحصول على عمليات التتبُّع.
    [...]
    
    --- CriticalEventLog ---
    capacity: 20
    timestamp_ms: 1666030897753
    window_ms: 300000
    
    libdebuggerd_client: failed to read status response from tombstoned: timeout reached?
    
    ----- Waiting Channels: pid 7068 at 2022-10-18 02:21:37.<US_SOCIAL_SECURITY_NUMBER>+0800 -----
    
    [...]
    

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

    المشاكل المعروفة

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