مثال عملی اشکال‌زدایی عملکرد: ANR

این بخش نحوه اشکال‌زدایی یک برنامه در حال عدم پاسخگویی (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 مطابقت دارد.

رابط کاربری Perfetto که پایان یک مسیر را نشان می‌دهد و محل فعال‌سازی ANR را برجسته می‌کند.
شکل ۲. محل فعال‌سازی ANR.

این ردیابی همچنین عملیات‌هایی را که برنامه هنگام وقوع ANR در حال اجرا بوده است، نشان می‌دهد. به طور خاص، برنامه کدی را در برش ردیابی handleNetworkResponse اجرا کرده است. این برش درون برش MyApp:SubmitButton قرار داشت. این برش ۱.۴۸ ثانیه از زمان CPU را مصرف کرده است (شکل ۳).

رابط کاربری Perfetto زمان مصرف CPU توسط اجرای handleNetworkResponse را در زمان ANR نشان می‌دهد.
شکل ۳. اجرا در زمان ANR.

اگر در لحظه ANR برای اشکال‌زدایی صرفاً به ردیابی‌های پشته تکیه کنید، ممکن است به اشتباه ANR را کاملاً به کدی که در حال اجرا در برش ردیابی handleNetworkResponse است نسبت دهید که هنگام پایان ضبط پروفایل هنوز پایان نیافته است. با این حال، ۱.۴۸ ثانیه برای راه‌اندازی یک ANR به خودی خود کافی نیست، حتی اگر عملیاتی پرهزینه باشد. برای درک اینکه چه چیزی قبل از این روش، نخ اصلی را مسدود کرده است، باید به گذشته نگاه کنید.

برای یافتن نقطه شروعی برای جستجوی علت ANR، بررسی آخرین فریم تولید شده توسط نخ رابط کاربری را آغاز می‌کنیم که مربوط به برش Choreographer#doFrame 551275 است و قبل از شروع برش MyApp:SubmitButton که به ANR ختم می‌شود، منابع تأخیر زیادی وجود ندارد (شکل ۴).

رابط کاربری بی‌نقص، آخرین فریم رندر شده توسط رابط کاربری قبل از ANR را نشان می‌دهد.
شکل ۴. آخرین فریم برنامه تولید شده قبل از ANR.

برای درک انسداد، تصویر را کوچک کنید تا برش کامل MyApp:SubmitButton را بررسی کنید. همانطور که در شکل 4 نشان داده شده است، متوجه یک جزئیات مهم در حالت‌های نخ خواهید شد: نخ 75٪ از زمان (6.7 ثانیه) را در حالت Sleeping و تنها 24٪ از زمان را در حالت Running گذرانده است.

رابط کاربری Perfetto وضعیت نخ‌ها را در حین عملیات نشان می‌دهد و ۷۵٪ زمان خواب و ۲۴٪ زمان اجرا را برجسته می‌کند.
شکل ۵. حالت‌های نخ در حین عملیات `MyApp:SubmitButton`.

این نشان می‌دهد که علت اصلی ANR انتظار بوده است، نه محاسبه. برای یافتن الگو، رویدادهای خواب را به صورت جداگانه بررسی کنید.

رابط کاربری Perfetto اولین بازه زمانی غیرفعال را در برش ردیابی MyAppSubmitButton نشان می‌دهد.
شکل ۶. اولین زمان غیرفعال شدن دکمه‌ی ارسال برنامه‌ی «MyAppSubmitButton»
رابط کاربری Perfetto که دومین بازه زمانی غیرفعال را در برش ردیابی MyAppSmitButton نشان می‌دهد.
شکل ۷. زمان خواب دوم در `MyAppSubmitButton`.
رابط کاربری Perfetto که سومین بازه زمانی غیرفعال را در برش ردیابی MyAppSubmitButton نشان می‌دهد.
شکل ۸. سومین زمان خواب در `MyAppSubmitButton`.
رابط کاربری Perfetto که چهارمین بازه زمانی غیرفعال را در برش ردیابی MyAppSmitButton نشان می‌دهد.
شکل ۹. چهارمین زمان غیرفعال شدن دکمه‌ی «MyAppSubmitButton»

سه وقفه خواب اول (شکل‌های ۶ تا ۸) تقریباً یکسان هستند، هر کدام تقریباً ۲ ثانیه. یک وقفه خواب چهارم (شکل ۹) ۰.۷ ثانیه است. مدت زمان دقیقاً ۲ ثانیه به ندرت در یک محیط محاسباتی تصادفی است. این موضوع قویاً نشان‌دهنده یک وقفه برنامه‌ریزی‌شده است، نه یک تداخل تصادفی در منابع. آخرین خواب ممکن است ناشی از پایان انتظار نخ به دلیل موفقیت عملیاتی باشد که در انتظار آن بوده است.

این فرضیه این است که برنامه چندین بار به زمان انقضای ۲ ثانیه‌ای تعریف‌شده توسط کاربر رسیده و در نهایت موفق شده و تأخیر کافی برای فعال‌سازی ANR ایجاد کرده است.

رابط کاربری Perfetto خلاصه‌ای از تأخیرها را در طول برش ردیابی MyApp:SubmitButton نشان می‌دهد که نشان‌دهنده چندین فاصله خواب ۲ ثانیه‌ای است.
شکل ۱۰. خلاصه‌ای از تأخیرها در طول برش `MyApp:SubmitButton`.

برای تأیید این موضوع، کد مرتبط با بخش ردیابی 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 به صورت کد ثابت با مدت زمان ۲۰۰۰ میلی‌ثانیه اجرا می‌کند. نکته مهم این است که این متد درون یک حلقه while اجرا می‌شود که تا ۱۰ بار تلاش مجدد انجام می‌دهد.

در این ردیابی خاص، کاربر احتمالاً اتصال شبکه ضعیفی داشته است. سه تلاش اول ناموفق بود و باعث سه وقفه ۲ ثانیه‌ای (در مجموع ۶ ثانیه) شد. تلاش چهارم پس از ۰.۷ ثانیه موفقیت‌آمیز بود و به کد اجازه داد تا به handleNetworkResponse ) ادامه دهد. با این حال، زمان انتظار انباشته شده، ANR را فعال کرده بود.

برای جلوگیری از این نوع ANR، عملیات مرتبط با شبکه که تأخیرهای متفاوتی دارند را به جای اجرای آنها در نخ اصلی، در یک نخ پس‌زمینه قرار دهید. این کار به رابط کاربری اجازه می‌دهد حتی با اتصال ضعیف نیز پاسخگو باقی بماند و این دسته از ANRها را به طور کامل از بین ببرد.