يوضِّح هذا القسم كيفية تصحيح خطأ التطبيق لا يستجيب (ANR) باستخدام
ProfilingManager مع مثال على عملية تتبُّع.
إعداد التطبيق لجمع أخطاء "التطبيق لا يستجيب"
ابدأ بإعداد مشغِّل خطأ "التطبيق لا يستجيب" في تطبيقك:
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); }
بعد تسجيل عملية تتبُّع خطأ "التطبيق لا يستجيب" وتحميلها، افتحها في واجهة مستخدم Perfetto.
تحليل عملية التتبُّع
بما أنّ خطأ "التطبيق لا يستجيب" هو الذي شغَّل عملية التتبُّع، يمكنك معرفة أنّ عملية التتبُّع انتهت عندما رصد النظام عدم استجابة سلسلة التعليمات الرئيسية في تطبيقك. يوضِّح الشكل 1 كيفية الانتقال إلى سلسلة التعليمات الرئيسية في تطبيقك التي تم وضع علامة عليها وفقًا لذلك ضمن واجهة المستخدم.
تتطابق نهاية عملية التتبُّع مع الطابع الزمني لخطأ "التطبيق لا يستجيب"، كما هو موضّح في الشكل 2.
تعرض عملية التتبُّع أيضًا العمليات التي كان التطبيق يُجريها عند حدوث خطأ "التطبيق لا يستجيب".
على وجه التحديد، شغَّل التطبيق رمزًا في شريحة التتبُّع handleNetworkResponse. كانت هذه الشريحة داخل شريحة MyApp:SubmitButton. استغرقت 1.48 ثانية من وقت وحدة المعالجة المركزية (الشكل 3).
إذا كنت تعتمد فقط على عمليات تتبُّع تسلسل استدعاء الدوال البرمجية في وقت حدوث خطأ "التطبيق لا يستجيب" لتصحيح الأخطاء، قد تُرجع خطأ "التطبيق لا يستجيب" بشكل خاطئ بالكامل إلى الرمز الذي يتم تشغيله ضمن شريحة التتبُّع handleNetworkResponse التي لم تنتهِ عند انتهاء تسجيل الملف الشخصي. ومع ذلك، لا يكفي وقت 1.48 ثانية لتشغيل خطأ "التطبيق لا يستجيب" بمفرده، على الرغم من أنّه عملية مكلفة. عليك الرجوع إلى وقت سابق لفهم ما منع سلسلة التعليمات الرئيسية قبل هذه الطريقة.
للحصول على نقطة بداية للبحث عن سبب خطأ "التطبيق لا يستجيب"، نبدأ البحث بعد آخر إطار أنشأته سلسلة واجهة المستخدم التي تتطابق مع شريحة Choreographer#doFrame 551275، وليس هناك مصادر كبيرة للتأخير قبل بدء شريحة MyApp:SubmitButton التي انتهت بخطأ "التطبيق لا يستجيب" (الشكل 4).
لفهم الحظر، عليك التصغير لفحص شريحة MyApp:SubmitButton الكاملة. ستلاحظ تفصيلاً مهمًا في حالات سلسلة التعليمات، كما هو موضّح في
الشكل 4: قضت سلسلة التعليمات 75% من الوقت (6.7 ثانية) في حالة Sleeping
و24% فقط من الوقت في حالة Running .
يشير ذلك إلى أنّ السبب الرئيسي لخطأ "التطبيق لا يستجيب" هو الانتظار، وليس الحوسبة. افحص حالات النوم الفردية للعثور على نمط.
الفواصل الزمنية الثلاث الأولى للنوم (الأشكال 6-8) متطابقة تقريبًا، وتبلغ ثانيتَين تقريبًا لكل منها. يبلغ وقت النوم الرابع الشاذ (الشكل 9) 0.7 ثانية. نادرًا ما تكون المدة التي تبلغ ثانيتَين بالضبط صدفة في بيئة الحوسبة. يشير ذلك بقوة إلى مهلة زمنية مبرمَجة بدلاً من التنافس العشوائي على الموارد. قد يكون سبب النوم الأخير هو انتهاء سلسلة التعليمات من الانتظار لأنّ العملية التي كانت تنتظرها نجحت.
الفرضية هي أنّ التطبيق كان يصل إلى مهلة زمنية محدّدة من قِبل المستخدم تبلغ ثانيتَين عدة مرات وينجح في النهاية، ما يؤدي إلى تأخير كافٍ لتشغيل خطأ "التطبيق لا يستجيب".
للتحقّق من ذلك، افحص الرمز المرتبط بقسم التتبُّع 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) { // ... } void prepareNetworkRequest() { // ... } public void handleNetworkResponse() { Trace.beginSection("handleNetworkResponse"); // ... Trace.endSection(); }
يؤكّد الرمز هذه الفرضية. تنفِّذ طريقة onClickSubmit طلب شبكة على سلسلة واجهة المستخدم مع NETWORK_TIMEOUT_MILLISECS مبرمَجة مسبقًا تبلغ 2000 ملي ثانية.
والأهم من ذلك، يتم تشغيلها داخل حلقة while تعيد المحاولة حتى 10 مرات.
في عملية التتبُّع المحدّدة هذه، من المحتمل أنّ اتصال الشبكة لدى المستخدم كان ضعيفًا. فشلت المحاولات الثلاث الأولى، ما أدّى إلى ثلاث مهلات زمنية تبلغ كل منها ثانيتَين (إجمالي 6 ثوانٍ).
نجحت المحاولة الرابعة بعد 0.7 ثانية، ما سمح للرمز بالانتقال إلى handleNetworkResponse. ومع ذلك، أدّى وقت الانتظار المتراكم إلى تشغيل خطأ "التطبيق لا يستجيب".
تجنَّب هذا النوع من أخطاء "التطبيق لا يستجيب" من خلال وضع العمليات المرتبطة بالشبكة التي تتفاوت حالات التأخير فيها في سلسلة تعليمات في الخلفية بدلاً من تنفيذها في سلسلة التعليمات الرئيسية. يسمح ذلك لواجهة المستخدم بالبقاء مستجيبة حتى مع الاتصال الضعيف، ما يزيل تمامًا هذه الفئة من أخطاء "التطبيق لا يستجيب".