Contoh praktis proses debug performa: ANR

Bagian ini menunjukkan cara men-debug Aplikasi Tidak Merespons (ANR) menggunakan ProfilingManager dengan contoh rekaman aktivitas.

Menyiapkan aplikasi untuk mengumpulkan ANR

Mulai dengan menyiapkan pemicu ANR di aplikasi Anda:

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);
}

Setelah merekam dan mengupload rekaman aktivitas ANR, buka di UI Perfetto.

Menganalisis rekaman aktivitas

Karena ANR memicu rekaman aktivitas, Anda tahu bahwa rekaman aktivitas berakhir saat sistem mendeteksi aplikasi Anda tidak merespons di thread utama. Gambar 1 menunjukkan cara membuka thread utama aplikasi Anda yang diberi tag sesuai dalam UI.

Navigasi UI Perfetto ke thread utama aplikasi.
Gambar 1. Navigasi ke thread utama aplikasi.

Akhir rekaman aktivitas cocok dengan stempel waktu ANR, seperti yang ditunjukkan pada Gambar 2.

UI Perfetto menampilkan akhir rekaman aktivitas, menandai lokasi pemicu ANR.
Gambar 2. Lokasi pemicu ANR.

Rekaman aktivitas juga menunjukkan operasi yang dijalankan aplikasi saat ANR terjadi. Secara khusus, aplikasi menjalankan kode di slice rekaman aktivitas handleNetworkResponse. Slice ini berada di dalam slice MyApp:SubmitButton. Proses ini menggunakan waktu CPU 1,48 detik (Gambar 3).

UI Perfetto yang menampilkan waktu CPU yang digunakan oleh eksekusi handleNetworkResponse
 pada saat ANR.
Gambar 3. Eksekusi pada saat ANR.

Jika hanya mengandalkan pelacakan tumpukan pada saat ANR terjadi untuk proses debug, Anda mungkin salah mengatribusikan ANR sepenuhnya ke kode yang berjalan dalam irisan rekaman aktivitas handleNetworkResponse yang belum berakhir saat profil selesai merekam. Namun, 1,48 detik tidak cukup untuk memicu ANR dengan sendirinya, meskipun merupakan operasi yang mahal. Anda perlu melihat lebih jauh ke belakang untuk memahami apa yang memblokir thread utama sebelum metode ini.

Untuk mendapatkan titik awal dalam mencari penyebab ANR, kita mulai mencari setelah frame terakhir yang dihasilkan oleh thread UI yang sesuai dengan irisan Choreographer#doFrame 551275 dan tidak ada sumber penundaan besar sebelum memulai irisan MyApp:SubmitButton yang berakhir dengan ANR (Gambar 4).

UI Perfetto yang menampilkan frame terakhir yang dirender oleh thread UI sebelum ANR.
Gambar 4. Frame aplikasi terakhir dibuat sebelum ANR.

Untuk memahami pemblokiran, perkecil tampilan untuk memeriksa seluruh irisan MyApp:SubmitButton. Anda akan melihat detail penting dalam status thread, seperti yang ditunjukkan pada Gambar 4: thread menghabiskan 75% waktu (6,7 detik) dalam status Sleeping dan hanya 24% waktu dalam status Running.

UI Perfetto yang menampilkan status thread selama operasi, yang menandai 75% waktu tidur dan 24% waktu berjalan.
Gambar 5. Status thread selama operasi `MyApp:SubmitButton`.

Hal ini menunjukkan bahwa penyebab utama ANR adalah menunggu, bukan komputasi. Periksa setiap kejadian tidur untuk menemukan pola.

UI Perfetto yang menampilkan interval tidur pertama dalam slice rekaman aktivitas MyAppSubmitButton.
Gambar 6. Waktu tidur pertama dalam `MyAppSubmitButton`.
UI Perfetto yang menampilkan interval tidur kedua dalam slice rekaman aktivitas MyAppSubmitButton.
Gambar 7. Waktu tidur kedua dalam `MyAppSubmitButton`.
UI Perfetto yang menampilkan interval tidur ketiga dalam slice rekaman aktivitas MyAppSubmitButton.
Gambar 8. Waktu tidur ketiga dalam `MyAppSubmitButton`.
UI Perfetto yang menampilkan interval tidur keempat dalam slice rekaman aktivitas MyAppSubmitButton.
Gambar 9. Waktu tidur keempat dalam `MyAppSubmitButton`.

Tiga interval tidur pertama (Gambar 6–8) hampir identik, masing-masing sekitar 2 detik. Tidur keempat yang tidak biasa (Gambar 9) adalah 0,7 detik. Durasi tepat 2 detik jarang merupakan kebetulan dalam lingkungan komputasi. Hal ini menunjukkan waktu tunggu yang diprogram, bukan persaingan sumber daya acak. Tidur terakhir mungkin disebabkan oleh thread yang menyelesaikan penungguannya karena operasi yang ditunggunya berhasil.

Hipotesis ini adalah bahwa aplikasi mencapai waktu tunggu yang ditentukan pengguna sebesar 2 detik beberapa kali dan akhirnya berhasil, sehingga menyebabkan penundaan yang cukup untuk memicu ANR.

UI Perfetto yang menampilkan ringkasan penundaan selama slice rekaman aktivitas MyApp:SubmitButton, yang menunjukkan beberapa interval tidur 2 detik.
Gambar 10. Ringkasan penundaan selama slice `MyApp:SubmitButton`.

Untuk memverifikasinya, periksa kode yang terkait dengan bagian rekaman aktivitas 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();
}

Kode tersebut mengonfirmasi hipotesis ini. Metode onClickSubmit menjalankan permintaan jaringan di UI thread dengan NETWORK_TIMEOUT_MILLISECS yang dikodekan secara permanen sebesar 2000 md. Yang penting, fungsi ini berjalan di dalam loop while yang mencoba lagi hingga 10 kali.

Dalam rekaman aktivitas khusus ini, pengguna kemungkinan memiliki konektivitas jaringan yang buruk. Tiga upaya pertama gagal, sehingga menyebabkan tiga waktu tunggu 2 detik (total 6 detik). Percobaan keempat berhasil setelah 0,7 detik, sehingga kode dapat dilanjutkan ke handleNetworkResponse. Namun, waktu tunggu yang terakumulasi sudah memicu ANR.

Hindari jenis ANR ini dengan menempatkan operasi terkait jaringan yang memiliki latensi bervariasi ke dalam thread latar belakang, bukan mengeksekusinya di thread utama. Hal ini memungkinkan UI tetap responsif meskipun konektivitas buruk, sehingga sepenuhnya menghilangkan class ANR ini.