Mendeteksi dan mendiagnosis error

Aplikasi Android mengalami error setiap kali terdapat penutupan tidak terduga yang disebabkan oleh pengecualian atau sinyal yang tidak tertangani. Aplikasi yang ditulis menggunakan Java atau Kotlin akan mengalami error jika melempar pengecualian yang tidak tertangani, ditunjukkan oleh class Throwable. Aplikasi yang ditulis menggunakan bahasa kode native akan mengalami error jika terdapat sinyal yang tidak tertangani, seperti SIGSEGV, selama eksekusinya.

Saat aplikasi mengalami error, Android menghentikan prosesnya dan menampilkan dialog yang memberi tahu pengguna bahwa aplikasi telah berhenti, sebagaimana ditunjukkan dalam gambar 1.

Error aplikasi di perangkat Android

Gambar 1. Error aplikasi di perangkat Android

Suatu aplikasi dapat mengalami error meskipun tidak berjalan di latar depan. Komponen aplikasi apa pun, bahkan seperti penerima siaran atau penyedia konten yang berjalan di latar belakang, dapat menyebabkan aplikasi mengalami error. Error ini sering kali membingungkan pengguna karena mereka tidak berinteraksi secara aktif dengan aplikasi.

Jika aplikasi mengalami error, Anda dapat menggunakan panduan di halaman ini untuk mendiagnosis dan mengatasi masalah.

Mendeteksi masalah

Anda mungkin tidak selalu tahu bahwa pengguna mengalami banyak sekali error dengan aplikasi Anda. Jika Anda sudah memublikasikan aplikasi, Android vitals dapat membantu Anda mengetahui masalahnya.

Android vitals

Android vitals dapat membantu meningkatkan performa aplikasi dengan memberi tahu Anda, melalui Konsol Play, bila aplikasi Anda menunjukkan error yang berlebihan. Android vitals menganggap error berlebihan jika aplikasi:

  • Menampilkan minimal satu error dalam setidaknya 1,09% sesi hariannya.
  • Menampilkan dua atau lebih error dalam setidaknya 0,18% sesi hariannya.

Sesi harian mengacu pada hari aplikasi Anda digunakan. Untuk informasi tentang cara Google Play mengumpulkan data Android vitals, lihat dokumentasi Konsol Play.

Setelah mengetahui bahwa aplikasi Anda mengalami error yang berlebihan, langkah berikutnya yang harus dilakukan adalah mendiagnosisnya.

Mendiagnosis error

Mengatasi error mungkin akan sulit. Namun, jika Anda dapat mengidentifikasi akar masalah error, kemungkinan besar Anda akan dapat menemukan solusinya.

Ada banyak situasi yang dapat menyebabkan error pada aplikasi Anda. Beberapa alasannya jelas, seperti memeriksa nilai null atau string kosong, tetapi alasan lainnya tidak begitu jelas, seperti meneruskan argumen yang tidak valid ke API atau bahkan interaksi multithread yang kompleks.

Error di Android menyebabkan pelacakan tumpukan, yang merupakan snapshot dari urutan fungsi bertingkat yang dipanggil dalam program Anda hingga error terjadi. Anda dapat melihat pelacakan tumpukan error di Android vitals.

Membaca pelacakan tumpukan

Langkah pertama untuk mengatasi error adalah dengan mengidentifikasi tempat terjadinya error. Anda dapat menggunakan pelacakan tumpukan yang tersedia dalam detail laporan jika Anda menggunakan Konsol Play atau output alat logcat. Jika pelacakan tumpukan tidak tersedia, Anda harus mereproduksi error tersebut secara lokal, baik dengan menguji aplikasi secara manual maupun dengan menghubungi pengguna yang terkena dampak, serta mereproduksinya saat menggunakan logcat.

Pelacakan berikut menunjukkan contoh error pada aplikasi yang ditulis menggunakan bahasa pemrograman Java:

--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException: crash sample
at com.android.developer.crashsample.MainActivity$1.onClick(MainActivity.java:27)
at android.view.View.performClick(View.java:6134)
at android.view.View$PerformClick.run(View.java:23965)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6440)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746)
--------- beginning of system

Pelacakan tumpukan menunjukkan dua informasi penting untuk men-debug error:

  • Jenis pengecualian yang dilempar.
  • Bagian kode tempat pengecualian dilempar.

Jenis pengecualian yang dilempar biasanya merupakan petunjuk yang sangat kuat tentang masalah yang terjadi. Lihat apakah petunjuk tersebut berupa IOException, OutOfMemoryError, atau yang lainnya, lalu temukan dokumentasi tentang class pengecualian.

Class, metode, file. dan nomor baris file sumber tempat pengecualian dilempar ditampilkan di baris kedua pelacakan tumpukan. Untuk setiap fungsi yang telah dipanggil, baris lainnya menunjukkan situs panggilan sebelumnya (disebut frame stack). Dengan menjalankan stack dan memeriksa kode, Anda mungkin menemukan tempat yang meneruskan nilai yang salah. Jika kode Anda tidak muncul di pelacakan tumpukan, maka kemungkinan di suatu tempat Anda meneruskan parameter yang tidak valid ke operasi asinkron. Anda dapat mencari tahu apa yang terjadi dengan memeriksa setiap baris pelacakan tumpukan, menemukan class API apa pun yang digunakan, dan mengonfirmasi bahwa parameter yang Anda teruskan sudah benar, serta bahwa Anda memanggilnya dari tempat yang diizinkan.

Pelacakan tumpukan untuk aplikasi dengan kode C dan C++ berfungsi dengan cara yang sama.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/foo/bar:10/123.456/78910:user/release-keys'
ABI: 'arm64'
Timestamp: 2020-02-16 11:16:31+0100
pid: 8288, tid: 8288, name: com.example.testapp  >>> com.example.testapp <<<
uid: 1010332
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
    x0  0000007da81396c0  x1  0000007fc91522d4  x2  0000000000000001  x3  000000000000206e
    x4  0000007da8087000  x5  0000007fc9152310  x6  0000007d209c6c68  x7  0000007da8087000
    x8  0000000000000000  x9  0000007cba01b660  x10 0000000000430000  x11 0000007d80000000
    x12 0000000000000060  x13 0000000023fafc10  x14 0000000000000006  x15 ffffffffffffffff
    x16 0000007cba01b618  x17 0000007da44c88c0  x18 0000007da943c000  x19 0000007da8087000
    x20 0000000000000000  x21 0000007da8087000  x22 0000007fc9152540  x23 0000007d17982d6b
    x24 0000000000000004  x25 0000007da823c020  x26 0000007da80870b0  x27 0000000000000001
    x28 0000007fc91522d0  x29 0000007fc91522a0
    sp  0000007fc9152290  lr  0000007d22d4e354  pc  0000007cba01b640

backtrace:
  #00  pc 0000000000042f89  /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::Crasher::crash() const)
  #01  pc 0000000000000640  /data/app/com.example.testapp/lib/arm64/libexample.so (com::example::runCrashThread())
  #02  pc 0000000000065a3b  /system/lib/libc.so (__pthread_start(void*))
  #03  pc 000000000001e4fd  /system/lib/libc.so (__start_thread)

Jika Anda tidak melihat class dan informasi level fungsi dalam pelacakan tumpukan native, Anda mungkin harus membuat file simbol debug native dan menguploadnya ke Konsol Google Play. Untuk informasi selengkapnya, lihat Men-deobfuscate pelacakan tumpukan error. Untuk informasi umum tentang masalah pada native code, lihat Mendiagnosis masalah pada native code.

Tips untuk mereproduksi error

Anda mungkin tidak dapat mereproduksi masalah hanya dengan memulai emulator atau menghubungkan perangkat ke komputer. Lingkungan pengembangan cenderung memiliki lebih banyak resource, seperti bandwidth, memori, dan penyimpanan. Gunakan jenis pengecualian untuk menentukan resource yang langka, atau menemukan korelasi antara versi Android, jenis perangkat, atau versi aplikasi Anda.

Error memori

Jika memiliki OutOfMemoryError, Anda dapat mulai membuat emulator dengan kapasitas memori rendah untuk digunakan dalam pengujian. Gambar 2 menunjukkan setelan AVD Manager tempat Anda dapat mengontrol jumlah memori di perangkat.

Setelan memori di AVD Manager

Gambar 2. Setelan memori di AVD Manager

Pengecualian jaringan

Karena pengguna sering kali berada di dalam atau di luar jangkauan jaringan seluler atau WiFi, pengecualian jaringan dalam aplikasi biasanya tidak boleh dianggap sebagai error, melainkan sebagai kondisi pengoperasian normal yang terjadi secara tidak terduga.

Jika Anda perlu mereproduksi pengecualian jaringan, seperti UnknownHostException, coba aktifkan mode pesawat saat aplikasi Anda mencoba menggunakan jaringan.

Opsi lainnya adalah mengurangi kualitas jaringan di emulator dengan memilih emulasi kecepatan jaringan dan/atau penundaan jaringan. Anda dapat menggunakan setelan Speed dan Latency di AVD Manager, atau memulai emulator dengan flag -netdelay dan -netspeed, sebagaimana ditunjukkan dalam contoh command line berikut:

emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm

Contoh ini menetapkan penundaan 20 detik di semua permintaan jaringan serta kecepatan upload dan download 14,4 Kbps. Untuk informasi selengkapnya tentang opsi command line untuk emulator, lihat Memulai Emulator dari command line.

Membaca dengan logcat

Setelah melakukan langkah-langkah untuk mereproduksi error, Anda dapat menggunakan alat seperti logcat untuk mendapatkan informasi selengkapnya.

Output logcat akan menunjukkan pesan log lain yang telah Anda cetak, beserta pesan log lainnya dari sistem. Jangan lupa untuk menonaktifkan semua pernyataan Log tambahan yang telah Anda tambahkan, karena mencetaknya akan memboroskan CPU dan baterai saat aplikasi Anda sedang berjalan.

Mencegah error yang disebabkan oleh pengecualian pointer null

Pengecualian pointer null (diidentifikasi oleh jenis error runtime NullPointerException) terjadi saat Anda mencoba mengakses objek yang null, biasanya dengan memanggil metode atau mengakses anggotanya. Pengecualian pointer null adalah penyebab terbesar terjadinya error aplikasi di Google Play. Tujuan null adalah untuk menunjukkan bahwa objek tidak ada—misalnya, objek belum dibuat atau ditetapkan. Untuk menghindari pengecualian pointer null, Anda harus memastikan bahwa referensi objek yang digunakan tidak null sebelum memanggil metode pada referensi tersebut atau mencoba mengakses anggotanya. Jika referensi objek bernilai null, tangani kasus ini dengan baik (misalnya, keluar dari metode sebelum melakukan operasi apa pun pada referensi objek dan tulis informasi ke log debug).

Pemeriksaan null untuk setiap parameter masing-masing metode yang dipanggil akan merepotkan, Anda dapat mengandalkan IDE atau jenis objek untuk menandakan nullability.

Bahasa pemrograman Java

Bagian berikut berlaku untuk bahasa pemrograman Java.

Peringatan waktu kompilasi

Beri anotasi parameter metode Anda dan nilai yang ditampilkan dengan @Nullable dan @NonNull untuk menerima peringatan waktu kompilasi dari IDE. Peringatan ini akan memberi tahu Anda bahwa objek bersifat nullable:

Peringatan pengecualian pointer null

Pemeriksaan null ini ditujukan untuk objek yang Anda ketahui dapat bernilai null. Pengecualian pada objek @NonNull adalah indikasi error dalam kode yang perlu ditangani.

Error waktu kompilasi

Karena nullability harus bermakna, Anda dapat menyematkannya dalam jenis yang Anda gunakan sehingga ada pemeriksaan waktu kompilasi untuk null. Jika mengetahui bahwa objek dapat bernilai null dan nullability tersebut harus ditangani, Anda dapat menggabungkannya dalam objek seperti Optional. Anda harus selalu memilih jenis yang menyampaikan nullability.

Kotlin

Di Kotlin, nullability adalah bagian dari sistem jenis. Misalnya, variabel harus dideklarasikan sejak awal sebagai nullable atau non-nullable. Jenis nullable ditandai dengan ?:

// non-null
var s: String = "Hello"

// null
var s: String? = "Hello"

Variabel non-nullable tidak dapat diberi nilai null dan nullability variabel nullable harus diperiksa sebelum digunakan sebagai non-null.

Jika tidak ingin memeriksa null secara eksplisit, Anda dapat menggunakan operator panggilan aman ?.:

val length: Int? = string?.length  // length is a nullable int
                                   // if string is null, then length is null

Sebagai praktik terbaik, pastikan Anda menangani kasus null untuk objek nullable. Jika tidak ditangani, aplikasi Anda dapat mengalami keadaan yang tidak diharapkan. Jika aplikasi Anda tidak lagi mengalami error dengan NullPointerException, Anda tidak akan mengetahui bahwa error ini ada.

Berikut adalah beberapa cara untuk memeriksa null:

  • Pemeriksaan if

    val length = if(string != null) string.length else 0
    

    Karena smart-cast dan pemeriksaan null, compiler Kotlin mengetahui bahwa nilai string adalah non-null sehingga memungkinkan Anda menggunakan referensi secara langsung, tanpa memerlukan operator panggilan yang aman.

  • ?: Operator Elvis

    Operator ini memungkinkan Anda menyatakan "jika objek non-null, tampilkan objek; jika tidak, tampilkan objek lain".

    val length = string?.length ?: 0
    

Anda tetap dapat memperoleh NullPointerException dalam Kotlin. Berikut adalah situasi yang paling umum:

  • Saat Anda secara eksplisit melempar NullPointerException.
  • Saat Anda menggunakan operator !! pernyataan null. Operator ini mengonversi semua nilai menjadi jenis non-null, dengan melempar NullPointerException jika nilainya null.
  • Saat mengakses referensi null jenis platform.

Jenis platform

Jenis platform adalah deklarasi objek yang berasal dari Java. Jenis ini diperlakukan secara khusus; pemeriksaan null tidak diterapkan, sehingga jaminan non-null sama seperti dalam Java. Saat Anda mengakses referensi jenis platform, Kotlin tidak membuat error waktu kompilasi, tetapi referensi ini dapat menyebabkan error runtime. Lihat contoh berikut dari dokumentasi Kotlin:

val list = ArrayList<String>() // non-null (constructor result) list.add("Item")
val size = list.size // non-null (primitive int) val item = list[0] // platform
type inferred (ordinary Java object) item.substring(1) // allowed, may throw an
                                                       // exception if item == null

Kotlin mengandalkan inferensi jenis saat nilai platform ditetapkan ke variabel Kotlin, atau Anda dapat menentukan jenis yang diharapkan. Cara terbaik untuk memastikan status nullability yang benar dari referensi yang berasal dari Java adalah dengan menggunakan anotasi nullability (misalnya, @Nullable) dalam kode Java Anda. Compiler Kotlin akan menampilkan referensi ini sebagai jenis nullable atau non-nullable sebenarnya, bukan sebagai jenis platform.

Java Jetpack API telah dianotasi dengan @Nullable atau @NonNull sesuai kebutuhan, dan pendekatan serupa telah dilakukan dalam SDK Android 11. Jenis yang berasal dari SDK ini, yang digunakan di Kotlin, akan ditampilkan sebagai jenis nullable atau non-nullable yang benar.

Berkat sistem jenis Kotlin, kami melihat berbagai aplikasi mengalami penurunan error NullPointerException secara signifikan. Misalnya, aplikasi Google Home mengalami penurunan error yang disebabkan oleh pengecualian pointer null sebesar 30% pada tahun aplikasi ini memigrasikan pengembangan fitur baru ke Kotlin.