Konsep dan penerapan Jetpack Compose
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.
ANR adalah masalah yang muncul karena thread utama aplikasi, yang bertanggung jawab untuk mengupdate UI, tidak dapat memproses peristiwa input pengguna atau melakukan draw, sehingga dapat membuat pengguna kebingungan. Untuk mengetahui informasi selengkapnya tentang thread utama aplikasi, lihat Ringkasan proses dan thread.
ANR dipicu untuk aplikasi jika salah satu kondisi berikut terjadi:
- Waktu pengiriman input habis: Jika aplikasi Anda tidak merespons peristiwa input (seperti penekanan tombol atau sentuhan layar) dalam 5 detik.
- Mengeksekusi layanan: Jika layanan yang dideklarasikan oleh aplikasi Anda tidak dapat menyelesaikan
eksekusi
Service.onCreate()danService.onStartCommand()/Service.onBind()dalam beberapa detik. **Service.startForeground()tidak dipanggil**: Jika aplikasi Anda menggunakanContext.startForegroundService()untuk memulai layanan baru di latar depan tetapi layanan tidak memanggilstartForeground()dalam 5 detik.- Broadcast intent: Jika
BroadcastReceiverbelum selesai dieksekusi dalam jangka waktu yang ditentukan. Jika aplikasi memiliki aktivitas di latar depan, waktu tunggu ini adalah 5 detik. - Interaksi
**JobScheduler**: JikaJobServicetidak ditampilkan dariJobService.onStartJob()atauJobService.onStopJob()dalam beberapa detik, atau jika tugas yang dimulai pengguna dimulai dan aplikasi Anda tidak memanggilJobService.setNotification()dalam beberapa detik setelahJobService.onStartJob()dipanggil. Untuk aplikasi yang menargetkan Android 13 dan yang lebih lama, ANR bersifat implisit dan tidak dilaporkan ke aplikasi. Untuk aplikasi yang menargetkan Android 14 dan yang lebih baru, ANR bersifat eksplisit dan dilaporkan ke aplikasi.
Jika aplikasi mengalami ANR, Anda dapat menggunakan pedoman di artikel ini untuk mendiagnosis dan memperbaiki masalahnya.
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 menunjukkan 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 menyertakan class yang dapat membantu memindahkan tugas ke thread pekerja. Lihat Thread pekerja untuk mengetahui informasi selengkapnya.
I/O di thread utama
Mengeksekusi operasi I/O di thread utama adalah penyebab umum operasi yang lambat
di thread utama, yang dapat menyebabkan ANR. Di Compose, developer sering kali
secara tidak sengaja memicu pembacaan disk (seperti SharedPreferences atau panggilan database)
saat mencoba mendapatkan status awal.
Jalankan operasi I/O yang berjalan lama di luar lapisan UI. Gunakan
withContext(Dispatchers.IO) di ViewModel atau, lebih baik lagi, gunakan Repositori
di lapisan data. Sebaiknya pindahkan semua operasi IO ke thread pekerja, seperti yang ditunjukkan di bagian sebelumnya.
Beberapa contoh operasi IO adalah operasi jaringan dan penyimpanan. Untuk mengetahui informasi selengkapnya, lihat Melakukan operasi jaringan dan Menyimpan data.
Pertentangan kunci
Dalam beberapa skenario, pekerjaan yang menyebabkan ANR tidak secara langsung dieksekusi di thread utama aplikasi. Jika thread pekerja menahan kunci pada resource yang diperlukan oleh thread utama untuk menyelesaikan pekerjaannya, ANR mungkin terjadi.
Misalnya, Gambar 3 menunjukkan linimasa Traceview dengan sebagian besar pekerjaan dilakukan di thread pekerja.

Gambar 3. Linimasa Traceview yang menunjukkan pekerjaan yang sedang dieksekusi di thread pekerja
Namun, 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 ditunjukkan di gambar 5.

Gambar 4. Thread utama dalam status Monitor
Rekaman aktivitas 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
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
berkomunikasi antara thread pekerja dan utama.
Penerima siaran lambat
Aplikasi dapat merespons pesan siaran, seperti mengaktifkan 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:
- Penerima siaran belum selesai mengeksekusi metode
onReceive-nya dalam waktu yang cukup lama. - Penerima siaran memanggil
goAsyncdan gagal memanggilfinishpada objekPendingResult.
Aplikasi Anda hanya dapat menjalankan operasi pendek dalam metode onReceive dari
BroadcastReceiver. Namun, jika aplikasi Anda memerlukan pemrosesan
yang lebih kompleks karena pesan siaran, Anda harus mengalihkan tugas ke
ViewModel (memanfaatkan kecanggihan coroutine, cakupan, dan dispatcher Kotlin)
jika tugas diperkirakan akan memakan waktu paling lama beberapa detik, jenis penampung status apa pun,
atau ke WorkManager untuk tugas yang diperkirakan akan memakan waktu lebih dari beberapa detik.
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 5. Linimasa Traceview yang menunjukkan tugas BroadcastReceiver di
thread utama
Perilaku ini dapat disebabkan oleh eksekusi operasi yang berjalan lama pada
metode onReceive() dari 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 tugas yang dialihkan ke thread pekerja di linimasa Traceview.

Gambar 6. 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. Namun, 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.