يوضّح هذا القسم كيفية تصحيح خطأ التطبيق لا يستجيب (ANR) باستخدام
ProfilingManager مع مثال على تتبُّع تسلسل استدعاء الدوال البرمجية.
إعداد التطبيق لجمع أخطاء ANR
ابدأ بإعداد مشغّل خطأ ANR في تطبيقك:
public void addANRTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANR); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = profilingResult -> { // Handle uploading trace to your back-end }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
بعد تسجيل تتبُّع خطأ ANR وتحميله، افتحه في واجهة مستخدم Perfetto.
تحليل عملية التتبُّع
بما أنّ خطأ ANR أدّى إلى تشغيل التتبُّع، ستعرف أنّ التتبُّع انتهى عندما رصد النظام عدم استجابة في سلسلة التعليمات الرئيسية لتطبيقك. يوضّح الشكل 1 كيفية الانتقال إلى سلسلة التعليمات الرئيسية لتطبيقك التي تم وضع علامة عليها وفقًا لذلك ضمن واجهة المستخدم.
تتطابق نهاية التتبُّع مع الطابع الزمني لخطأ ANR، كما هو موضّح في الشكل 2.
تعرض عملية التتبُّع أيضًا العمليات التي كان التطبيق ينفّذها عند حدوث خطأ ANR.
على وجه التحديد، نفَّذ التطبيق الرمز في شريحة التتبُّع handleNetworkResponse. كانت شريحة البيانات هذه داخل شريحة البيانات MyApp:SubmitButton. استغرقت 1.48 ثانية من وقت وحدة المعالجة المركزية (الشكل 3).
إذا كنت تعتمد حاليًا على عمليات تتبُّع تسلسل استدعاء الدوال البرمجية في لحظة حدوث خطأ ANR لتصحيح الأخطاء، قد تنسب خطأ ANR بشكل خاطئ بالكامل إلى الرمز البرمجي الذي يتم تنفيذه ضمن شريحة التتبُّع handleNetworkResponse التي لم تنتهِ عند انتهاء تسجيل الملف الشخصي. ومع ذلك، لا يكفي وقت التنفيذ البالغ 1.48 ثانية لتفعيل خطأ ANR وحده، على الرغم من أنّه عملية مكلفة. عليك الرجوع إلى وقت أبعد لفهم ما أوقف مؤقتًا سلسلة التعليمات الرئيسية قبل تنفيذ هذه الطريقة.
للحصول على نقطة بداية للبحث عن سبب خطأ ANR، نبدأ البحث بعد آخر إطار تم إنشاؤه بواسطة سلسلة التعليمات الخاصة بواجهة المستخدم والذي يتوافق مع شريحة Choreographer#doFrame 551275، ولا توجد مصادر كبيرة للتأخير قبل بدء شريحة MyApp:SubmitButton التي انتهت بخطأ ANR (الشكل 4).
لفهم سبب الحظر، يمكنك التصغير لفحص شريحة MyApp:SubmitButton
البيانات الكاملة. ستلاحظ تفصيلاً مهمًا في حالات سلسلة المحادثات، كما هو موضّح في الشكل 4: استغرقت سلسلة المحادثات 75% من الوقت (6.7 ثانية) في الحالة Sleeping و24% فقط من الوقت في الحالة Running.
يشير ذلك إلى أنّ السبب الرئيسي لخطأ ANR هو الانتظار، وليس الاحتساب. افحص حالات النوم الفردية للعثور على نمط.
فترات النوم الثلاث الأولى (الأشكال 6-8) متطابقة تقريبًا، حيث تبلغ كل منها ثانيتَين تقريبًا. يبلغ معدّل النوم الرابع الشاذ (الشكل 9) 0.7 ثانية. من النادر أن تكون مدة 2 ثانية بالضبط مجرد صدفة في بيئة حوسبة. يشير ذلك بقوة إلى انتهاء مهلة مبرمَجة بدلاً من تعارض عشوائي في الموارد. قد يكون سبب السكون الأخير هو انتهاء انتظار سلسلة التعليمات لأنّ العملية التي كانت تنتظرها قد نجحت.
تتمثّل هذه الفرضية في أنّ التطبيق كان يتجاوز مهلة محدّدة من قِبل المستخدم تبلغ ثانيتَين عدة مرات، ثم ينجح في النهاية، ما يؤدي إلى حدوث تأخير كافٍ لتفعيل خطأ ANR.
للتأكّد من ذلك، افحص الرمز المرتبط بقسم MyApp:SubmitButton لتتبُّع الأخطاء:
private static final int NETWORK_TIMEOUT_MILLISECS = 2000; public void setupButtonCallback() { findViewById(R.id.submit).setOnClickListener(submitButtonView -> { Trace.beginSection("MyApp:SubmitButton"); onClickSubmit(); Trace.endSection(); }); } public void onClickSubmit() { prepareNetworkRequest(); boolean networkRequestSuccess = false; int maxAttempts = 10; while (!networkRequestSuccess && maxAttempts > 0) { networkRequestSuccess = performNetworkRequest(NETWORK_TIMEOUT_MILLISECS); maxAttempts--; } if (networkRequestSuccess) { handleNetworkResponse(); } } boolean performNetworkRequest(int timeoutMiliseconds) { // ... } // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
يؤكّد الرمز هذه الفرضية. تنفّذ الطريقة onClickSubmit طلب شبكة على سلسلة واجهة المستخدم مع قيمة NETWORK_TIMEOUT_MILLISECS مبرمَجة مسبقًا تبلغ 2000 ملي ثانية.
والأهم من ذلك، يتم تشغيله داخل حلقة while تعيد المحاولة حتى 10 مرات.
في عملية التتبُّع المحدّدة هذه، من المحتمل أنّ اتصال المستخدم بالشبكة كان ضعيفًا. فشلت المحاولات الثلاث الأولى، ما أدّى إلى حدوث ثلاث مهلات مدة كل منها ثانيتان (إجمالي 6 ثوانٍ).
نجحت المحاولة الرابعة بعد 0.7 ثانية، ما سمح للرمز البرمجي بالانتقال إلى
handleNetworkResponse. ومع ذلك، أدّى وقت الانتظار المتراكم إلى حدوث خطأ ANR.
يمكنك تجنُّب هذا النوع من أخطاء ANR من خلال وضع العمليات المتعلقة بالشبكة والتي تتفاوت في وقت الاستجابة في سلسلة تعليمات تعمل في الخلفية بدلاً من تنفيذها في سلسلة التعليمات الرئيسية. ويتيح ذلك لواجهة المستخدم الاستجابة حتى مع ضعف الاتصال بالإنترنت، ما يؤدي إلى التخلص تمامًا من هذا النوع من أخطاء ANR.