Mengelola memori aplikasi

Halaman ini menjelaskan cara mengurangi penggunaan memori dalam aplikasi Anda secara proaktif. Untuk mengetahui informasi tentang cara sistem operasi Android mengelola memori, lihat Ringkasan pengelolaan memori.

Random access memory (RAM) merupakan resource berharga untuk 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.

Memantau memori yang tersedia dan penggunaan memori

Anda harus menemukan masalah penggunaan memori aplikasi sebelum dapat memperbaikinya. 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 stack trace untuk setiap alokasi, dan beralihlah ke kode yang sesuai pada editor Android Studio.

Melepaskan memori sebagai respons terhadap peristiwa

Android dapat memperoleh kembali memori dari aplikasi Anda atau menghentikan aplikasi sepenuhnya jika diperlukan untuk mengosongkan memori untuk tugas penting, seperti yang dijelaskan dalam Ringkasan pengelolaan memori. Untuk menyeimbangkan memori sistem secara maksimal dan menghindari penghentian proses aplikasi oleh sistem, Anda dapat mengimplementasikan antarmuka ComponentCallbacks2 dalam class Activity. Metode callback onTrimMemory() yang disediakan memungkinkan aplikasi Anda memproses peristiwa terkait memori saat aplikasi berada di latar depan atau latar belakang. Kemudian, hal ini memungkinkan aplikasi Anda merilis objek sebagai respons terhadap siklus proses aplikasi atau peristiwa sistem yang menunjukkan bahwa sistem perlu memperoleh kembali memori.

Anda dapat menerapkan callback onTrimMemory() untuk merespons berbagai peristiwa terkait memori, seperti ditunjukkan dalam contoh berikut:

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 is raised.
     */
    override fun onTrimMemory(level: Int) {

        // Determine which lifecycle or system event is raised.
        when (level) {

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
                /*
                   Release any memory 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
                   begins stopping 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 is one of the
                   first to be terminated.
                */
            }

            else -> {
                /*
                  Release any non-critical data structures.

                  The app receives 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 is raised.
     */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event is raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /*
                   Release any UI objects that currently hold memory.

                   The user interface moves 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 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
                   begins stopping 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 is one of the
                   first to be terminated.
                */

                break;

            default:
                /*
                  Release any non-critical data structures.

                  The app receives an unrecognized memory level value
                  from the system. Treat this as a generic low-memory message.
                */
                break;
        }
    }
}

Memeriksa jumlah memori yang Anda perlukan

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 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(). Tindakan ini menampilkan 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 di mana sistem akan mulai mengakhiri proses. Objek ActivityManager.MemoryInfo juga mengekspos lowMemory, yang merupakan boolean sederhana yang memberi tahu Anda apakah perangkat kehabisan memori.

Contoh cuplikan kode berikut menunjukkan cara menggunakan metode getMemoryInfo() di aplikasi Anda.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check 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 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 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

Sebaiknya jangan biarkan layanan berjalan jika tidak diperlukan. Membiarkan layanan yang tidak perlu tetap berjalan adalah salah satu kesalahan pengelolaan memori terburuk yang dapat dilakukan aplikasi Android. Jika aplikasi Anda memerlukan layanan untuk bekerja di latar belakang, jangan biarkan layanan tersebut terus berjalan kecuali jika diperlukan untuk menjalankan tugas. Hentikan saat layanan telah menyelesaikan tugasnya. Jika tidak, kebocoran memori dapat terjadi.

Saat Anda memulai layanan, sistem lebih suka mempertahankan agar proses untuk layanan tersebut selalu berjalan. Perilaku ini membuat proses layanan menjadi sangat tidak efisien karena RAM yang digunakan oleh layanan menjadi tidak tersedia untuk 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, hindari penggunaan layanan persisten karena jenis layanan ini terus-menerus membebani memori yang tersedia. Sebagai gantinya, kami merekomendasikan penggunaan implementasi alternatif, seperti WorkManager. Untuk mengetahui informasi selengkapnya tentang cara menggunakan WorkManager guna menjadwalkan proses latar belakang, lihat Pekerjaan persisten.

Menggunakan container data yang dioptimalkan

Beberapa class yang disediakan oleh bahasa pemrograman tidak dioptimalkan untuk digunakan pada perangkat seluler. Misalnya, implementasi HashMap generik bisa sangat menguras memori karena memerlukan objek entri tersendiri untuk setiap pemetaan.

Framework Android mencakup beberapa penampung 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 dapat meningkatkan fleksibilitas dan pemeliharaan kode. Namun, abstraksi memerlukan biaya yang jauh lebih mahal karena umumnya memerlukan lebih banyak kode yang perlu dijalankan, yang memerlukan lebih banyak waktu dan RAM untuk memetakan kode ke dalam memori. Jika abstraksi Anda tidak memberikan manfaat secara signifikan, hindari abstraksi.

Menggunakan lite protobuf untuk data serial

Buffering protokol (protobuf) adalah mekanisme yang 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 menggunakan protobuf untuk data Anda, selalu gunakan 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 lebih lanjut, baca readme protobuf.

Menghindari churn memori

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 frame karena interaksi yang diperlukan antara pembersih sampah memori dan thread aplikasi data. Makin banyak waktu yang dihabiskan sistem untuk pembersihan sampah memori, makin cepat daya baterai habis.

Sering kali, churn memori dapat menyebabkan terjadinya sejumlah besar peristiwa pembersihan sampah memori. 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.

Gunakan Memory Profiler untuk menemukan tempat dalam kode dengan churn memori yang tinggi agar Anda dapat memperbaikinya.

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 berbasis factory.

Anda juga dapat mengevaluasi apakah kumpulan objek menguntungkan kasus penggunaan. Dengan kumpulan objek, alih-alih melepaskan instance objek ke lantai, Anda akan melepaskannya ke dalam kumpulan setelah tidak diperlukan lagi. Saat berikutnya instance objek dari jenis tersebut diperlukan, Anda dapat memperolehnya dari kumpulan, bukan mengalokasikannya.

Evaluasi performa secara menyeluruh 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 rilis, lalu inisialisasinya selama akuisisi dapat menimbulkan overhead yang bukan nol.

Menahan lebih banyak instance objek di dalam kumpulan daripada yang diperlukan juga dapat membebani pembersihan sampah memori. Meskipun kumpulan objek mengurangi jumlah pemanggilan pembersihan sampah memori, kumpulan objek tersebut pada akhirnya meningkatkan jumlah pekerjaan yang diperlukan untuk setiap pemanggilan, karena ini sebanding dengan jumlah byte aktif (yang dapat dijangkau).

Menghapus resource dan library yang boros memori

Beberapa resource dan library dalam kode Anda bisa 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, atau resource 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, resource, frame animasi, dan library pihak ketiga dapat berkontribusi pada ukuran aplikasi Anda. Android Studio dan Android SDK menyediakan beberapa alat untuk membantu Anda mengurangi ukuran resource dan dependensi eksternal. Alat ini mendukung metode penyingkatan kode modern, seperti kompilasi R8.

Untuk mengetahui informasi selengkapnya tentang cara mengurangi ukuran aplikasi secara keseluruhan, lihat Mengurangi ukuran aplikasi.

Menggunakan Hilt atau 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 di aplikasi Anda, pertimbangkan untuk menggunakan Hilt atau Dagger. Hilt adalah library injeksi dependensi untuk Android yang berjalan di atas Dagger. Dagger tidak menggunakan refleksi untuk memindai kode aplikasi. Anda dapat menggunakan implementasi waktu kompilasi statis Dagger di aplikasi Android tanpa biaya runtime atau penggunaan memori yang tidak perlu.

Framework injeksi dependensi lain yang menggunakan refleksi 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. Saat menggunakan library eksternal, Anda mungkin perlu mengoptimalkannya untuk perangkat seluler. Rencanakan pekerjaan ini sebelumnya dan analisis library dalam hal ukuran kode dan jejak RAM sebelum 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, analitik, 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 pada tingkat yang lebih rendah. Hal ini akan sangat menyulitkan saat Anda menggunakan subclass Activity dari library—yang mungkin memiliki banyak dependensi—saat library menggunakan refleksi, yang bersifat umum dan memerlukan penyesuaian ProGuard secara manual agar dapat berfungsi.

Selain itu, hindari menggunakan library bersama untuk hanya satu atau dua fitur dari lusinan fitur lainnya. Jangan menarik sejumlah besar kode dan overhead yang 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.