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

Mulailah 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 rekaman aktivitas tersebut di Perfetto UI.

Menganalisis rekaman aktivitas

Karena ANR memicu pelacakan, Anda tahu bahwa pelacakan berakhir saat sistem mendeteksi ketidakresponsifan di thread utama aplikasi Anda. 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 yang menampilkan akhir rekaman aktivitas, yang menandai lokasi pemicu ANR.
Gambar 2. Lokasi pemicu ANR.

Rekaman aktivitas juga menampilkan 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. Kueri ini menggunakan waktu CPU selama 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 Anda hanya mengandalkan pelacakan tumpukan pada saat ANR untuk proses debug, Anda mungkin salah mengatribusikan ANR sepenuhnya ke kode yang berjalan dalam slice rekaman aktivitas handleNetworkResponse yang belum berakhir saat profil selesai direkam. Namun, 1,48 detik tidak cukup untuk memicu ANR dengan sendirinya, meskipun ini adalah operasi yang mahal. Anda perlu melihat lebih jauh ke belakang untuk memahami apa yang memblokir thread utama sebelum metode ini.

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

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

Untuk memahami pemblokiran, perkecil untuk memeriksa seluruh slice 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 menyoroti 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 polanya.

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. Sleep keempat yang merupakan pencilan (Gambar 9) adalah 0,7 detik. Durasi tepat 2 detik jarang terjadi secara kebetulan di lingkungan komputasi. Hal ini sangat menunjukkan waktu tunggu yang diprogram, bukan pertentangan resource acak. Tidur terakhir mungkin disebabkan oleh thread yang menyelesaikan penantiannya karena operasi yang ditunggunya berhasil.

Hipotesisnya adalah aplikasi mencapai waktu tunggu yang ditentukan pengguna selama 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 memverifikasi hal ini, periksa kode yang terkait dengan bagian trace 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();
}

Kode ini mengonfirmasi hipotesis tersebut. Metode onClickSubmit menjalankan permintaan jaringan pada UI thread dengan NETWORK_TIMEOUT_MILLISECS hardcode sebesar 2.000 md. Yang terpenting, fungsi ini berjalan di dalam loop while yang mencoba ulang 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). Upaya keempat berhasil setelah 0,7 detik, sehingga kode dapat melanjutkan ke handleNetworkResponse. Namun, akumulasi waktu tunggu telah 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 jenis ANR ini.