Random access memory (RAM) merupakan resource
berharga dalam setiap lingkungan pengembangan software, terlebih
pada sistem operasi seluler
yang sering dibatasi oleh memori fisik.
Meskipun Android Runtime (ART) dan mesin virtual Dalvik menjalankan
pembersihan sampah memori secara rutin, hal ini tidak berarti Anda dapat mengabaikan
kapan dan di mana aplikasi Anda mengalokasikan dan melepaskan memori.
Anda tetap perlu menghindari
timbulnya kebocoran memori, yang biasanya disebabkan oleh penahanan
referensi objek dalam variabel anggota statis, dan
melepaskan objek Reference
pada waktu yang tepat
seperti ditetapkan oleh
callback siklus proses.
Halaman ini menjelaskan cara mengurangi penggunaan memori secara proaktif dalam aplikasi Anda. Untuk informasi tentang cara sistem operasi Android mengelola memori, lihat Ringkasan Pengelolaan Memori Android.
Memantau memori yang tersedia dan penggunaan memori
Agar dapat memperbaiki masalah penggunaan memori pada aplikasi, Anda harus menemukan masalah tersebut terlebih dahulu. Memory Profiler di Android Studio membantu Anda menemukan dan mendiagnosis masalah memori melalui cara berikut:
- Lihat bagaimana aplikasi Anda mengalokasikan memori dari waktu ke waktu. Memory Profiler menampilkan grafik real time yang menunjukkan banyaknya memori yang digunakan aplikasi Anda, jumlah objek Java yang dialokasikan, dan kapan pembersihan sampah memori dilakukan.
- Mulai peristiwa pembersihan sampah memori dan ambil cuplikan dari heap Java selagi aplikasi berjalan.
- Rekam alokasi memori aplikasi Anda, lalu periksa semua objek yang dialokasikan, lihat pelacakan tumpukan untuk setiap alokasi, dan beralihlah ke kode yang sesuai pada editor Android Studio.
Melepaskan memori sebagai respons terhadap peristiwa
Seperti dijelaskan dalam Ringkasan Pengelolaan Memori Android, Android bisa mendapatkan kembali memori dari aplikasi Anda melalui beberapa cara atau, jika perlu, mengakhiri aplikasi Anda sepenuhnya guna mengosongkan memori untuk tugas-tugas penting. Untuk menyeimbangkan memori sistem lebih jauh lagi dan menghindari pengakhiran proses aplikasi oleh sistem, Anda dapat mengimplementasikan antarmuka ComponentCallbacks2
dalam class Activity
. Metode callback onTrimMemory()
yang diberikan memungkinkan aplikasi Anda mendeteksi peristiwa terkait memori saat aplikasi Anda berada di latar depan atau latar belakang, dan kemudian melepaskan objek sebagai respons terhadap peristiwa siklus proses aplikasi atau sistem yang mengindikasikan bahwa sistem perlu mendapatkan kembali memori.
Misalnya, Anda dapat mengimplementasikan callback onTrimMemory()
untuk merespons berbagai peristiwa terkait memori seperti yang ditunjukkan di sini:
Kotlin
import android.content.ComponentCallbacks2 // Other import statements ... class MainActivity : AppCompatActivity(), ComponentCallbacks2 { // Other activity code ... /** * Release memory when the UI becomes hidden or when system resources become low. * @param level the memory-related event that was raised. */ override fun onTrimMemory(level: Int) { // Determine which lifecycle or system event was raised. when (level) { ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> { /* Release any UI objects that currently hold memory. The user interface has moved to the background. */ } ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE, ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW, ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> { /* Release any memory that your app doesn't need to run. The device is running low on memory while the app is running. The event raised indicates the severity of the memory-related event. If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will begin killing background processes. */ } ComponentCallbacks2.TRIM_MEMORY_BACKGROUND, ComponentCallbacks2.TRIM_MEMORY_MODERATE, ComponentCallbacks2.TRIM_MEMORY_COMPLETE -> { /* Release as much memory as the process can. The app is on the LRU list and the system is running low on memory. The event raised indicates where the app sits within the LRU list. If the event is TRIM_MEMORY_COMPLETE, the process will be one of the first to be terminated. */ } else -> { /* Release any non-critical data structures. The app received an unrecognized memory level value from the system. Treat this as a generic low-memory message. */ } } } }
Java
import android.content.ComponentCallbacks2; // Other import statements ... public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 { // Other activity code ... /** * Release memory when the UI becomes hidden or when system resources become low. * @param level the memory-related event that was raised. */ public void onTrimMemory(int level) { // Determine which lifecycle or system event was raised. switch (level) { case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: /* Release any UI objects that currently hold memory. The user interface has moved to the background. */ break; case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: /* Release any memory that your app doesn't need to run. The device is running low on memory while the app is running. The event raised indicates the severity of the memory-related event. If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will begin killing background processes. */ break; case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: case ComponentCallbacks2.TRIM_MEMORY_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: /* Release as much memory as the process can. The app is on the LRU list and the system is running low on memory. The event raised indicates where the app sits within the LRU list. If the event is TRIM_MEMORY_COMPLETE, the process will be one of the first to be terminated. */ break; default: /* Release any non-critical data structures. The app received an unrecognized memory level value from the system. Treat this as a generic low-memory message. */ break; } } }
Callback
onTrimMemory()
ditambahkan di Android 4.0 (API level 14). Untuk versi sebelumnya,
Anda dapat menggunakan
onLowMemory()
yang kira-kira setara dengan
peristiwa TRIM_MEMORY_COMPLETE
.
Memeriksa banyaknya memori yang sebaiknya digunakan
Agar beberapa proses dapat berjalan sekaligus, Android menetapkan batas pasti
ukuran heap yang dialokasikan untuk setiap aplikasi. Batas ukuran heap yang pasti ini bervariasi
antarperangkat, tergantung banyaknya RAM yang dimiliki perangkat
secara keseluruhan. Jika aplikasi Anda telah mencapai kapasitas heap dan
mencoba mengalokasikan lebih banyak
memori, sistem akan menampilkan OutOfMemoryError
.
Untuk menghindari kehabisan memori, Anda dapat mengkueri sistem untuk mengetahui
banyaknya ruang heap yang Anda miliki pada perangkat saat ini.
Untuk mengetahui angkanya, panggil
getMemoryInfo()
. Ini mengembalikan objek
ActivityManager.MemoryInfo
yang memberikan
informasi tentang status
memori saat ini pada perangkat, termasuk memori yang tersedia, memori total, dan
ambang batas memori—tingkat memori yang mana sistem akan mulai
mengakhiri proses. Objek
ActivityManager.MemoryInfo
juga menampilkan boolean sederhana,
lowMemory
yang memberi tahu Anda apakah perangkat kehabisan memori.
Cuplikan kode berikut menunjukkan contoh bagaimana Anda dapat menggunakan metode
getMemoryInfo()
pada aplikasi Anda.
Kotlin
fun doSomethingMemoryIntensive() { // Before doing something that requires a lot of memory, // check to see whether the device is in a low memory state. if (!getAvailableMemory().lowMemory) { // Do memory intensive work ... } } // Get a MemoryInfo object for the device's current memory status. private fun getAvailableMemory(): ActivityManager.MemoryInfo { val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager return ActivityManager.MemoryInfo().also { memoryInfo -> activityManager.getMemoryInfo(memoryInfo) } }
Java
public void doSomethingMemoryIntensive() { // Before doing something that requires a lot of memory, // check to see whether the device is in a low memory state. ActivityManager.MemoryInfo memoryInfo = getAvailableMemory(); if (!memoryInfo.lowMemory) { // Do memory intensive work ... } } // Get a MemoryInfo object for the device's current memory status. private ActivityManager.MemoryInfo getAvailableMemory() { ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE); ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memoryInfo); return memoryInfo; }
Menggunakan konstruksi kode yang lebih hemat memori
Beberapa fitur Android, class Java, dan konstruksi kode cenderung menggunakan lebih banyak memori dibandingkan yang lain. Anda dapat meminimalkan banyaknya memori yang digunakan aplikasi dengan memilih alternatif yang lebih efisien dalam kode Anda.
Menggunakan layanan seperlunya
Membiarkan layanan berjalan saat tidak diperlukan merupakan salah satu kesalahan pengelolaan memori terburuk yang bisa dilakukan aplikasi Android. Jika aplikasi Anda memerlukan layanan untuk menjalankan tugas di latar belakang, jangan biarkan layanan tersebut terus berjalan kecuali jika diperlukan untuk menjalankan tugas. Ingatlah untuk menghentikan layanan saat tugasnya selesai. Jika tidak, Anda berisiko menimbulkan kebocoran memori secara tidak sengaja.
Saat Anda memulai layanan, sistem lebih suka mempertahankan agar proses untuk layanan tersebut selalu berjalan. Perilaku ini menjadikan proses layanan sangat tidak efisien karena RAM yang digunakan oleh layanan tidak tersedia untuk proses-proses lainnya. Keadaan ini mengurangi jumlah proses yang di-cache yang dapat dipertahankan sistem dalam cache LRU, sehingga peralihan aplikasi menjadi kurang efisien. Selain itu, tindakan ini dapat membebani sistem saat memori yang tersedia sangat terbatas dan sistem tidak dapat mempertahankan cukup proses untuk meng-hosting semua layanan yang sedang berjalan.
Secara umum, sebaiknya Anda menghindari penggunaan layanan persisten karena
jenis layanan ini terus-menerus membebani memori yang tersedia. Sebagai gantinya, kami
merekomendasikan penggunaan implementasi alternatif
seperti JobScheduler
. Untuk mengetahui informasi selengkapnya
tentang cara menggunakan JobScheduler
untuk menjadwalkan proses
latar belakang, lihat
Pengoptimalan Latar Belakang.
Jika Anda harus menggunakan layanan, cara
terbaik untuk membatasi masa aktif layanan adalah dengan menggunakan IntentService
, yang akan menutup
dengan sendirinya begitu selesai menangani intent yang memulainya.
Untuk mengetahui informasi selengkapnya, baca
Menjalankan Layanan di Latar Belakang.
Menggunakan container data yang dioptimalkan
Beberapa class yang disediakan oleh bahasa pemrograman tidak dioptimalkan untuk
penggunaan pada perangkat seluler. Misalnya, implementasi
HashMap
generik bisa sangat menguras memori
karena memerlukan objek entri tersendiri untuk setiap pemetaan.
Framework Android mencakup beberapa container data yang dioptimalkan, termasuk
SparseArray
, SparseBooleanArray
,
dan LongSparseArray
.
Misalnya, class SparseArray
lebih
efisien karena dapat mencegah perlunya sistem melakukan
autobox
pada kunci dan terkadang nilai (yang membuat satu objek lagi atau
dua objek per entri).
Jika perlu, Anda dapat beralih ke array mentah kapan saja untuk menggunakan struktur data yang benar-benar ramping.
Hati-hati dengan abstraksi kode
Developer sering menggunakan abstraksi sebagai praktik pemrograman yang baik, karena abstraksi dapat meningkatkan fleksibilitas dan pemeliharaan kode. Namun, abstraksi memunculkan risiko signifikan: biasanya, abstraksi memerlukan eksekusi kode yang lebih banyak, yang berarti memerlukan lebih banyak waktu dan lebih banyak RAM agar kode dapat dipetakan ke dalam memori. Karena itu, jika abstraksi Anda tidak memberikan manfaat signifikan, sebaiknya Anda menghindarinya.
Menggunakan lite protobuf untuk data serial
Buffering protokol (protobuf) adalah mekanisme tidak tergantung bahasa, tidak tergantung platform, dan dapat diperluas yang dirancang Google untuk membuat serialisasi data terstruktur—mirip dengan XML, tetapi lebih kecil, lebih cepat, dan lebih sederhana. Jika ingin menggunakan protobuf untuk data Anda, sebaiknya Anda selalu menggunakan lite protobuf dalam kode sisi klien. Protobuf reguler menghasilkan kode yang sangat panjang, yang dapat menyebabkan berbagai masalah pada aplikasi seperti peningkatan penggunaan RAM, peningkatan ukuran APK secara signifikan, dan eksekusi yang lebih lambat.
Untuk mengetahui informasi selengkapnya, lihat bagian "Versi lite" di protobuf readme.
Menghindari churn memori
Seperti yang disebutkan sebelumnya, peristiwa pembersihan sampah memori tidak memengaruhi performa aplikasi. Namun, banyaknya peristiwa pembersihan sampah memori yang terjadi dalam waktu singkat dapat dengan cepat menghabiskan daya baterai serta sedikit meningkatkan waktu untuk menyiapkan bingkai karena interaksi yang diperlukan antara pembersih sampah memori dan thread aplikasi data. Semakin banyak waktu yang dihabiskan sistem untuk pembersihan sampah memori, semakin cepat daya baterai habis.
Sering kali, churn memori dapat menyebabkan sejumlah besar peristiwa pembersihan sampah memori terjadi. Dalam praktiknya, churn memori menunjukkan jumlah pengalokasian objek sementara yang terjadi dalam rentang waktu tertentu.
Misalnya, Anda dapat mengalokasikan beberapa objek sementara dalam
loop for
. Atau Anda dapat membuat objek
Paint
atau Bitmap
baru di dalam
fungsi onDraw()
tampilan.
Dalam kedua kasus ini, aplikasi akan membuat banyak objek dengan cepat pada volume tinggi.
Hal ini dapat menghabiskan semua memori yang tersedia dengan cepat,
sehingga memaksa terjadinya peristiwa pembersihan sampah memori.
Tentu saja, Anda perlu menemukan area dalam kode dengan churn memori yang tinggi agar dapat mengatasi masalah ini. Untuk itu, sebaiknya Anda menggunakan Memory Profiler di Android Studio.
Setelah mengidentifikasi area masalah dalam kode Anda, cobalah untuk mengurangi jumlah alokasi dalam area yang kritis performa. Pertimbangkan untuk mengeluarkan objek dari loop dalam, atau mungkin memindahkannya ke dalam struktur alokasi berbasis Factory.
Kemungkinan lainnya adalah mengevaluasi apakah kumpulan objek menguntungkan kasus penggunaan. Dengan kumpulan objek, bukan melepas instance objek di lantai, Anda akan melepaskannya ke dalam kumpulan setelah tidak diperlukan lagi. Saat berikutnya instance objek dari jenis tersebut diperlukan, instance tersebut dapat diperoleh dari kumpulan, bukan mengalokasikannya.
Evaluasi performa secara menyeluruh sangat penting untuk menentukan apakah kumpulan objek cocok dalam situasi tertentu. Ada kalanya kumpulan objek dapat memperburuk performa. Meskipun menghindari alokasi, kumpulan tersebut menimbulkan overhead lain. Misalnya, mempertahankan kumpulan biasanya melibatkan sinkronisasi yang memiliki overhead yang tidak dapat diabaikan. Selain itu, menghapus instance objek gabungan (untuk menghindari kebocoran memori) selama release, lalu inisialisasinya selama acquire dapat memiliki overhead yang bukan nol. Terakhir, menahan lebih banyak instance objek di dalam kumpulan daripada yang diinginkan juga akan membebani GC. Meskipun kumpulan objek mengurangi jumlah pemanggilan GC, kumpulan objek tersebut pada akhirnya meningkatkan jumlah pekerjaan yang perlu dilakukan pada setiap pemanggilan, karena ini sebanding dengan jumlah byte live (yang dapat dijangkau).
Menghapus resource dan library yang boros memori
Beberapa resource dan library dalam kode Anda dapat menghabiskan memori tanpa Anda sadari. Ukuran keseluruhan APK, termasuk library pihak ketiga atau resource tersemat, dapat memengaruhi jumlah memori yang digunakan aplikasi Anda. Anda dapat memperbaiki konsumsi memori aplikasi dengan menghapus komponen, resource, atau library yang berlebihan, tidak perlu, atau membengkak dari kode Anda.
Mengurangi ukuran APK secara keseluruhan
Anda dapat mengurangi penggunaan memori aplikasi secara signifikan dengan mengurangi ukuran keseluruhan aplikasi. Ukuran bitmap, sumber daya, bingkai animasi, dan library pihak ketiga semuanya dapat berkontribusi pada ukuran aplikasi Anda. Android Studio dan Android SDK menyediakan beberapa alat untuk membantu Anda mengurangi ukuran sumber daya dan dependensi eksternal. Alat ini mendukung metode penyingkatan kode modern, seperti kompilasi R8. (Android Studio 3.3 dan yang lebih rendah menggunakan ProGuard, bukan kompilasi R8.)
Untuk mengetahui informasi selengkapnya tentang cara mengurangi ukuran APK secara keseluruhan, lihat panduan cara mengurangi ukuran aplikasi Anda.
Menggunakan Dagger 2 untuk injeksi dependensi
Framework injeksi dependensi dapat menyederhanakan kode yang Anda tulis dan memberikan lingkungan adaptif yang berguna untuk pengujian dan perubahan konfigurasi lainnya.
Jika Anda ingin menggunakan framework injeksi dependensi pada aplikasi Anda, pertimbangkan untuk menggunakan Dagger 2. Dagger tidak menggunakan refleksi untuk memindai kode aplikasi. Dengan implementasi waktu kompilasi statis, Dagger dapat digunakan pada aplikasi Android tanpa biaya runtime atau penggunaan memori yang tidak perlu.
Framework injeksi dependensi lain yang menggunakan refleksi cenderung menginisialisasi proses dengan memindai kode untuk menemukan anotasi. Proses ini dapat memerlukan siklus CPU dan RAM yang jauh lebih banyak, dan dapat menyebabkan keterlambatan yang kentara saat aplikasi diluncurkan.
Hati-hati saat menggunakan library eksternal
Kode library eksternal sering kali tidak ditulis untuk lingkungan seluler dan dapat menjadi tidak efisien saat digunakan untuk bekerja pada klien seluler. Jika memutuskan untuk menggunakan library eksternal, Anda mungkin perlu mengoptimalkannya untuk perangkat seluler. Rencanakan tugas tersebut di muka dan analisis library dalam hal ukuran kode dan kebutuhan RAM sebelum memutuskan untuk menggunakannya.
Bahkan beberapa library yang dioptimalkan untuk lingkungan seluler pun dapat menyebabkan masalah karena implementasinya yang berbeda. Misalnya, satu library mungkin menggunakan lite protobuf sementara library lain menggunakan micro protobuf, yang menghasilkan dua implementasi protobuf yang berbeda di aplikasi Anda. Hal ini dapat terjadi dengan berbagai implementasi logging, analisis, framework pemuatan gambar, penyimpanan cache, dan berbagai hal lainnya yang tidak Anda harapkan.
Meskipun dapat membantu
menghapus API dan resource dengan tanda yang tepat, ProGuard tidak dapat menghapus
dependensi internal yang besar pada sebuah library. Fitur yang Anda inginkan dalam library ini
mungkin memerlukan dependensi tingkat lebih rendah. Hal ini akan
sangat menyulitkan jika Anda menggunakan subclass Activity
dari sebuah
library (yang cenderung memiliki banyak dependensi),
jika library menggunakan refleksi (yang umum dan berarti Anda harus menghabiskan
banyak waktu untuk mengutak-atik ProGuard secara manual agar berfungsi), dan sebagainya.
Selain itu, hindari menggunakan library bersama untuk hanya satu atau dua fitur dari lusinan fitur lainnya. Anda tentu tidak ingin menulis sejumlah besar kode dan overhead yang bahkan tidak Anda gunakan. Saat mempertimbangkan apakah akan menggunakan library, temukan implementasi yang benar-benar cocok dengan kebutuhan Anda. Jika tidak, Anda mungkin perlu membuat implementasi Anda sendiri.