Menjaga aplikasi Anda tetap responsif

Gambar 1. Dialog ANR yang ditampilkan kepada pengguna.

Anda mungkin saja menulis kode yang mampu memenangkan semua tes performa di dunia, tetapi masih bekerja dengan lamban, tidak responsif, membeku selama periode yang cukup panjang, atau memakan waktu yang terlalu lama untuk memproses input. Masalah terburuk yang dapat terjadi pada daya respons aplikasi Anda adalah dialog "Aplikasi Tidak Merespons" (ANR).

Di Android, sistem melindungi perangkat dari aplikasi yang tidak cukup responsif selama jangka waktu tertentu dengan menampilkan dialog yang memberitahukan bahwa aplikasi berhenti merespons, seperti dialog pada Gambar 1. Pada tahap ini, aplikasi Anda tidak responsif selama jangka waktu yang cukup lama sehingga sistem menawarkan opsi kepada pengguna untuk keluar dari aplikasi tersebut. Sangat penting untuk mendesain responsivitas ke dalam aplikasi Anda sehingga sistem tidak pernah menampilkan dialog ANR kepada pengguna.

Dokumen ini menjelaskan cara sistem Android menentukan apakah aplikasi merespons atau tidak, dan memberikan panduan untuk memastikan aplikasi akan selalu responsif.

Apa yang memicu ANR?

Secara umum, sistem akan menampilkan ANR jika aplikasi tidak dapat merespons input pengguna. Misalnya, ketika aplikasi memblokir beberapa operasi I/O (sering kali berupa akses jaringan) di thread UI sehingga sistem tidak dapat memproses aktivitas input pengguna yang masuk. Contoh lainnya, aplikasi memakan waktu terlalu lama membangun struktur dalam memori yang rumit atau menghitung langkah game selanjutnya di thread UI. Penting untuk selalu memastikan penghitungan ini bekerja secara efisien, meskipun kode yang paling efisien tetap memerlukan waktu untuk dijalankan.

Di setiap situasi yang mengakibatkan aplikasi melakukan operasi yang mungkin akan berjalan lama, jangan lakukan operasi di thread UI. Sebagai gantinya, Anda dapat membuat thread worker dan melakukan sebagian operasi di sana. Tindakan ini akan menjaga thread UI (yang menggerakkan loop aktivitas antarmuka pengguna) tetap berjalan dan mencegah sistem menyimpulkan bahwa kode Anda telah dibekukan. Ini dikarenakan threading semacam itu biasanya dilakukan di level class, dan Anda dapat menganggap responsivitas sebagai masalah class. (Bandingkan ini dengan performa kode dasar yang merupakan masalah di level metode.)

Di Android, daya respons aplikasi dipantau oleh layanan sistem Activity Manager dan Window Manager. Android akan menampilkan dialog ANR untuk aplikasi tertentu saat mendeteksi salah satu kondisi berikut:

  • Tidak ada respons terhadap peristiwa input (seperti peristiwa sentuh layar atau tombol) dalam lima detik.
  • BroadcastReceiver belum selesai dijalankan dalam 10 detik.

Cara menghindari ANR

Secara default, aplikasi Android biasanya berjalan sepenuhnya di satu thread, yaitu "thread UI" atau "thread utama"). Hal ini berarti semua operasi aplikasi Anda di thread UI yang memerlukan waktu penyelesaian yang lama dapat memicu dialog ANR karena aplikasi tidak memiliki kesempatan untuk menangani aktivitas input atau broadcast intent.

Oleh karena itu, setiap metode yang berjalan di UI thread harus melakukan operasi seminimal mungkin di thread tersebut. Khususnya, aktivitas harus diminimalkan untuk menyiapkan metode siklus proses utama seperti onCreate() dan onResume(). Operasi yang dapat berjalan lama, seperti operasi jaringan atau database, maupun penghitungan berat secara komputasi, seperti mengubah ukuran bitmap, harus dilakukan dalam thread pekerja (atau untuk operasi database, melalui konten asinkron).

Cara pembuatan thread pekerja yang paling efektif untuk operasi berdurasi lebih lama adalah dengan class AsyncTask. Cukup luaskan AsyncTask dan implementasikan metode doInBackground() untuk menjalankan tugas tersebut. Untuk memposting perubahan progres kepada pengguna, Anda dapat memanggil publishProgress(), yang akan memanggil metode callback onProgressUpdate(). Dari implementasi onProgressUpdate() (yang berjalan pada UI thread), Anda dapat memberi tahu pengguna. Contoh:

Kotlin

    private class DownloadFilesTask : AsyncTask<URL, Int, Long>() {

        // Do the long-running work in here
        override fun doInBackground(vararg urls: URL): Long? {
            val count: Float = urls.size.toFloat()
            var totalSize: Long = 0
            urls.forEachIndexed { index, url ->
                totalSize += Downloader.downloadFile(url)
                publishProgress((index / count * 100).toInt())
                // Escape early if cancel() is called
                if (isCancelled) return totalSize
            }
            return totalSize
        }

        // This is called each time you call publishProgress()
        override fun onProgressUpdate(vararg progress: Int?) {
            setProgressPercent(progress.firstOrNull() ?: 0)
        }

        // This is called when doInBackground() is finished
        override fun onPostExecute(result: Long?) {
            showNotification("Downloaded $result bytes")
        }
    }
    

Java

    private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
        // Do the long-running work in here
        protected Long doInBackground(URL... urls) {
            int count = urls.length;
            long totalSize = 0;
            for (int i = 0; i < count; i++) {
                totalSize += Downloader.downloadFile(urls[i]);
                publishProgress((int) ((i / (float) count) * 100));
                // Escape early if cancel() is called
                if (isCancelled()) break;
            }
            return totalSize;
        }

        // This is called each time you call publishProgress()
        protected void onProgressUpdate(Integer... progress) {
            setProgressPercent(progress[0]);
        }

        // This is called when doInBackground() is finished
        protected void onPostExecute(Long result) {
            showNotification("Downloaded " + result + " bytes");
        }
    }
    

Untuk menjalankan thread pekerja ini, cukup buat instance dan panggil execute():

Kotlin

    DownloadFilesTask().execute(url1, url2, url3)
    

Java

    new DownloadFilesTask().execute(url1, url2, url3);
    

Meskipun lebih rumit daripada AsyncTask, Anda sebaiknya membuat class Thread atau HandlerThread sendiri. Jika melakukannya, Anda harus menetapkan prioritas thread ke prioritas "latar belakang" dengan memanggil Process.setThreadPriority() dan meneruskan THREAD_PRIORITY_BACKGROUND. Jika tidak ditetapkan ke prioritas yang lebih rendah dengan cara ini, thread masih dapat memperlambat aplikasi karena thread tersebut beroperasi dengan prioritas yang sama seperti UI thread secara default.

Jika Anda mengimplementasikan Thread atau HandlerThread, pastikan UI thread Anda tidak memblokir selagi menunggu thread pekerja selesai—jangan panggil Thread.wait() atau Thread.sleep(). Daripada memblokir selagi menunggu thread pekerja selesai, thread utama Anda harus memberikan Handler ke thread lainnya untuk memposting kembali setelah selesai. Mendesain aplikasi Anda dengan cara ini akan memungkinkan UI thread aplikasi tetap responsif terhadap input, sehingga menghindari dialog ANR yang disebabkan oleh waktu tunggu peristiwa input selama 5 detik.

Batasan spesifik pada waktu eksekusi BroadcastReceiver menekankan apa yang ingin dilakukan oleh penerima siaran: jumlah tugas yang kecil dan terpisah di latar belakang seperti menyimpan setelan atau mendaftarkan Notification. Seperti halnya metode lain yang dipanggil di UI thread, aplikasi harus menghindari operasi atau penghitungan yang dapat berjalan lama di penerima siaran. Namun, daripada melakukan tugas intensif melalui thread pekerja, aplikasi Anda harus memulai IntentService jika tindakan yang dapat berjalan lama perlu dilakukan sebagai respons terhadap siaran intent.

Masalah umum lainnya dengan objek BroadcastReceiver terjadi jika objek tersebut terlalu sering dijalankan. Eksekusi latar belakang yang sering dapat mengurangi jumlah memori yang tersedia untuk aplikasi lain. Untuk informasi selengkapnya tentang cara mengaktifkan dan menonaktifkan objek BroadcastReceiver secara efisien, lihat Memanipulasi Penerima Siaran sesuai Permintaan.

Tips: Anda dapat menggunakan StrictMode untuk membantu menemukan operasi yang dapat berjalan lama seperti operasi jaringan atau database yang mungkin tidak sengaja dilakukan di thread utama Anda.

Memperkuat responsivitas

Umumnya, 100 hingga 200 md adalah nilai minimum yang akan membuat pengguna mulai mengalami kelambatan dalam suatu aplikasi. Oleh karena itu, berikut beberapa tips tambahan yang sebaiknya dilakukan untuk menghindari ANR dan membuat aplikasi Anda tampak responsif bagi pengguna:

  • Jika aplikasi melakukan operasi di latar belakang sebagai respons terhadap masukan pengguna, tunjukkan bahwa progres sedang berlangsung (seperti dengan ProgressBar di UI Anda).
  • Khususnya untuk game, lakukan penghitungan gerakan dalam thread pekerja.
  • Jika aplikasi memiliki fase penyiapan awal yang menghabiskan waktu, cobalah untuk menampilkan layar pembuka atau merender tampilan utama secepat mungkin, lalu tunjukkan bahwa pemuatan sedang berlangsung dan isi informasi secara asinkron. Dalam hal ini, sebaiknya tunjukkan bahwa progres sedang berlangsung. Jangan sampai pengguna merasa bahwa aplikasi berhenti merespons.
  • Gunakan alat performa, seperti Systrace dan Traceview untuk menentukan bottleneck dalam responsivitas aplikasi Anda.