Bergabunglah bersama kami di ⁠#Android11: The Beta Launch Show pada tanggal 3 Juni!

ANR

Saat UI thread aplikasi Android diblokir terlalu lama, error "Aplikasi Tidak Merespons" (ANR) akan dipicu. Jika aplikasi digunakan di latar depan, sistem menampilkan dialog kepada pengguna, seperti ditunjukkan di gambar 1. Dialog ANR memberikan kesempatan kepada pengguna untuk memaksa menghentikan aplikasi.

Gambar 1. Dialog ANR yang ditampilkan kepada pengguna

Gambar 1. Dialog ANR yang ditampilkan kepada pengguna

ANR adalah masalah yang muncul karena thread utama aplikasi, yang bertanggung jawab untuk mengupdate UI, tidak dapat memproses peristiwa masukan pengguna atau melakukan drawing, sehingga dapat membuat pengguna merasa frustrasi. Untuk informasi selengkapnya tentang thread utama aplikasi, lihat Proses dan thread.

ANR akan dipicu untuk aplikasi jika salah satu kondisi berikut terjadi:

  • Saat aktivitas berada di latar depan, aplikasi tidak merespons peristiwa input atau BroadcastReceiver (seperti peristiwa menekan tombol atau menyentuh layar) dalam 5 detik.
  • Meski Anda tidak memiliki aktivitas di latar depan, BroadcastReceiver belum selesai mengeksekusi dalam waktu yang cukup lama.

Jika aplikasi mengalami ANR, Anda dapat menggunakan pedoman di artikel ini untuk mendiagnosis dan memperbaiki masalahnya.

Mendeteksi dan mendiagnosis masalah

Android menyediakan beberapa cara untuk memberi tahu Anda bahwa aplikasi bermasalah, serta membantu Anda mendiagnosisnya. Jika Anda telah memublikasikan aplikasi, Android vitals dapat memberi tahu Anda bahwa terjadi masalah, dan ada alat diagnostik untuk membantu Anda menemukan masalahnya.

Android vitals

Android vitals dapat membantu menyempurnakan performa aplikasi dengan memberi tahu Anda, melalui Konsol Play, ketika aplikasi menampilkan ANR berlebihan. Android vitals menganggap ANR berlebihan jika aplikasi:

  • Menampilkan minimal satu ANR dalam minimal 0,47% sesi hariannya.
  • Menampilkan 2 atau lebih ANR dalam minimal 0,24% sesi hariannya.

Sesi harian mengacu pada tanggal selama aplikasi Anda digunakan.

Untuk informasi tentang cara Google Play mengumpulkan data Android vitals, lihat dokumentasi Konsol Play.

Mendiagnosis ANR

Ada beberapa pola umum yang harus dicari saat mendiagnosis ANR:

  1. Aplikasi melakukan operasi lambat yang melibatkan I/O di thread utama.
  2. Aplikasi melakukan perhitungan yang lama di thread utama.
  3. Thread utama melakukan panggilan pengikat sinkron ke proses lain, dan proses lain tersebut memerlukan waktu yang lama untuk kembali.
  4. Thread utama diblokir menunggu blok yang disinkronkan untuk operasi panjang yang terjadi di thread lain.
  5. Thread utama mengalami deadlock dengan thread lain, baik dalam proses Anda atau melalui panggilan binder. Thread utama tidak hanya menunggu operasi yang panjang selesai, tetapi dalam situasi deadlock. Untuk informasi selengkapnya, lihat Deadlock di Wikipedia.

Teknik berikut dapat membantu Anda mencari tahu penyebab mana yang mengakibatkan ANR.

Mode ketat

Menggunakan StrictMode akan membantu Anda menemukan operasi I/O yang tidak disengaja di thread utama saat Anda mengembangkan aplikasi. Anda dapat menggunakan StrictMode di tingkat aplikasi atau aktivitas.

Mengaktifkan dialog ANR latar belakang

Android menampilkan dialog ANR untuk aplikasi yang memerlukan waktu terlalu lama untuk memproses pesan siaran hanya jika Tampilkan semua ANR diaktifkan di Opsi developer pada perangkat. Karena alasan ini, dialog ANR latar belakang tidak selalu ditampilkan kepada pengguna, tetapi aplikasi tetap dapat mengalami masalah performa.

Traceview

Anda dapat menggunakan Traceview untuk mendapatkan rekaman aktivitas aplikasi yang sedang berjalan saat memeriksa kasus penggunaan dan mengidentifikasi tempat terjadinya kesibukan thread utama. Untuk informasi tentang cara menggunakan Traceview, lihat Pemrofilan dengan Traceview dan dmtracedump.

Mengambil file rekaman aktivitas

Android menyimpan informasi rekaman aktivitas saat mengalami ANR. Di OS versi lama, ada satu file /data/anr/traces.txt di perangkat. Di OS versi baru, ada beberapa file /data/anr/anr_*. Anda dapat mengakses rekaman aktivitas ANR dari perangkat atau emulator dengan menggunakan Android Debug Bridge (adb) sebagai root:

adb root
    adb shell ls /data/anr
    adb pull /data/anr/<filename>
    

Anda dapat memperoleh laporan bug dari perangkat fisik dengan menggunakan opsi developer Ambil laporan bug di perangkat, atau perintah adb bugreport pada komputer pengembangan. Untuk informasi selengkapnya, lihat Merekam dan membaca laporan bug.

Memperbaiki masalah

Setelah mengidentifikasi masalah, Anda dapat menggunakan tips di bagian ini untuk memperbaiki masalah yang umum ditemukan.

Kode lambat di thread utama

Identifikasi tempat di kode tempat thread utama aplikasi sibuk selama lebih dari 5 detik. Cari kasus penggunaan yang mencurigakan di aplikasi dan coba reproduksi ANR.

Misalnya, Gambar 2 menunjukkan linimasa Traceview tempat thread utama sibuk selama lebih dari 5 detik.

Gambar 2. Linimasa Traceview yang menunjukkan thread utama yang sibuk

Gambar 2. Linimasa Traceview yang menunjukkan thread utama yang sibuk

Gambar 2 menunjukkan kepada kita bahwa sebagian besar kode yang mengganggu terjadi di pengendali onClick(View), seperti yang ditunjukkan dalam contoh kode berikut:

Kotlin

    override fun onClick(v: View) {
        // This task runs on the main thread.
        BubbleSort.sort(data)
    }
    

Java

    @Override
    public void onClick(View view) {
        // This task runs on the main thread.
        BubbleSort.sort(data);
    }
    

Dalam kasus ini, Anda harus memindahkan pekerjaan yang berjalan di thread utama ke thread pekerja. Android Framework mencakup class yang dapat membantu memindahkan tugas ke thread pekerja, untuk informasi selengkapnya, lihat Class helper untuk threading. Kode berikut menunjukkan cara menggunakan class helper AsyncTask untuk memproses tugas di thread pekerja:

Kotlin

    override fun onClick(v: View) {
        // The long-running operation is run on a worker thread
        object : AsyncTask<Array<Int>, Int, Long>() {
            override fun doInBackground(vararg params: Array<Int>): Long? =
                BubbleSort.sort(params[0])

        }.execute(data)
    }
    

Java

    @Override
    public void onClick(View view) {
       // The long-running operation is run on a worker thread
       new AsyncTask<Integer[], Integer, Long>() {
           @Override
           protected Long doInBackground(Integer[]... params) {
               BubbleSort.sort(params[0]);
           }
       }.execute(data);
    }
    

Traceview menunjukkan bahwa sebagian besar kode berjalan di thread pekerja, seperti ditunjukkan di Gambar 3. Thread utama tersedia untuk merespons peristiwa pengguna.

Gambar 3. Linimasa Traceview yang menunjukkan pekerjaan yang ditangani oleh thread pekerja

Gambar 3. Linimasa Traceview yang menunjukkan pekerjaan yang ditangani oleh thread pekerja

IO di thread utama

Mengeksekusi operasi IO di thread utama adalah penyebab umum operasi yang lambat di thread utama, yang dapat menyebabkan ANR. Sebaiknya pindahkan semua operasi IO ke thread pekerja, seperti yang ditunjukkan di bagian sebelumnya.

Beberapa contoh operasi IO adalah operasi jaringan dan penyimpanan. Untuk informasi selengkapnya, lihat Melakukan operasi jaringan dan Menyimpan data.

Pertentangan kunci

Di beberapa skenario, pekerjaan yang menyebabkan ANR tidak secara langsung dieksekusi di thread utama aplikasi. Jika thread pekerja memegang kunci pada resource yang diperlukan oleh thread utama untuk menyelesaikan pekerjaannya, ANR mungkin terjadi.

Misalnya, Gambar 4 menunjukkan linimasa Traceview tempat sebagian besar pekerjaan dilakukan di thread pekerja.

Gambar 4. Linimasa Traceview yang menunjukkan pekerjaan yang sedang dieksekusi pada thread pekerja

Gambar 4. Linimasa Traceview yang menunjukkan pekerjaan yang sedang dieksekusi pada thread pekerja

Akan tetapi, jika pengguna masih mengalami ANR, sebaiknya lihat status thread utama di Android Device Monitor. Biasanya, thread utama akan berada dalam status RUNNABLE jika sudah siap untuk mengupdate UI dan umumnya responsif.

Namun, jika thread utama tidak dapat melanjutkan eksekusi, statusnya BLOCKED dan tidak dapat merespons peristiwa. Status ditunjukkan di Android Device Monitor sebagai Monitor atau Wait, seperti yang ditunjukkan dalam gambar 5

Gambar 5. Thread utama dalam status Monitor

Gambar 5. Thread utama dalam status Monitor

Trace berikut menunjukkan thread utama aplikasi yang diblokir menunggu resource:

...
    AsyncTask #2" prio=5 tid=18 Runnable
      | group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
      | sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
      | state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
      | stack=0x94a7e000-0x94a80000 stackSize=1038KB
      | held mutexes= "mutator lock"(shared held)
      at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
      at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
      - locked <0x083105ee> (a java.lang.Boolean)
      at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
      at android.os.AsyncTask$2.call(AsyncTask.java:305)
      at java.util.concurrent.FutureTask.run(FutureTask.java:237)
      at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
      at java.lang.Thread.run(Thread.java:761)
    ...
    

Meninjau trace dapat membantu Anda mencari kode yang memblokir thread utama. Kode berikut bertanggung jawab untuk memegang kunci yang memblokir thread utama di trace sebelumnya:

Kotlin

    override fun onClick(v: View) {
        // The worker thread holds a lock on lockedResource
        LockTask().execute(data)

        synchronized(lockedResource) {
            // The main thread requires lockedResource here
            // but it has to wait until LockTask finishes using it.
        }
    }

    class LockTask : AsyncTask<Array<Int>, Int, Long>() {
        override fun doInBackground(vararg params: Array<Int>): Long? =
                synchronized(lockedResource) {
                    // This is a long-running operation, which makes
                    // the lock last for a long time
                    BubbleSort.sort(params[0])
                }
    }
    

Java

    @Override
    public void onClick(View v) {
        // The worker thread holds a lock on lockedResource
       new LockTask().execute(data);

       synchronized (lockedResource) {
           // The main thread requires lockedResource here
           // but it has to wait until LockTask finishes using it.
       }
    }

    public class LockTask extends AsyncTask<Integer[], Integer, Long> {
       @Override
       protected Long doInBackground(Integer[]... params) {
           synchronized (lockedResource) {
               // This is a long-running operation, which makes
               // the lock last for a long time
               BubbleSort.sort(params[0]);
           }
       }
    }
    

Contoh lain adalah thread utama aplikasi yang menunggu hasil dari thread pekerja, seperti yang ditunjukkan dalam kode berikut. Perlu diperhatikan bahwa penggunaan wait() dan notify() bukan pola yang direkomendasikan di Kotlin, yang memiliki mekanismenya sendiri untuk menangani kondisi serentak. Saat menggunakan Kotlin, sebaiknya gunakan mekanisme khusus Kotlin jika memungkinkan.

Kotlin

    fun onClick(v: View) {
        val lock = java.lang.Object()
        val waitTask = WaitTask(lock)
        synchronized(lock) {
            try {
                waitTask.execute(data)
                // Wait for this worker thread’s notification
                lock.wait()
            } catch (e: InterruptedException) {
            }
        }
    }

    internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
        override fun doInBackground(vararg params: Array<Int>): Long? {
            synchronized(lock) {
                BubbleSort.sort(params[0])
                // Finished, notify the main thread
                lock.notify()
            }
        }
    }
    

Java

    public void onClick(View v) {
       WaitTask waitTask = new WaitTask();
       synchronized (waitTask) {
           try {
               waitTask.execute(data);
               // Wait for this worker thread’s notification
               waitTask.wait();
           } catch (InterruptedException e) {}
       }
    }

    class WaitTask extends AsyncTask<Integer[], Integer, Long> {
       @Override
       protected Long doInBackground(Integer[]... params) {
           synchronized (this) {
               BubbleSort.sort(params[0]);
               // Finished, notify the main thread
               notify();
           }
       }
    }
    

Ada beberapa situasi lain yang dapat memblokir thread utama, termasuk thread yang menggunakan Lock, Semaphore, serta kumpulan resource (seperti kumpulan koneksi database) atau mekanisme pengecualian bersama (mutex) lainnya.

Anda harus mengevaluasi kunci yang ditahan oleh aplikasi di resource secara umum, tetapi jika ingin menghindari ANR, sebaiknya lihat kunci yang ditahan untuk resource yang diperlukan oleh thread utama.

Pastikan bahwa kunci ditahan dalam waktu yang paling sedikit, atau bahkan, evaluasi apakah aplikasi perlu menahan kunci. Jika Anda menggunakan kunci guna menentukan waktu untuk mengupdate UI berdasarkan pemrosesan thread pekerja, gunakan mekanisme seperti onProgressUpdate() dan onPostExecute() untuk melakukan komunikasi antara thread pekerja dan utama.

Deadlock

Deadlock terjadi saat thread memasuki status menunggu karena resource yang diperlukan ditahan oleh thread lain, yang juga menunggu resource yang ditahan oleh thread pertama. Jika thread utama aplikasi mengalami situasi ini, ANR mungkin terjadi.

Deadlock adalah fenomena yang dipelajari dengan baik dalam ilmu komputer, dan terdapat algoritme pencegahan deadlock yang dapat Anda gunakan untuk menghindari deadlock.

Untuk informasi selengkapnya, lihat Deadlock dan Algoritme pencegahan deadlock di Wikipedia.

Penerima siaran lambat

Aplikasi dapat merespons pesan siaran, seperti menonaktifkan atau menonaktifkan mode pesawat atau perubahan status konektivitas, melalui penerima siaran. ANR terjadi saat aplikasi memerlukan waktu yang terlalu lama untuk memproses pesan siaran.

ANR terjadi dalam kasus-kasus berikut:

Aplikasi Anda hanya dapat menjalankan operasi pendek dalam metode onReceive() BroadcastReceiver. Namun, jika aplikasi memerlukan pemrosesan yang lebih kompleks karena pesan siaran, Anda harus mengalihkan tugas ke IntentService.

Anda dapat menggunakan alat seperti Traceview untuk mengidentifikasi apakah penerima siaran mengeksekusi operasi yang berjalan lama di thread utama aplikasi. Misalnya, Gambar 6 menunjukkan linimasa penerima siaran yang memproses pesan di thread utama selama sekitar 100 detik.

Gambar 6. Linimasa Traceview yang menunjukkan pekerjaan BroadcastReceiver pada thread utama

Gambar 6. Linimasa Traceview yang menunjukkan pekerjaan BroadcastReceiver pada thread utama

Perilaku ini dapat disebabkan oleh eksekusi operasi yang berjalan lama pada metode onReceive() BroadcastReceiver, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

    override fun onReceive(context: Context, intent: Intent) {
        // This is a long-running operation
        BubbleSort.sort(data)
    }
    

Java

    @Override
    public void onReceive(Context context, Intent intent) {
        // This is a long-running operation
        BubbleSort.sort(data);
    }
    

Dalam situasi seperti ini, sebaiknya pindahkan operasi yang berjalan lama ke IntentService karena menggunakan thread pekerja untuk mengeksekusi pekerjaannya. Kode berikut menunjukkan cara menggunakan IntentService untuk memproses operasi yang berjalan lama:

Kotlin

    override fun onReceive(context: Context, intent: Intent) {
        Intent(context, MyIntentService::class.java).also { intentService ->
            // The task now runs on a worker thread.
            context.startService(intentService)
        }
    }

    class MyIntentService : IntentService("MyIntentService") {
        override fun onHandleIntent(intent: Intent?) {
            BubbleSort.sort(data)
        }
    }
    

Java

    @Override
    public void onReceive(Context context, Intent intent) {
        // The task now runs on a worker thread.
        Intent intentService = new Intent(context, MyIntentService.class);
        context.startService(intentService);
    }

    public class MyIntentService extends IntentService {
       @Override
       protected void onHandleIntent(@Nullable Intent intent) {
           BubbleSort.sort(data);
       }
    }
    

Akibat menggunakan IntentService, operasi yang berjalan lama dieksekusi di thread pekerja, bukan di thread utama. Gambar 7 menunjukkan pekerjaan yang dialihkan ke thread pekerja di linimasa Traceview.

Gambar 7. Linimasa Traceview yang menunjukkan pesan siaran yang diproses di thread pekerja

Gambar 7. Linimasa Traceview yang menunjukkan pesan siaran yang diproses di thread pekerja

Penerima siaran dapat menggunakan goAsync() untuk memberikan tanda kepada sistem bahwa diperlukan lebih banyak waktu untuk memproses pesan. Akan tetapi, Anda harus memanggil finish() pada objek PendingResult. Contoh berikut menunjukkan cara memanggil finish() untuk mengizinkan sistem mendaur ulang penerima siaran dan menghindari ANR:

Kotlin

    val pendingResult = goAsync()

    object : AsyncTask<Array<Int>, Int, Long>() {
        override fun doInBackground(vararg params: Array<Int>): Long? {
            // This is a long-running operation
            BubbleSort.sort(params[0])
            pendingResult.finish()
            return 0L
        }
    }.execute(data)
    

Java

    final PendingResult pendingResult = goAsync();
    new AsyncTask<Integer[], Integer, Long>() {
       @Override
       protected Long doInBackground(Integer[]... params) {
           // This is a long-running operation
           BubbleSort.sort(params[0]);
           pendingResult.finish();
       }
    }.execute(data);
    

Namun, memindahkan kode dari penerima siaran lambat ke thread lain dan menggunakan goAsync() tidak akan memperbaiki ANR jika siaran berada di latar belakang. Waktu tunggu ANR masih berlaku.

Untuk informasi selengkapnya tentang ANR, lihat Menjaga aplikasi Anda tetap responsif. Untuk informasi selengkapnya tentang thread, lihat Performa threading.