أسباب ANR وكيفية تجنبها

ANR (التطبيق لا يستجيب) - خطأ يحدث عندما لا يستجيب التطبيق. نتيجة لذلك ، يتم فتح مربع حوار يطالب المستخدم بالانتظار أو إغلاق التطبيق.
بديل الصورة

شروط ANR


  • لا تتم معالجة أحداث الإدخال (الأزرار وأحداث اللمس) لمدة 5 ثوانٍ ؛
  • لم تتم معالجة BroadcastReceiver (onRecieve ()) خلال الوقت المحدد (المقدمة - 10 ثانية ، الخلفية - 60 ثانية) ؛
  • ContentProvider لم يكتمل خلال 10 ثوانٍ.

عادة ما يتم حظر الخيط الرئيسي.

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

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

class AppErrors { ... void handleShowAnrUi(Message msg) { Dialog dialogToShow = null; synchronized (mService) { AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj; final ProcessRecord proc = data.proc; if (proc == null) { Slog.e(TAG, "handleShowAnrUi: proc is null"); return; } if (proc.anrDialog != null) { Slog.e(TAG, "App already has anr dialog: " + proc); MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.ALREADY_SHOWING); return; } Intent intent = new Intent("android.intent.action.ANR"); if (!mService.mProcessesReady) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); } mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */); boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; if (mService.canShowErrorDialogs() || showBackground) { dialogToShow = new AppNotRespondingDialog(mService, mContext, data); proc.anrDialog = dialogToShow; } else { MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR, AppNotRespondingDialog.CANT_SHOW); // Just kill the app if there is no dialog to be shown. mService.killAppAtUsersRequest(proc, null); } } // If we've created a crash dialog, show it without the lock held if (dialogToShow != null) { dialogToShow.show(); } } ... 

ومع ذلك ، لا يبدأ ANR هنا. كما قلت أعلاه ، أحد الأسباب الأولى لهذا الخطأ هو تأخير حدث الإدخال ، وهو 5 ثوانٍ. من خلال البحث القصير يمكننا العثور على مكان تعيين هذه القيمة.

 namespace android { // Default input dispatching timeout if there is no focused application or paused window // from which to determine an appropriate dispatching timeout. const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec 

الآن يمكننا أن ننظر في رمز حيث يسمى الجزء الأصلي. يحدث هذا في الفصل InputManagerService .

  // Native callback. private long notifyANR(InputApplicationHandle inputApplicationHandle, InputWindowHandle inputWindowHandle, String reason) { return mWindowManagerCallbacks.notifyANR( inputApplicationHandle, inputWindowHandle, reason); } 

وهنا هو mWindowManagerCallbacks في InputMonitor :

  if (appWindowToken != null && appWindowToken.appToken != null) { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. final AppWindowContainerController controller = appWindowToken.getController(); final boolean abort = controller != null && controller.keyDispatchingTimedOut(reason, (windowState != null) ? windowState.mSession.mPid : -1); if (!abort) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. return appWindowToken.mInputDispatchingTimeoutNanos; } } else if (windowState != null) { try { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. long timeout = ActivityManager.getService().inputDispatchingTimedOut( windowState.mSession.mPid, aboveSystem, reason); if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. return timeout * 1000000L; // nanoseconds } } catch (RemoteException ex) { } } return 0; // abort dispatching } 

دعونا نلقي نظرة فاحصة على inputDispatchingTimedOut (). هنا نعرض فقط الرسالة من خلال ActivityManager عن انتهاء صلاحية المهلة وترك المستخدم يقرر ما إذا كان سيتم إلغاء الإجراء أو الاستمرار في الانتظار. وفي ActivityManagerService يتم استدعاء AppErrors في حالة التعطل أو ANR.

  private boolean makeAppCrashingLocked(ProcessRecord app, String shortMsg, String longMsg, String stackTrace) { app.crashing = true; app.crashingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); startAppProblemLocked(app); app.stopFreezingAllLocked(); return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace); } private void makeAppNotRespondingLocked(ProcessRecord app, String activity, String shortMsg, String longMsg) { app.notResponding = true; app.notRespondingReport = generateProcessError(app, ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING, activity, shortMsg, longMsg, null); startAppProblemLocked(app); app.stopFreezingAllLocked(); } 

الأسباب الرئيسية لل ANR


  • قفل الإدخال / الإخراج
  • ازدحام الشبكة
  • حظر الصفحات
  • حلقة لا نهاية لها
  • منطق العمل يستغرق وقتا طويلا

تجنب ANR


  • مؤشر ترابط واجهة المستخدم الرئيسية ينفذ المنطق المقترن فقط بواجهة المستخدم ؛
  • يتم إجراء العمليات الحسابية المعقدة (على سبيل المثال ، عمليات قواعد البيانات وعمليات الإدخال والإخراج وعمليات الشبكة ، وما إلى ذلك) في دفق منفصل ؛
  • استخدم معالج التفاعل بين مؤشر ترابط واجهة المستخدم وسير العمل ؛
  • استخدام RxJava الخ للتعامل مع العمليات غير المتزامنة.

كيفية التقاط ANR


  • يمكن تخزين معلومات حول ANR في الملف /data/anr/traces.txt ، أو في مسار مختلف / data / anr / anr_ *. يمكنك الحصول عليها باستخدام الأوامر التالية:

     adb root adb shell ls /data/anr adb pull /data/anr/<filename> 
  • استخدم ANR-WatchDog مشروع مفتوح المصدر لاكتشاف ANR
  • انظر تجنب ANR :)

ملاحظة: أنشر جميع الاختيارات في قناة التلغراف @ paradisecurity .

Source: https://habr.com/ru/post/ar439086/


All Articles