Sistem Android Go yang ringan telah meningkatkan persyaratan untuk aplikasi yang telah diinstal sebelumnya - ukuran dan memori yang digunakan. Kami menghadapi tantangan untuk memenuhi persyaratan ini. Kami melakukan sejumlah optimisasi dan memutuskan untuk secara serius mengubah arsitektur shell grafis kami - Yandex.Luncher. Kepala tim pengembangan aplikasi mobile Alexander Starchenko berbagi pengalaman ini.
- Nama saya Alexander, saya dari St. Petersburg, dari tim yang mengembangkan Yandex.Loncher dan Yandex.Phone. Hari ini saya akan memberi tahu Anda bagaimana kami mengoptimalkan memori di Launcher. Pertama, saya akan menjelaskan secara singkat apa itu Launcher. Selanjutnya, kita membahas alasan mengapa kita perlu mengoptimalkan memori. Setelah itu, kami akan mempertimbangkan cara mengukur memori dengan benar dan terdiri dari apa. Kemudian mari kita lanjutkan berlatih. Saya akan berbicara tentang bagaimana kita mengoptimalkan memori di Launcher dan bagaimana kita sampai pada solusi radikal untuk masalah ini. Dan pada akhirnya saya akan berbicara tentang bagaimana kita memantau penggunaan memori, bagaimana kita mengendalikannya.

"Peluncur" atau "Peluncur" - tidak begitu penting. Kami di Yandex biasa memanggilnya Peluncur, dan dalam laporan saya akan menggunakan kata "Peluncur".

Poin penting lainnya: Peluncur didistribusikan secara luas melalui preset, yaitu, ketika Anda membeli ponsel baru, Yandex.Loncher cukup sering berubah menjadi satu-satunya manajer aplikasi, manajer desktop rumah di ponsel Anda.
Sekarang untuk alasan mengapa kita perlu mengoptimalkan memori. Saya akan mulai dengan alasan kami. Singkatnya, ini Android Go. Dan sekarang lebih lama. Pada akhir 2017, Google memperkenalkan Android Oreo dan versi khusus, edisi Android Oreo Go. Apa yang spesial tentang ini? Versi ini dirancang untuk ponsel kelas bawah, untuk ponsel murah dengan RAM hingga satu gigabyte. Apa lagi yang dia spesial? Untuk aplikasi yang sudah diinstal pada versi Android ini, Google mengajukan persyaratan tambahan. Secara khusus - persyaratan untuk konsumsi RAM. Secara kasar, beberapa saat setelah peluncuran, memori aplikasi dihapus, dan ukurannya tidak boleh melebihi 30-50 megabita untuk Peluncur, tergantung pada ukuran layar ponsel. 30 pada yang terkecil, 50 pada layar besar.
Perlu juga dicatat bahwa Google terus mengembangkan daerah ini, dan sudah ada edisi Android Pie Go.
Apa alasan lain untuk mengoptimalkan penggunaan memori? Pertama, aplikasi Anda cenderung tidak akan mengunduh. Kedua, ini akan bekerja lebih cepat, karena akan lebih kecil kemungkinannya untuk memungut pemulung dan memori akan lebih jarang dialokasikan. Objek tambahan tidak akan dibuat, tampilan tambahan tidak akan meningkat, dll. Secara tidak langsung, menilai dari pengalaman kami, ini akan menyebabkan penurunan ukuran apk aplikasi Anda. Semua ini bersama-sama akan memberi Anda lebih banyak instalasi dan peringkat yang lebih baik di Google Play.
Oke, sekarang kita tahu mengapa mengoptimalkan memori. Mari kita lihat apa artinya mengukur dan terdiri dari apa.

Mungkin banyak dari Anda telah melihat gambar ini. Ini adalah tangkapan layar dari Android Studio Profile, dari tampilan memori. Alat ini dijelaskan secara cukup rinci di developer.android.com. Mungkin banyak dari Anda telah menggunakannya. Siapa yang tidak menggunakan - coba.
Apa yang baik di sini? Itu selalu ada di tangan. Lebih mudah digunakan dalam proses pengembangan. Namun, ia memiliki beberapa kelemahan. Tidak semua alokasi aplikasi Anda terlihat di sini. Misalnya, font yang diunduh tidak terlihat di sini. Juga, dengan bantuan alat ini tidak nyaman untuk melihat kelas mana yang dimuat ke dalam memori, dan Anda tidak dapat menggunakan alat ini dalam mode otomatis, yaitu, Anda tidak dapat mengonfigurasi beberapa jenis tes otomatis berdasarkan pada Android Studio Profile.

Alat berikut telah ada sejak pengembangan Android di Eclipse, singkatnya adalah Memory Analyzer, MAT. Ini disediakan sebagai aplikasi mandiri dan kompatibel dengan dump memori yang dapat Anda simpan dari Android Studio.
Untuk melakukan ini, Anda harus menggunakan utilitas kecil, konverter profesional. Muncul dengan edisi Android Go dan memiliki beberapa keunggulan. Sebagai contoh, ia dapat membangun Paths ke gs root. Ini sangat membantu kami untuk melihat kelas mana yang dimuat oleh Launcher dan kapan mereka dimuat. Kami tidak dapat melakukan ini menggunakan Android Studio Profiler.

Alat selanjutnya adalah utilitas dumpsys, khususnya dumpsys meminfo. Di sini Anda melihat bagian dari output dari perintah ini. Ini memberikan pengetahuan tingkat tinggi tentang konsumsi memori. Namun, ia memiliki kelebihan tertentu. Lebih mudah digunakan dalam mode otomatis. Anda dapat dengan mudah mengkonfigurasi tes yang menjalankan perintah ini. Ini juga menunjukkan memori segera untuk semua proses. Dan menunjukkan semua lokasi. Sejauh yang kami tahu, Google menggunakan nilai memori dari alat ini dalam proses pengujian.
Mari kita gunakan contoh output untuk menjelaskan secara singkat apa yang terdiri dari memori aplikasi. Yang pertama adalah Java Heap, semua lokasi kode Java dan Kotlin Anda. Biasanya bagian ini cukup besar. Selanjutnya adalah Native Heap. Berikut adalah alokasi dari kode asli. Bahkan jika Anda tidak secara eksplisit menggunakan kode asli dalam aplikasi Anda, alokasi akan ada di sini, karena banyak objek Android - tampilan yang sama - mengalokasikan memori asli. Bagian selanjutnya adalah Kode. Segala sesuatu yang berkaitan dengan kode sampai di sini: bytecode, font. Kode juga bisa sangat besar jika Anda menggunakan banyak pustaka pihak ketiga yang tidak dioptimalkan. Berikut ini adalah tumpukan perangkat lunak Jawa dan kode asli, biasanya kecil. Selanjutnya adalah memori grafis. Ini termasuk Permukaan, tekstur, yaitu, memori yang menyebar antara CPU dan GPU digunakan untuk rendering. Berikutnya adalah bagian Pribadi Lainnya. Ini termasuk semua yang tidak termasuk dalam bagian di atas, segala sesuatu yang sistem tidak bisa hancurkan. Biasanya ini adalah semacam alokasi asli. Berikutnya adalah bagian Sistem, ini adalah bagian dari memori sistem yang dikaitkan dengan aplikasi Anda.
Dan pada akhirnya kami memiliki TOTAL, ini adalah jumlah dari semua bagian yang terdaftar. Kami ingin menguranginya.

Apa lagi yang penting untuk diketahui tentang pengukuran memori? Pertama-tama, aplikasi kita tidak sepenuhnya mengendalikan semua alokasi. Artinya, kami, sebagai pengembang, tidak memiliki kendali penuh atas kode apa yang akan diunduh.
Berikut ini. Memori aplikasi dapat melompat banyak. Selama proses pengukuran, Anda dapat mengamati perbedaan kuat dalam bacaan. Ini mungkin karena waktu yang dibutuhkan serta berbagai skenario. Dalam hal ini, ketika kita mengoptimalkan memori, menganalisisnya, sangat penting untuk melakukan ini dalam kondisi yang sama. Idealnya, di perangkat yang sama. Lebih baik lagi jika Anda memiliki opsi untuk memanggil Pengumpul Sampah.
Bagus Kita tahu mengapa kita perlu mengoptimalkan memori, bagaimana mengukurnya dengan benar, terdiri dari apa. Ayo berlatih, dan saya akan memberi tahu Anda bagaimana kami mengoptimalkan memori di Launcher.

Itu adalah situasi pada awalnya. Kami memiliki tiga proses, yang secara total dialokasikan sekitar 120 megabita. Ini hampir empat kali lebih banyak dari yang kita ingin terima.

Mengenai alokasi proses utama, ada bagian besar Java Heap, banyak grafik, kode besar, dan Native Heap yang cukup besar.

Pertama, kami mendekati masalah dengan cukup naif dan memutuskan untuk mengikuti beberapa rekomendasi dari Google dari beberapa sumber, mencoba menyelesaikan masalah dengan cepat. Kami menarik perhatian pada metode sintetis yang dihasilkan selama proses kompilasi. Kami memiliki lebih dari 2 ribu dari mereka. Dalam beberapa jam, kita semua menghapusnya, menghapus memori.

Dan mereka mendapat keuntungan sekitar satu atau dua megabyte di bagian kode. Bagus
Selanjutnya, kami mengalihkan perhatian ke enum. Seperti yang Anda tahu, enum adalah kelas. Dan seperti yang Google akhirnya akui, enum tidak terlalu efisien dalam memori. Kami menerjemahkan semua enum ke InDef dan StringDef. Di sini Anda dapat mengajukan keberatan kepada saya bahwa ProgArt akan membantu di sini. Namun pada kenyataannya, ProgArt tidak akan mengganti semua enum dengan tipe primitif. Lebih baik melakukannya sendiri. Omong-omong, kami punya lebih dari 90 enum, cukup banyak.

Pengoptimalan ini telah memakan waktu berhari-hari, karena sebagian besar harus dilakukan secara manual, dan kami memenangkan sekitar tiga hingga enam megabita di bagian tumpukan Java.
Selanjutnya, kami menarik perhatian ke koleksi. Kami menggunakan koleksi Java yang cukup standar, seperti HashMap. Kami memiliki lebih dari 150 dari mereka, dan semuanya diciptakan pada awal Launcher. Kami menggantinya dengan SparseArray, SimpleArrayMap dan ArrayMap dan mulai membuat koleksi dengan ukuran yang telah ditentukan sehingga slot kosong tidak akan dialokasikan. Artinya, kami meneruskan ukuran koleksi ke konstruktor.

Ini juga memberikan keuntungan tertentu, dan pengoptimalan ini juga memakan waktu berhari-hari, yang sebagian besar kami lakukan secara manual.
Kemudian kami mengambil langkah yang lebih spesifik. Kami melihat bahwa kami memiliki tiga proses. Seperti yang kita ketahui, bahkan proses kosong di Android membutuhkan sekitar 8-10 megabita memori, cukup banyak.
Rincian tentang proses itu disampaikan oleh rekan saya Arthur Vasilov. Belum lama ini di konferensi Mosdroid
adalah laporannya , juga tentang Android Go.

Apa yang kami miliki setelah optimasi ini? Pada perangkat uji utama, kami mengamati konsumsi memori di wilayah 80-100 megabyte, tidak cukup buruk, tetapi masih belum cukup. Kami mulai mengukur memori pada perangkat lain. Kami menemukan bahwa pada perangkat yang lebih cepat, konsumsi memori jauh lebih besar. Ternyata kami memiliki banyak inisialisasi tertunda yang berbeda. Setelah beberapa waktu, Launcher menggelembungkan beberapa tampilan, memulai beberapa perpustakaan, dll.

Apa yang telah kita lakukan Pertama-tama, kami melewati tampilan, semua tata letak. Menghapus semua tampilan yang meningkat dengan visibilitas yang hilang. Mereka membawa mereka ke tata letak yang terpisah, mulai mengembang mereka secara terprogram. Yang tidak kami butuhkan, kami biasanya berhenti menggembung sampai saat pengguna membutuhkannya. Kami memperhatikan optimasi gambar. Kami berhenti memuat gambar yang tidak dilihat pengguna saat ini. Dalam kasus Launcher, ini adalah gambar-ikon aplikasi dalam daftar aplikasi lengkap. Sampai pembukaannya, kami tidak mengirimkannya. Ini memberi kami kemenangan yang sangat baik di bagian grafis.
Kami juga memeriksa cache gambar kami di memori. Ternyata tidak semuanya optimal, tidak semua gambar yang sesuai dengan layar ponsel tempat Launcher dijalankan disimpan dalam memori.
Setelah itu, kami mulai menganalisis bagian kode dan memperhatikan bahwa kami memiliki banyak kelas yang agak berat dari suatu tempat. Ternyata ini sebagian besar adalah kelas perpustakaan. Kami menemukan beberapa hal aneh di beberapa perpustakaan. Salah satu perpustakaan membuat HashMap dan dalam initializer statis itu menyumbatnya dengan sejumlah objek yang cukup besar.

Perpustakaan lain juga memuat file audio dalam blok statis, yang menempati sekitar 700 kilobyte memori.

Kami berhenti menginisialisasi perpustakaan seperti itu, mulai bekerja dengannya hanya ketika fungsi-fungsi ini benar-benar dibutuhkan oleh pengguna. Semua optimisasi ini memakan waktu beberapa minggu. Kami menguji banyak, memeriksa bahwa kami tidak memperkenalkan masalah tambahan. Tapi kami juga mendapat kemenangan yang cukup baik, sekitar 25 dari 40 megabita di bagian Native, Heap, Code, dan Java Heap.
Tetapi ini tidak cukup. Konsumsi memori masih belum turun hingga 30 megabyte. Tampaknya kami telah kehabisan semua opsi untuk beberapa optimasi otomatis dan aman sederhana.
Kami memutuskan untuk mempertimbangkan solusi radikal. Di sini kita melihat dua pilihan - pembuatan aplikasi lite-terpisah atau pemrosesan arsitektur Launcher dan transisi ke arsitektur modular dengan kemampuan untuk membangun Launcher tanpa modul tambahan. Opsi pertama cukup panjang dan mahal. Kemungkinan besar, pembuatan aplikasi semacam itu akan menghasilkan aplikasi terpisah yang lengkap untuk Anda, yang perlu didukung dan dikembangkan sepenuhnya. Di sisi lain, opsi dengan arsitektur modular juga cukup mahal, cukup berisiko, tetapi tetap lebih cepat, karena Anda sudah bekerja dengan basis kode yang terkenal, Anda sudah memiliki serangkaian tes unit otomatis, tes integrasi, dan tes manual kasus.
Perlu dicatat bahwa apa pun pilihan yang Anda pilih, Anda harus meninggalkan sebagian fitur aplikasi Anda dalam versi untuk Android Go. Ini normal. Google melakukan hal yang sama di aplikasi Go-nya.
Sebagai hasilnya, setelah menerapkan arsitektur modular, kami cukup andal memecahkan masalah memori kami dan mulai lulus tes bahkan pada perangkat dengan layar kecil, yaitu, kami mengurangi konsumsi memori hingga 30 megabyte.

Sedikit tentang pemantauan memori, tentang bagaimana kita mengendalikan penggunaan memori. Pertama-tama, kami menyiapkan analisis statis, Lint pada kesalahan yang sama dalam kasus ketika kami menggunakan enum, membuat metode sintetis atau menggunakan koleksi yang tidak dioptimalkan.
Lebih jauh lebih sulit. Kami menyiapkan tes integrasi otomatis yang menjalankan Peluncur pada emulator dan setelah beberapa saat lepaskan konsumsi memori. Jika sangat berbeda dari bangunan sebelumnya, peringatan dan peringatan dipicu. Kemudian kami mulai menyelidiki masalah dan tidak mempublikasikan perubahan yang meningkatkan penggunaan memori Launcher.
Untuk meringkas. Ada berbagai alat untuk memonitor memori, mengukur memori untuk operasi yang cepat dan efisien. Lebih baik menggunakan semuanya, karena mereka memiliki pro dan kontra.
Solusi radikal dengan arsitektur modular ternyata lebih andal dan efisien bagi kami. Kami menyesal tidak segera meminumnya. Tetapi langkah-langkah yang saya bicarakan di awal laporan tidak sia-sia. Kami memperhatikan bahwa versi utama aplikasi mulai menggunakan memori secara optimal, untuk bekerja lebih cepat. Terima kasih