Tips dan Alat untuk Mengoptimalkan Aplikasi Android

Diterbitkan: 2022-03-11

Perangkat Android memiliki banyak inti, jadi menulis aplikasi yang lancar adalah tugas yang mudah bagi siapa saja, bukan? Salah. Karena semua yang ada di Android dapat dilakukan dengan banyak cara berbeda, memilih opsi terbaik bisa jadi sulit. Jika Anda ingin memilih metode yang paling efisien, Anda harus tahu apa yang terjadi di balik tenda. Untungnya, Anda tidak harus bergantung pada perasaan atau indra penciuman Anda, karena ada banyak alat di luar sana yang dapat membantu Anda menemukan kemacetan dengan mengukur dan menjelaskan apa yang terjadi. Aplikasi yang dioptimalkan dengan benar dan mulus sangat meningkatkan pengalaman pengguna, dan juga menghabiskan lebih sedikit baterai.

Mari kita lihat beberapa angka terlebih dahulu untuk mempertimbangkan betapa pentingnya pengoptimalan. Menurut posting Nimbledroid, 86% pengguna (termasuk saya) telah mencopot pemasangan aplikasi setelah menggunakannya hanya sekali karena kinerja yang buruk. Jika Anda memuat beberapa konten, Anda memiliki waktu kurang dari 11 detik untuk menunjukkannya kepada pengguna. Hanya setiap pengguna ketiga yang akan memberi Anda lebih banyak waktu. Anda mungkin juga mendapatkan banyak ulasan buruk di Google Play karenanya.

Bangun Aplikasi yang Lebih Baik: Pola Kinerja Android

Menguji kesabaran pengguna Anda adalah jalan pintas untuk mencopot pemasangan.
Menciak

Hal pertama yang diperhatikan setiap pengguna berulang kali adalah waktu mulai aplikasi. Menurut posting Nimbledroid lainnya, dari 100 aplikasi teratas, 40 dimulai dalam waktu kurang dari 2 detik, dan 70 dimulai dalam waktu kurang dari 3 detik. Jadi jika memungkinkan, Anda biasanya harus menampilkan beberapa konten sesegera mungkin dan menunda pemeriksaan latar belakang dan pembaruan sedikit.

Selalu ingat, optimasi prematur adalah akar dari semua kejahatan. Anda juga tidak boleh membuang terlalu banyak waktu dengan optimasi mikro. Anda akan melihat manfaat paling besar dari pengoptimalan kode yang sering berjalan. Misalnya, ini termasuk fungsi onDraw() , yang menjalankan setiap frame, idealnya 60 kali per detik. Menggambar adalah operasi paling lambat di luar sana, jadi cobalah menggambar ulang hanya apa yang harus Anda lakukan. Lebih lanjut tentang ini akan datang nanti.

Kiat Kinerja

Cukup teori, berikut adalah daftar beberapa hal yang harus Anda pertimbangkan jika kinerja penting bagi Anda.

1. String vs. StringBuilder

Katakanlah Anda memiliki sebuah String, dan untuk beberapa alasan Anda ingin menambahkan lebih banyak String ke dalamnya 10 ribu kali. Kodenya bisa terlihat seperti ini.

 String string = "hello"; for (int i = 0; i < 10000; i++) { string += " world"; }

Anda dapat melihat di Android Studio Monitors betapa tidak efisiennya beberapa rangkaian String. Ada banyak Pengumpulan Sampah (GC) yang terjadi.

String vs StringBuilder

Operasi ini memakan waktu sekitar 8 detik pada perangkat saya yang cukup bagus, yang memiliki Android 5.1.1. Cara yang lebih efisien untuk mencapai tujuan yang sama adalah menggunakan StringBuilder, seperti ini.

 StringBuilder sb = new StringBuilder("hello"); for (int i = 0; i < 10000; i++) { sb.append(" world"); } String string = sb.toString();

Pada perangkat yang sama ini terjadi hampir seketika, dalam waktu kurang dari 5 ms. Visualisasi CPU dan Memori hampir benar-benar datar, sehingga Anda dapat membayangkan seberapa besar peningkatan ini. Namun perhatikan, bahwa untuk mencapai perbedaan ini, kami harus menambahkan 10 ribu String, yang mungkin tidak sering Anda lakukan. Jadi jika Anda menambahkan hanya beberapa String sekali, Anda tidak akan melihat peningkatan apa pun. Omong-omong, jika Anda melakukannya:

 String string = "hello" + " world";

Itu akan dikonversi secara internal ke StringBuilder, jadi itu akan berfungsi dengan baik.

Anda mungkin bertanya-tanya, mengapa menggabungkan String dengan cara pertama sangat lambat? Hal ini disebabkan oleh fakta bahwa String tidak dapat diubah, jadi setelah dibuat, String tidak dapat diubah. Bahkan jika Anda berpikir Anda sedang mengubah nilai sebuah String, Anda sebenarnya sedang membuat sebuah String baru dengan nilai yang baru. Dalam contoh seperti:

 String myString = "hello"; myString += " world";

Apa yang akan Anda dapatkan di memori bukanlah 1 String "hello world", tetapi sebenarnya 2 String. String myString akan berisi "hello world", seperti yang Anda harapkan. Namun, String asli dengan nilai "halo" masih hidup, tanpa referensi apa pun, menunggu untuk dikumpulkan. Ini juga alasan mengapa Anda harus menyimpan kata sandi dalam array char alih-alih String. Jika Anda menyimpan kata sandi sebagai String, kata sandi akan tetap berada di memori dalam format yang dapat dibaca manusia hingga GC berikutnya untuk jangka waktu yang tidak dapat diprediksi. Kembali ke kekekalan yang dijelaskan di atas, String akan tetap berada di memori bahkan jika Anda menetapkan nilai lain setelah menggunakannya. Namun, jika Anda mengosongkan array char setelah menggunakan kata sandi, itu akan hilang dari mana-mana.

2. Memilih Tipe Data yang Benar

Sebelum Anda mulai menulis kode, Anda harus memutuskan tipe data apa yang akan Anda gunakan untuk koleksi Anda. Misalnya, haruskah Anda menggunakan Vector atau ArrayList ? Yah, itu tergantung pada usecase Anda. Jika Anda memerlukan koleksi thread-safe, yang akan memungkinkan hanya satu utas sekaligus untuk bekerja dengannya, Anda harus memilih Vector , karena disinkronkan. Dalam kasus lain, Anda mungkin harus tetap menggunakan ArrayList , kecuali jika Anda benar-benar memiliki alasan khusus untuk menggunakan vektor.

Bagaimana jika Anda menginginkan koleksi dengan benda-benda unik? Nah, Anda mungkin harus memilih Set . Mereka tidak dapat berisi duplikat berdasarkan desain, jadi Anda tidak perlu mengurusnya sendiri. Ada beberapa jenis set, jadi pilih satu yang sesuai dengan kasus penggunaan Anda. Untuk sekelompok item unik yang sederhana, Anda dapat menggunakan HashSet . Jika Anda ingin mempertahankan urutan item yang disisipkan, pilih LinkedHashSet . TreeSet mengurutkan item secara otomatis, jadi Anda tidak perlu memanggil metode penyortiran apa pun di dalamnya. Itu juga harus mengurutkan item secara efisien, tanpa Anda harus memikirkan algoritma pengurutan.

Data mendominasi. Jika Anda telah memilih struktur data yang tepat dan mengatur berbagai hal dengan baik, algoritme hampir selalu terbukti dengan sendirinya. Struktur data, bukan algoritme, adalah inti dari pemrograman.
— 5 Aturan Pemrograman Rob Pike

Mengurutkan bilangan bulat atau string cukup mudah. Namun, bagaimana jika Anda ingin mengurutkan kelas berdasarkan beberapa properti? Katakanlah Anda sedang menulis daftar makanan yang Anda makan, dan menyimpan nama dan stempel waktunya. Bagaimana Anda mengurutkan makanan berdasarkan stempel waktu dari yang terendah hingga tertinggi? Untungnya, ini cukup sederhana. Cukup mengimplementasikan antarmuka Comparable di kelas Meal dan mengganti fungsi compareTo() . Untuk mengurutkan makanan berdasarkan stempel waktu terendah hingga tertinggi, kita bisa menulis sesuatu seperti ini.

 @Override public int compareTo(Object object) { Meal meal = (Meal) object; if (this.timestamp < meal.getTimestamp()) { return -1; } else if (this.timestamp > meal.getTimestamp()) { return 1; } return 0; }

3. Pembaruan Lokasi

Ada banyak aplikasi di luar sana yang mengumpulkan lokasi pengguna. Anda harus menggunakan API Layanan Lokasi Google untuk tujuan itu, yang berisi banyak fungsi berguna. Ada artikel terpisah tentang menggunakannya, jadi saya tidak akan mengulanginya.

Saya hanya ingin menekankan beberapa poin penting dari perspektif kinerja.

Pertama-tama, gunakan hanya lokasi yang paling tepat yang Anda butuhkan. Misalnya, jika Anda melakukan prakiraan cuaca, Anda tidak memerlukan lokasi yang paling akurat. Mendapatkan hanya area yang sangat kasar berdasarkan jaringan lebih cepat, dan lebih hemat baterai. Anda dapat mencapainya dengan menetapkan prioritas ke LocationRequest.PRIORITY_LOW_POWER .

Anda juga dapat menggunakan fungsi LocationRequest yang disebut setSmallestDisplacement() . Menyetel ini dalam meter akan menyebabkan aplikasi Anda tidak diberi tahu tentang perubahan lokasi jika lebih kecil dari nilai yang diberikan. Misalnya, jika Anda memiliki peta dengan restoran terdekat di sekitar Anda, dan Anda mengatur perpindahan terkecil ke 20 meter, aplikasi tidak akan membuat permintaan untuk memeriksa restoran jika pengguna hanya berjalan-jalan di dalam ruangan. Permintaan itu tidak akan berguna, karena toh tidak akan ada restoran baru di dekatnya.

Aturan kedua adalah meminta pembaruan lokasi hanya sesering Anda membutuhkannya. Ini cukup jelas. Jika Anda benar-benar membuat aplikasi prakiraan cuaca itu, Anda tidak perlu meminta lokasi setiap beberapa detik, karena Anda mungkin tidak memiliki prakiraan yang tepat (hubungi saya jika ada). Anda dapat menggunakan fungsi setInterval() untuk menyetel interval yang diperlukan saat perangkat akan memperbarui aplikasi Anda tentang lokasi. Jika beberapa aplikasi terus meminta lokasi pengguna, setiap aplikasi akan diberi tahu pada setiap pembaruan lokasi baru, meskipun Anda memiliki setInterval() yang lebih tinggi. Untuk mencegah aplikasi Anda terlalu sering diberi tahu, pastikan untuk selalu menyetel interval pembaruan tercepat dengan setFastestInterval() .

Dan terakhir, aturan ketiga adalah meminta pembaruan lokasi hanya jika Anda membutuhkannya. Jika Anda menampilkan beberapa objek terdekat di peta setiap x detik dan aplikasi berjalan di latar belakang, Anda tidak perlu mengetahui lokasi baru. Tidak ada alasan untuk memperbarui peta jika pengguna tidak dapat melihatnya. Pastikan untuk berhenti mendengarkan pembaruan lokasi bila perlu, sebaiknya di onPause() . Anda kemudian dapat melanjutkan pembaruan di onResume() .

4. Permintaan Jaringan

Ada kemungkinan besar aplikasi Anda menggunakan internet untuk mengunduh atau mengunggah data. Jika ya, Anda memiliki beberapa alasan untuk memperhatikan penanganan permintaan jaringan. Salah satunya adalah data seluler yang sangat terbatas untuk banyak orang dan tidak boleh disia-siakan.

Yang kedua adalah baterai. Baik WiFi maupun jaringan seluler dapat menghabiskan cukup banyak jika digunakan terlalu banyak. Katakanlah Anda ingin mengunduh 1 kb. Untuk membuat permintaan jaringan, Anda harus membangunkan radio seluler atau WiFi, lalu Anda dapat mengunduh data Anda. Namun, radio tidak akan langsung tertidur setelah operasi. Ini akan tetap dalam keadaan cukup aktif selama sekitar 20-40 detik lagi, tergantung pada perangkat dan operator Anda.

Permintaan Jaringan

Jadi, apa yang dapat Anda lakukan? Kelompok. Untuk menghindari membangunkan radio setiap beberapa detik, ambil lebih dulu hal-hal yang mungkin dibutuhkan pengguna dalam beberapa menit mendatang. Cara pengelompokan yang tepat sangat dinamis bergantung pada aplikasi Anda, tetapi jika memungkinkan, Anda harus mengunduh data yang mungkin dibutuhkan pengguna dalam 3-4 menit ke depan. Seseorang juga dapat mengedit parameter batch berdasarkan jenis internet pengguna, atau status pengisian daya. Misalnya, jika pengguna menggunakan WiFi saat mengisi daya, Anda dapat mengambil lebih banyak data daripada jika pengguna menggunakan internet seluler dengan baterai lemah. Mempertimbangkan semua variabel ini bisa menjadi hal yang sulit, yang hanya dilakukan oleh sedikit orang. Untungnya, ada Manajer Jaringan GCM untuk menyelamatkan!

GCM Network Manager adalah kelas yang sangat membantu dengan banyak atribut yang dapat disesuaikan. Anda dapat dengan mudah menjadwalkan tugas berulang dan satu kali. Pada tugas berulang, Anda dapat mengatur interval pengulangan terendah, serta tertinggi. Ini akan memungkinkan pengelompokan tidak hanya permintaan Anda, tetapi juga permintaan dari aplikasi lain. Radio harus dibangunkan hanya sekali per beberapa periode, dan saat sedang aktif, semua aplikasi dalam antrian mengunduh dan mengunggah apa yang seharusnya mereka lakukan. Manajer ini juga mengetahui jenis jaringan dan status pengisian daya perangkat, sehingga Anda dapat menyesuaikannya. Anda dapat menemukan lebih banyak detail dan sampel di artikel ini, saya mendorong Anda untuk memeriksanya. Contoh tugas terlihat seperti ini:

 Task task = new OneoffTask.Builder() .setService(CustomService.class) .setExecutionWindow(0, 30) .setTag(LogService.TAG_TASK_ONEOFF_LOG) .setUpdateCurrent(false) .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) .setRequiresCharging(false) .build();

Omong-omong, sejak Android 3.0, jika Anda melakukan permintaan jaringan di utas utama, Anda akan mendapatkan NetworkOnMainThreadException . Itu pasti akan memperingatkan Anda untuk tidak melakukannya lagi.

5. Refleksi

Refleksi adalah kemampuan kelas dan objek untuk memeriksa konstruktor, bidang, metode, dan sebagainya mereka sendiri. Biasanya digunakan untuk kompatibilitas mundur, untuk memeriksa apakah metode yang diberikan tersedia untuk versi OS tertentu. Jika Anda harus menggunakan refleksi untuk tujuan itu, pastikan untuk men-cache respons, karena menggunakan refleksi cukup lambat. Beberapa perpustakaan yang banyak digunakan juga menggunakan Refleksi, seperti Roboguice untuk injeksi ketergantungan. Itulah alasan mengapa Anda harus memilih Dagger 2. Untuk lebih jelasnya tentang refleksi, Anda dapat memeriksa posting terpisah.

6. Autoboxing

Autoboxing dan unboxing adalah proses mengubah tipe primitif menjadi tipe Object, atau sebaliknya. Dalam praktiknya itu berarti mengonversi int menjadi Integer. Untuk mencapai itu, compiler menggunakan fungsi Integer.valueOf() secara internal. Konversi tidak hanya lambat, Objek juga membutuhkan lebih banyak memori daripada padanan primitifnya. Mari kita lihat beberapa kode.

 Integer total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

Meskipun ini membutuhkan rata-rata 500 ms, menulis ulang untuk menghindari autoboxing akan mempercepatnya secara drastis.

 int total = 0; for (int i = 0; i < 1000000; i++) { total += i; }

Solusi ini berjalan sekitar 2 ms, yang 25 kali lebih cepat. Jika Anda tidak percaya saya, mengujinya. Angkanya jelas akan berbeda per perangkat, tetapi masih harus jauh lebih cepat. Dan ini juga merupakan langkah yang sangat sederhana untuk dioptimalkan.

Oke, Anda mungkin tidak sering membuat variabel bertipe Integer seperti ini. Tetapi bagaimana dengan kasus-kasus yang lebih sulit untuk dihindari? Seperti di peta, di mana Anda harus menggunakan Objek, seperti Map<Integer, Integer> ? Lihatlah solusi yang digunakan banyak orang.

 Map<Integer, Integer> myMap = new HashMap<>(); for (int i = 0; i < 100000; i++) { myMap.put(i, random.nextInt()); }

Memasukkan 100k int acak di peta membutuhkan waktu sekitar 250 md untuk dijalankan. Sekarang lihat solusinya dengan SparseIntArray.

 SparseIntArray myArray = new SparseIntArray(); for (int i = 0; i < 100000; i++) { myArray.put(i, random.nextInt()); }

Ini membutuhkan waktu yang jauh lebih sedikit, kira-kira 50ms. Ini juga salah satu metode yang lebih mudah untuk meningkatkan kinerja, karena tidak ada yang rumit yang harus dilakukan, dan kodenya juga tetap dapat dibaca. Saat menjalankan aplikasi yang jelas dengan solusi pertama membutuhkan 13MB memori saya, menggunakan int primitif membutuhkan sesuatu di bawah 7MB, jadi hanya setengahnya.

SparseIntArray hanyalah salah satu koleksi keren yang dapat membantu Anda menghindari autoboxing. Peta seperti Map<Integer, Long> dapat diganti dengan SparseLongArray , karena nilai peta bertipe Long . Jika Anda melihat kode sumber SparseLongArray , Anda akan melihat sesuatu yang cukup menarik. Di bawah tenda, pada dasarnya hanya sepasang array. Anda juga dapat menggunakan SparseBooleanArray dengan cara yang sama.

Jika Anda membaca kode sumbernya, Anda mungkin memperhatikan catatan yang mengatakan bahwa SparseIntArray bisa lebih lambat dari HashMap . Saya telah banyak bereksperimen, tetapi bagi saya SparseIntArray selalu lebih baik baik dari segi memori maupun kinerja. Saya kira masih terserah Anda yang mana yang Anda pilih, bereksperimenlah dengan kasus penggunaan Anda dan lihat mana yang paling cocok untuk Anda. Pasti memiliki SparseArrays di kepala Anda saat menggunakan peta.

7. OnDraw

Seperti yang saya katakan di atas, ketika Anda mengoptimalkan kinerja, Anda mungkin akan melihat manfaat paling besar dalam mengoptimalkan kode yang sering dijalankan. Salah satu fungsi yang banyak berjalan adalah onDraw() . Mungkin tidak mengejutkan Anda bahwa itu bertanggung jawab untuk menggambar tampilan di layar. Karena perangkat biasanya berjalan pada 60 fps, fungsi ini dijalankan 60 kali per detik. Setiap frame memiliki 16 ms untuk ditangani sepenuhnya, termasuk persiapan dan gambarnya, jadi Anda harus benar-benar menghindari fungsi yang lambat. Hanya utas utama yang dapat menggambar di layar, jadi Anda harus menghindari melakukan operasi yang mahal di atasnya. Jika Anda membekukan utas utama selama beberapa detik, Anda mungkin mendapatkan dialog Application Not Responding (ANR) yang terkenal. Untuk mengubah ukuran gambar, pekerjaan basis data, dll., gunakan utas latar belakang.

Jika Anda berpikir pengguna Anda tidak akan menyadari penurunan frame rate, Anda salah!
Menciak

Saya telah melihat beberapa orang mencoba mempersingkat kode mereka, berpikir bahwa itu akan lebih efisien dengan cara itu. Itu jelas bukan cara yang tepat, karena kode yang lebih pendek sama sekali tidak berarti kode yang lebih cepat. Dalam situasi apa pun Anda tidak boleh mengukur kualitas kode dengan jumlah baris.

Salah satu hal yang harus Anda hindari di onDraw() adalah mengalokasikan objek seperti Paint. Siapkan semua yang ada di konstruktor, jadi siap saat menggambar. Bahkan jika Anda memiliki onDraw() yang dioptimalkan, Anda harus memanggilnya sesering yang Anda perlukan. Apa yang lebih baik daripada memanggil fungsi yang dioptimalkan? Yah, tidak memanggil fungsi apa pun sama sekali. Jika Anda ingin menggambar teks, ada fungsi pembantu yang cukup rapi yang disebut drawText() , di mana Anda dapat menentukan hal-hal seperti teks, koordinat, dan warna teks.

8. ViewHolders

Anda mungkin tahu yang satu ini, tapi saya tidak bisa melewatkannya. Pola desain Viewholder adalah cara untuk membuat daftar gulir menjadi lebih mulus. Ini adalah semacam caching tampilan, yang secara serius dapat mengurangi panggilan ke findViewById() dan meningkatkan tampilan dengan menyimpannya. Itu bisa terlihat seperti ini.

 static class ViewHolder { TextView title; TextView text; public ViewHolder(View view) { title = (TextView) view.findViewById(R.id.title); text = (TextView) view.findViewById(R.id.text); } }

Kemudian, di dalam fungsi getView() adaptor Anda, Anda dapat memeriksa apakah Anda memiliki tampilan yang bisa digunakan. Jika tidak, Anda membuatnya.

 ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, viewGroup, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.title.setText("Hello World");

Anda dapat menemukan banyak info berguna tentang pola ini di internet. Ini juga dapat digunakan jika tampilan daftar Anda memiliki beberapa jenis elemen yang berbeda di dalamnya, seperti beberapa header bagian.

9. Mengubah Ukuran Gambar

Kemungkinannya adalah, aplikasi Anda akan berisi beberapa gambar. Jika Anda mengunduh beberapa JPG dari web, mereka dapat memiliki resolusi yang sangat besar. Namun, perangkat tempat mereka akan ditampilkan akan jauh lebih kecil. Bahkan jika Anda mengambil foto dengan kamera perangkat Anda, ukurannya harus dikurangi sebelum ditampilkan karena resolusi foto jauh lebih besar daripada resolusi layar. Mengubah ukuran gambar sebelum menampilkannya adalah hal yang penting. Jika Anda mencoba menampilkannya dalam resolusi penuh, memori Anda akan cepat habis. Ada banyak tulisan tentang menampilkan bitmap secara efisien di dokumen Android, saya akan mencoba merangkumnya.

Jadi Anda memiliki bitmap, tetapi Anda tidak tahu apa-apa tentangnya. Ada flag Bitmap yang berguna yang disebut inJustDecodeBounds di layanan Anda, yang memungkinkan Anda mengetahui resolusi bitmap. Anggaplah bitmap Anda berukuran 1024x768, dan ImageView yang digunakan untuk menampilkannya hanya berukuran 400x300. Anda harus terus membagi resolusi bitmap dengan 2 hingga masih lebih besar dari ImageView yang diberikan. Jika Anda melakukannya, itu akan menurunkan sampel bitmap dengan faktor 2, memberi Anda bitmap 512x384. Bitmap yang di-downsampling menggunakan memori 4x lebih sedikit, yang akan banyak membantu Anda menghindari kesalahan OutOfMemory yang terkenal.

Sekarang Anda tahu bagaimana melakukannya, Anda seharusnya tidak melakukannya. … Setidaknya, tidak jika aplikasi Anda sangat bergantung pada gambar, dan itu bukan hanya 1-2 gambar. Hindari hal-hal seperti mengubah ukuran dan mendaur ulang gambar secara manual, gunakan beberapa perpustakaan pihak ketiga untuk itu. Yang paling populer adalah Picasso by Square, Universal Image Loader, Fresco by Facebook, atau favorit saya, Glide. Ada komunitas pengembang aktif yang sangat besar di sekitarnya, jadi Anda juga dapat menemukan banyak orang yang membantu di bagian masalah di GitHub.

10. Modus Ketat

Strict Mode adalah alat pengembang yang cukup berguna yang tidak diketahui banyak orang. Biasanya digunakan untuk mendeteksi permintaan jaringan atau akses disk dari utas utama. Anda dapat mengatur masalah apa yang harus dicari oleh Mode Ketat dan hukuman apa yang harus dipicu. Contoh google terlihat seperti ini:

 public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }

Jika Anda ingin mendeteksi setiap masalah yang dapat ditemukan oleh Mode Ketat, Anda juga dapat menggunakan detectAll() . Seperti banyak tip kinerja, Anda tidak boleh membabi buta mencoba memperbaiki semua laporan Mode Ketat. Selidiki saja, dan jika Anda yakin itu bukan masalah, biarkan saja. Pastikan juga untuk menggunakan Mode Ketat hanya untuk debugging, dan selalu nonaktifkan pada build produksi.

Performa Debug: Cara Pro

Sekarang mari kita lihat beberapa alat yang dapat membantu Anda menemukan kemacetan, atau setidaknya menunjukkan bahwa ada sesuatu yang salah.

1. Monitor Android

Ini adalah alat yang dibangun ke dalam Android Studio. Secara default, Anda dapat menemukan Android Monitor di sudut kiri bawah, dan Anda dapat beralih di antara 2 tab di sana. Logcat dan Monitor. Bagian Monitor berisi 4 grafik berbeda. Jaringan, CPU, GPU, dan Memori. Mereka cukup jelas, jadi saya akan segera membahasnya. Berikut adalah tangkapan layar dari grafik yang diambil saat mem-parsing beberapa JSON saat diunduh.

Monitor Android

Bagian Jaringan menunjukkan lalu lintas masuk dan keluar dalam KB/s. Bagian CPU menampilkan penggunaan CPU dalam persen. Monitor GPU menampilkan berapa banyak waktu yang diperlukan untuk merender bingkai jendela UI. Ini adalah monitor paling detail dari 4 ini, jadi jika Anda ingin detail lebih lanjut, baca ini.

Terakhir kami memiliki monitor Memori, yang mungkin paling sering Anda gunakan. Secara default ini menunjukkan jumlah memori Gratis dan Alokasi saat ini. Anda dapat memaksa Pengumpulan Sampah dengan itu juga, untuk menguji apakah jumlah memori yang digunakan turun. Ini memiliki fitur berguna yang disebut Dump Java Heap, yang akan membuat file HPROF yang dapat dibuka dengan HPROF Viewer dan Analyzer. Itu akan memungkinkan Anda untuk melihat berapa banyak objek yang telah Anda alokasikan, berapa banyak memori yang diambil oleh apa, dan mungkin objek mana yang menyebabkan kebocoran memori. Mempelajari cara menggunakan penganalisis ini bukanlah tugas yang paling sederhana di luar sana, tetapi sangat berharga. Hal berikutnya yang dapat Anda lakukan dengan Monitor Memori adalah melakukan Pelacakan Alokasi berjangka waktu, yang dapat Anda mulai dan hentikan sesuai keinginan. Ini bisa berguna dalam banyak kasus, misalnya saat menggulir atau memutar perangkat.

2. Penarikan GPU

Ini adalah alat pembantu sederhana, yang dapat Anda aktifkan di Opsi Pengembang setelah Anda mengaktifkan mode pengembang. Pilih Debug GPU overdraw, "Show overdraw area", dan layar Anda akan mendapatkan beberapa warna aneh. Tidak apa-apa, itulah yang seharusnya terjadi. Warna berarti berapa kali area tertentu digambar berlebihan. Warna asli berarti tidak ada overdraw, inilah yang harus Anda tuju. Biru berarti satu overdraw, hijau berarti dua, pink tiga, merah empat.

Penarikan GPU

Meskipun melihat warna asli adalah yang terbaik, Anda akan selalu melihat beberapa penarikan berlebih, terutama di sekitar teks, panel navigasi, dialog, dan banyak lagi. Jadi jangan mencoba untuk menyingkirkannya sepenuhnya. Jika aplikasi Anda berwarna kebiruan atau kehijauan, itu mungkin baik-baik saja. Namun, jika Anda melihat terlalu banyak warna merah pada beberapa layar sederhana, Anda harus menyelidiki apa yang terjadi. Mungkin terlalu banyak fragmen yang ditumpuk satu sama lain, jika Anda terus menambahkannya alih-alih menggantinya. Seperti yang telah saya sebutkan di atas, menggambar adalah bagian paling lambat dari aplikasi, jadi tidak ada gunanya menggambar sesuatu jika ada lebih dari 3 lapisan yang digambar di atasnya. Jangan ragu untuk memeriksa aplikasi favorit Anda dengannya. Anda akan melihat bahwa bahkan aplikasi dengan lebih dari satu miliar unduhan memiliki area merah, jadi santai saja saat Anda mencoba mengoptimalkan.

3. Rendering GPU

Ini adalah alat lain dari opsi Pengembang, yang disebut rendering GPU Profil. Setelah memilihnya, pilih "Di layar sebagai bilah". Anda akan melihat beberapa bar berwarna muncul di layar Anda. Karena setiap aplikasi memiliki bilah terpisah, anehnya bilah status memiliki bilahnya sendiri, dan jika Anda memiliki tombol navigasi perangkat lunak, mereka juga memiliki bilahnya sendiri. Bagaimanapun, bilah diperbarui saat Anda berinteraksi dengan layar.

Rendering GPU

Bilah terdiri dari 3-4 warna, dan menurut dokumen Android, ukurannya memang penting. Semakin kecil, semakin baik. Di bagian bawah Anda memiliki warna biru, yang mewakili waktu yang digunakan untuk membuat dan memperbarui daftar tampilan Tampilan. Jika bagian ini terlalu tinggi, berarti ada banyak gambar tampilan kustom, atau banyak pekerjaan yang dilakukan dalam fungsi onDraw() . Jika Anda memiliki Android 4.0+, Anda akan melihat bilah ungu di atas bilah biru. Ini mewakili waktu yang dihabiskan untuk mentransfer sumber daya ke utas render. Kemudian muncul bagian merah, yang mewakili waktu yang dihabiskan oleh penyaji 2D Android yang mengeluarkan perintah ke OpenGL untuk menggambar dan menggambar ulang daftar tampilan. Di bagian atas adalah bilah oranye, yang mewakili waktu CPU menunggu GPU menyelesaikan pekerjaannya. Jika terlalu tinggi, aplikasi melakukan terlalu banyak pekerjaan pada GPU.

Jika Anda cukup baik, ada satu warna lagi di atas oranye. Ini adalah garis hijau yang mewakili ambang 16 ms. Karena tujuan Anda harus menjalankan aplikasi pada 60 fps, Anda memiliki 16 ms untuk menggambar setiap frame. Jika Anda tidak berhasil, beberapa bingkai mungkin dilewati, aplikasi bisa menjadi tersentak-sentak, dan pengguna pasti akan menyadarinya. Berikan perhatian khusus pada animasi dan pengguliran, di situlah kehalusan paling penting. Meskipun Anda dapat mendeteksi beberapa bingkai yang dilewati dengan alat ini, itu tidak akan membantu Anda mengetahui di mana sebenarnya masalahnya.

4. Penampil Hirarki

Ini adalah salah satu alat favorit saya di luar sana, karena sangat kuat. Anda dapat memulainya dari Android Studio melalui Tools -> Android -> Android Device Monitor, atau juga di folder sdk/tools Anda sebagai "monitor". Anda juga dapat menemukan hierarachyviewer mandiri yang dapat dieksekusi di sana, tetapi karena sudah usang, Anda harus membuka monitor. Bagaimanapun Anda membuka Android Device Monitor, alihkan ke perspektif Hierarchy Viewer. Jika Anda tidak melihat aplikasi berjalan yang ditetapkan ke perangkat Anda, ada beberapa hal yang dapat Anda lakukan untuk memperbaikinya. Coba juga periksa utas masalah ini, ada orang dengan semua jenis masalah dan semua jenis solusi. Sesuatu harus bekerja untuk Anda juga.

Dengan Hierarchy Viewer, Anda bisa mendapatkan gambaran yang sangat rapi tentang hierarki tampilan Anda (jelas). Jika Anda melihat setiap tata letak dalam XML terpisah, Anda mungkin dengan mudah melihat tampilan yang tidak berguna. Namun, jika Anda terus menggabungkan tata letak, itu dapat dengan mudah membingungkan. Alat seperti ini membuatnya mudah dikenali, misalnya, beberapa RelativeLayout, yang hanya memiliki 1 turunan, RelativeLayout lainnya. Itu membuat salah satunya bisa dilepas.

Hindari memanggil requestLayout() , karena menyebabkan melintasi seluruh hierarki tampilan, untuk mengetahui seberapa besar setiap tampilan seharusnya. Jika ada beberapa konflik dengan pengukuran, hierarki mungkin dilalui beberapa kali, yang jika terjadi selama beberapa animasi, pasti akan membuat beberapa frame dilewati. Jika Anda ingin mengetahui lebih lanjut tentang cara Android menggambar tampilannya, Anda dapat membaca ini. Mari kita lihat satu tampilan seperti yang terlihat di Hierarchy Viewer.

Penampil Hirarki

Sudut kanan atas berisi tombol untuk memaksimalkan pratinjau tampilan tertentu di jendela mandiri. Di bawahnya Anda juga dapat melihat pratinjau sebenarnya dari tampilan di aplikasi. Item berikutnya adalah angka, yang menunjukkan berapa banyak anak yang dimiliki view tersebut, termasuk view itu sendiri. Jika Anda memilih simpul (sebaiknya yang akar) dan menekan “Dapatkan waktu tata letak” (3 lingkaran berwarna), Anda akan memiliki 3 nilai lagi yang terisi, bersama dengan lingkaran berwarna yang muncul berlabel ukuran, tata letak, dan gambar. Mungkin tidak mengejutkan bahwa fase pengukuran mewakili waktu yang diperlukan untuk mengukur tampilan yang diberikan. Fase tata letak adalah tentang waktu rendering, sedangkan menggambar adalah operasi menggambar yang sebenarnya. Nilai dan warna ini relatif satu sama lain. Warna hijau berarti tampilan ditampilkan di 50% teratas dari semua tampilan di pohon. Kuning berarti rendering dalam 50% lebih lambat dari semua tampilan di pohon, merah berarti tampilan yang diberikan adalah salah satu yang paling lambat. Karena nilai-nilai ini relatif, akan selalu ada yang merah. Anda tidak bisa menghindari mereka.

Di bawah nilai, Anda memiliki nama kelas, seperti "TextView", ID tampilan internal objek, dan android:id tampilan, yang Anda tetapkan dalam file XML. Saya mendorong Anda untuk membangun kebiasaan menambahkan ID ke semua tampilan, bahkan jika Anda tidak mereferensikannya dalam kode. Ini akan membuat mengidentifikasi tampilan di Hierarchy Viewer sangat sederhana, dan jika Anda memiliki pengujian otomatis dalam proyek Anda, itu juga akan membuat penargetan elemen jauh lebih cepat. Itu akan menghemat waktu Anda dan kolega Anda untuk menulisnya. Menambahkan ID ke elemen yang ditambahkan dalam file XML cukup mudah. Tapi bagaimana dengan elemen yang ditambahkan secara dinamis? Nah, ternyata sangat sederhana juga. Cukup buat file ids.xml di dalam folder nilai Anda dan ketik bidang yang diperlukan. Ini bisa terlihat seperti ini:

 <resources> <item name="item_title" type="id"/> <item name="item_body" type="id"/> </resources>

Kemudian dalam kode, Anda dapat menggunakan setId(R.id.item_title) . Itu tidak bisa lebih sederhana.

Ada beberapa hal lagi yang perlu diperhatikan saat mengoptimalkan UI. Anda biasanya harus menghindari hierarki yang dalam sambil lebih memilih yang dangkal, mungkin lebar. Jangan gunakan tata letak yang tidak Anda perlukan. Misalnya, Anda mungkin dapat mengganti grup LinearLayouts bersarang dengan RelativeLayout , atau TableLayout . Jangan ragu untuk bereksperimen dengan tata letak yang berbeda, jangan hanya selalu menggunakan LinearLayout dan RelativeLayout . Coba juga buat beberapa tampilan khusus saat diperlukan, ini dapat meningkatkan kinerja secara signifikan jika dilakukan dengan baik. Misalnya, tahukah Anda bahwa Instagram tidak menggunakan TextViews untuk menampilkan komentar?

Anda dapat menemukan beberapa info lebih lanjut tentang Hierarchy Viewer di situs Pengembang Android dengan deskripsi panel yang berbeda, menggunakan alat Pixel Perfect, dll. Satu hal lagi yang akan saya tunjukkan adalah menangkap tampilan dalam file .psd, yang dapat dilakukan oleh tombol "Tangkap lapisan jendela". Setiap tampilan akan berada di lapisan terpisah, jadi sangat mudah untuk menyembunyikan atau mengubahnya di Photoshop atau GIMP. Oh, itu alasan lain untuk menambahkan ID ke setiap tampilan yang Anda bisa. Ini akan membuat lapisan memiliki nama yang benar-benar masuk akal.

Anda akan menemukan lebih banyak alat debugging di opsi Pengembang, jadi saya menyarankan Anda untuk mengaktifkannya dan melihat apa yang mereka lakukan. Apa yang mungkin salah?

Situs pengembang Android berisi serangkaian praktik terbaik untuk kinerja. Mereka mencakup banyak bidang yang berbeda, termasuk manajemen memori, yang belum pernah saya bicarakan. Saya diam-diam mengabaikannya, karena menangani memori dan melacak kebocoran memori adalah cerita yang terpisah. Menggunakan perpustakaan pihak ketiga untuk menampilkan gambar secara efisien akan banyak membantu, tetapi jika Anda masih memiliki masalah memori, lihat Leak canary yang dibuat oleh Square, atau baca ini.

Membungkus

Jadi, ini adalah kabar baik. Hal baru yang buruk adalah, mengoptimalkan aplikasi Android jauh lebih rumit. Ada banyak cara untuk melakukan semuanya, jadi Anda harus terbiasa dengan pro dan kontra dari mereka. Biasanya tidak ada solusi peluru perak yang hanya memiliki manfaat. Hanya dengan memahami apa yang terjadi di balik layar, Anda akan dapat memilih solusi yang terbaik untuk Anda. Hanya karena pengembang favorit Anda mengatakan bahwa ada sesuatu yang baik, itu tidak berarti bahwa itu adalah solusi terbaik untuk Anda. Ada lebih banyak area untuk didiskusikan dan lebih banyak alat pembuatan profil yang lebih canggih, jadi kami mungkin akan membahasnya lain kali.

Pastikan Anda belajar dari pengembang top dan perusahaan top. Anda dapat menemukan beberapa ratus blog teknik di tautan ini. Ini jelas bukan hanya hal-hal terkait Android, jadi jika Anda hanya tertarik pada Android, Anda harus memfilter blog tertentu. Saya akan sangat merekomendasikan blog Facebook dan Instagram. Meskipun UI Instagram di Android dipertanyakan, blog teknik mereka memiliki beberapa artikel yang sangat keren. Bagi saya itu luar biasa bahwa sangat mudah untuk melihat bagaimana hal-hal dilakukan di perusahaan yang menangani ratusan juta pengguna setiap hari, jadi tidak membaca blog mereka tampak gila. Dunia berubah sangat cepat, jadi jika Anda tidak terus-menerus berusaha meningkatkan, belajar dari orang lain, dan menggunakan alat baru, Anda akan tertinggal. Seperti yang dikatakan Mark Twain, orang yang tidak membaca tidak memiliki kelebihan dibandingkan orang yang tidak bisa membaca.