Menetapkan anggaran memori aplikasi

Anggaran memori aplikasi memungkinkan aplikasi mendeklarasikan anggaran memori untuk dirinya sendiri, yang memberi tahu sistem untuk memangkas penggunaan memori saat aplikasi menggunakan lebih dari anggaran yang ditetapkan. Hal ini sangat berguna untuk aplikasi sistem dan paket atau untuk aplikasi yang menargetkan perangkat dengan memori terbatas, tempat developer mengetahui set kerja memori yang diharapkan (memori yang diperlukan aplikasi secara aktif untuk aktivitas yang sedang dilakukan pengguna) dan ingin memastikan aplikasinya tidak menggunakan terlalu banyak resource RAM bersama sistem.

Anggaran tetap seimbang dengan menggunakan pengeluaran dan pertukaran memori untuk menghapus halaman memori yang tidak digunakan baru-baru ini, sehingga memfokuskan jejak memori aplikasi pada set kerja saat ini. Saat aplikasi melebihi anggaran yang dideklarasikan, sistem operasi akan menargetkan klaim kembali secara khusus pada aplikasi tersebut:

  1. Halaman yang didukung file bersih (seperti kode tidak aktif dan aset yang dipetakan) akan dikeluarkan terlebih dahulu karena dapat dibaca ulang dari penyimpanan jika diperlukan.
  2. Halaman kotor yang didukung file ditulis kembali ke penyimpanan dan dihapus.
  3. Halaman memori anonim (seperti alokasi heap) dikompresi dan ditukar ke zRAM.

Selama working set tidak melebihi anggaran, aplikasi akan berfungsi dengan baik tanpa menggunakan lebih banyak memori daripada yang ada dalam anggaran yang ditetapkan. Sistem operasi mengeluarkan memori yang tidak digunakan dan mengompresi halaman heap yang tidak aktif untuk ditukar sehingga alokasi memori tetap terikat tanpa menghentikan proses.

Yang terjadi jika aplikasi melebihi anggarannya

Melebihi anggaran memori tidak akan menyebabkan sistem menghentikan atau membuat aplikasi Anda error. Sebagai gantinya, saat aplikasi menggunakan lebih banyak memori daripada yang diizinkan anggarannya, OS akan menemukan memori yang belum digunakan aplikasi selama beberapa waktu dan menyimpannya dengan aman hingga diperlukan lagi. Hal ini memberi ruang untuk alokasi memori aplikasi baru dan menjaga penggunaan memori aplikasi secara keseluruhan tetap sesuai anggaran.

Selama ada ruang antara apa yang dibutuhkan aplikasi pada waktu tertentu (set kerjanya) dan anggaran yang ditetapkan, aplikasi akan terus berjalan seperti biasa. Jika aplikasi menetapkan anggaran yang lebih kecil dari set kerjanya, performa aplikasi saat runtime dapat melambat.

Untuk mempelajari cara OS melacak dan mengklaim kembali memori, lihat panduan arsitektur memori:

Mendeklarasikan anggaran dalam manifes Android

Mendeklarasikan anggaran memori di AndroidManifest.xml adalah metode utama dan yang direkomendasikan untuk menentukan anggaran. Ini tidak memerlukan kode runtime, langsung berlaku saat proses dimulai, dan memberikan kontrak yang jelas untuk sistem operasi.

Deklarasi <memory-budget> berlaku di perangkat yang menjalankan Android 17 QPR2 (level API 37.2) dan yang lebih baru. Pada versi Android yang lebih lama, parser manifes platform akan mengabaikan elemen XML yang tidak dikenali dengan aman, sehingga Anda dapat mengadopsi <memory-budget> tanpa memengaruhi kompatibilitas mundur.

Menyatakan anggaran dasar pengukuran

Untuk sebagian besar aplikasi, Anda hanya perlu menentukan satu anggaran untuk aplikasi. Deklarasikan elemen <memory-budget> langsung di dalam tag <application>:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.simpleapp">

    <application
        android:label="@string/app_name">

        <!-- Baseline budget for the application -->
        <memory-budget android:maxMb="256" />

    </application>
</manifest>

Tindakan ini menetapkan anggaran memori residen sebesar 256 MB di semua proses dan status untuk paket. Jika jejak memori aplikasi melebihi 256 MB, sistem operasi akan memangkas halaman memori yang tidak aktif menggunakan pengeluaran dan pertukaran.

Memvariasikan anggaran menurut status proses

Aplikasi memerlukan jumlah memori yang berbeda, bergantung pada visibilitas penggunanya:

  • Latar depan: Proses menghosting Aktivitas yang terlihat dan berinteraksi dengan pengguna. Status ini biasanya memiliki jejak terbesar karena UI dan grafis yang aktif.
  • Terasa: Proses ini terasa oleh pengguna, tetapi tidak menghosting jendela yang terlihat (misalnya, menghosting layanan latar depan pemutaran media, download latar belakang yang aktif, navigasi belokan demi belokan, atau metode input aktif).
  • Latar belakang: Proses ini menjalankan tugas latar belakang, penerima, atau sinkronisasi data. Sistem ini diharapkan dapat mempertahankan jejak minimal.

Anda dapat mendeklarasikan beberapa klausa <memory-budget> untuk mencocokkan status berikut:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.simpleapp">

    <application
        android:label="@string/app_name">

        <!-- Default budget for visible foreground UI -->
        <memory-budget android:maxMb="200" />

        <!-- Tighter budget when playing audio in background -->
        <memory-budget
            android:maxMb="120"
            android:state="perceptible" />

        <!-- Minimal budget when fully in background -->
        <memory-budget
            android:maxMb="48"
            android:state="background" />

    </application>
</manifest>

Klausa dasar tanpa android:state tidak diperlukan; jika Anda hanya menentukan klausa khusus negara bagian (misalnya, android:state="background"), negara bagian lain tetap tidak dibatasi oleh anggaran aplikasi. Jika disertakan, klausa tanpa android:state akan bertindak sebagai penggantian default untuk status yang tidak ditentukan (seperti latar depan), yang akan diganti oleh klausa berikutnya yang lebih ketat saat aplikasi beralih ke status perceptible atau background.

Saat mengukur anggaran untuk setiap status, targetkan set kerja aktif dari status tersebut, bukan memori residen puncak (batas atas RSS):

  • Alokasikan anggaran foreground untuk rendering frame yang lancar: Berikan ruang yang cukup pada UI latar depan untuk set kerja aktifnya agar reklamasi sinkron tidak menunda rendering frame. Jika aplikasi Anda menyertakan alur kerja mandiri yang menggunakan banyak memori (seperti editor gambar atau video beresolusi tinggi), pertimbangkan untuk memindahkan aktivitas tersebut ke proses khusus (lihat Aplikasi multiproses) sehingga alokasinya dianggarkan secara independen dan dihentikan saat pengguna keluar.
  • Alokasikan anggaran background secara ketat untuk pekerjaan headless: RSS latar belakang yang tinggi dalam telemetri lapangan sering kali mencerminkan alokasi UI yang dipertahankan setelah meninggalkan latar depan (lihat Menempatkan layanan terikat bersama UI yang menggunakan banyak memori dan Melepaskan memori sebagai respons terhadap peristiwa) atau pembacaan file satu kali selama pengindeksan dan sinkronisasi latar belakang. Karena pekerjaan latar belakang headless tidak memiliki batas waktu frame, mengeluarkan halaman cache file satu kali dan menukar halaman heap yang tidak aktif ke zRAM saat sinkronisasi latar belakang mengalokasikan secara singkat di luar anggarannya tidak akan menyebabkan jank yang terlihat oleh pengguna sekaligus mencegah lonjakan latar belakang mengeluarkan aplikasi latar depan pengguna.

Cara status anggaran dipetakan ke status proses

Platform memetakan status anggaran manifes ke status proses runtime berdasarkan RunningAppProcessInfo.importance, yang dihitung oleh Activity Manager dari komponen aktif proses itu sendiri dan setiap pengikatan layanan atau kueri penyedia konten yang masuk dari aplikasi lain:

  • foreground: Interaksi pengguna yang aktif dan terlihat, seperti menghosting Aktivitas yang dilanjutkan, mempertahankan status aplikasi teratas saat layar nonaktif, atau menghosting layanan atau penyedia konten yang secara aktif terikat atau dikueri oleh aplikasi latar depan teratas.
  • perceptible: Workload yang dapat dilihat oleh pengguna tanpa jendela yang terlihat, seperti pemutaran media aktif, navigasi belokan demi belokan, pengambilan kamera atau mikrofon, download latar belakang aktif, layanan latar depan sinkronisasi data, atau layanan yang terikat oleh klien latar depan menggunakan flag seperti BIND_NOT_FOREGROUND (lihat Mengontrol pewarisan dengan flag BIND). Meskipun metrik platform internal mungkin melabeli beberapa workload ini sebagai PROCESS_STATE_IMPORTANT_FOREGROUND, konstanta internal tersebut tidak menandakan jendela yang terlihat dan diatur oleh anggaran perceptible.
  • background: Pekerjaan yang tidak langsung terlihat oleh pengguna, seperti tugas latar belakang, alarm, penerima siaran, atau Aktivitas sebelumnya setelah pengguna keluar.

Tabel berikut menunjukkan cara tingkat kepentingan runtime dipetakan ke status manifes:

Manifes android:state Kepentingan runtime (RunningAppProcessInfo) Komponen umum
foreground IMPORTANCE_FOREGROUND
IMPORTANCE_TOP_SLEEPING
Aktivitas terlihat yang dilanjutkan, aplikasi teratas dengan layar terkunci, layanan atau penyedia konten yang terikat oleh aplikasi teratas
perceptible IMPORTANCE_FOREGROUND_SERVICE
IMPORTANCE_VISIBLE
Pemutaran media, navigasi, download, atau layanan latar depan sinkronisasi yang aktif; layanan yang terikat dengan layanan latar depan atau tanda yang terlihat
background IMPORTANCE_PERCEPTIBLE
IMPORTANCE_CANT_SAVE_STATE
IMPORTANCE_SERVICE
Tugas latar belakang, penerima, sinkronisasi latar belakang, Activity sebelumnya, atau aplikasi Home di latar belakang

Untuk mengetahui detail tentang cara binding klien meningkatkan proses host layanan atau penyedia dan cara proses bertransisi kembali ke background atau cached setelah klien melepaskan binding atau keluar dari layar, lihat Binding layanan dan status proses.

Saat proses memasuki status di-cache (IMPORTANCE_CACHED) tanpa komponen aktif, platform akan merilis anggaran memori aktifnya dan mengelola memorinya melalui reklamasi proses di-cache standar.

Memeriksa status dan anggaran proses aplikasi Anda

Salah satu cara untuk memeriksa status dan tingkat kepentingan proses aktif aplikasi Anda selama pengembangan adalah dengan membuat kueri Activity Manager menggunakan ADB:

adb shell dumpsys activity processes <package-name>

Contoh singkat berikut menunjukkan data proses dan entri kontrol OOM untuk aplikasi yang menjalankan layanan latar depan:

ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
  All known processes:
  *APP* UID 10123 ProcessRecord{edf056c 3919:com.example.app/u0a123}
    pid=3919
    oom adj: max=1001 curRaw=200 setRaw=200 cur=200 set=200
    curProcState=4 mRepProcState=4 setProcState=4 lastStateTime=-42s360ms
    hasStartedServices=true
    mHasForegroundServices=true forcingToImportant=null
...
  Process OOM control (48 total):
    Proc #22: prcp  F/S/FGS  ---NFU-TI  t: 0 3919:com.example.app/u0a123 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS  lastRss=0.00 lastCachedRss=0.00

Dalam output ini:

  • curProcState=4 dan state: cur=FGS mencerminkan bahwa proses berada dalam status layanan latar depan. Jika aplikasi Anda menghosting aktivitas terlihat yang aktif, aktivitas ini akan muncul sebagai TOP. Jika menjalankan komponen latar depan yang penting (seperti download atau sinkronisasi), aplikasi akan muncul sebagai IMPF.
  • Dalam tabel kontrol OOM Proses, prcp menunjukkan bahwa proses dievaluasi berdasarkan tingkat prioritas yang terlihat, yang sesuai dengan anggaran perceptible.

Untuk memeriksa anggaran memori yang saat ini diterapkan dan penggunaan memori residen:

adb shell dumpsys meminfo <package-name>

Mulai Android 17 QPR2, output ini menyertakan bagian Memory Budget yang menampilkan batas maksimum aktif, sumber batas (seperti AndroidManifest atau MemoryBudgetManager), dan memori residen saat ini.

Aplikasi multiproses

Jika aplikasi Anda membagi pekerjaannya di beberapa proses, konfigurasikan anggaran proses khusus menggunakan tag <process> di dalam <processes>.

Misalnya, pertimbangkan aplikasi streaming musik (com.example.radio):

  1. Proses utama: Menghosting UI yang terlihat dan mesin pemutaran audio (MediaSessionService dengan layanan latar depan mediaPlayback). Saat terlihat, proses beroperasi di bawah anggaran latar depan 180 MB. Saat pengguna keluar dari aplikasi saat musik terus diputar, proses akan memasuki status perceptible, dengan anggaran 64 MB yang cukup untuk mesin pemutaran dan buffering audio.
  2. Proses sinkronisasi (:sync): Proses khusus yang menjalankan sinkronisasi metadata latar belakang dan pengindeksan download. Karena proses ini hanya aktif di latar belakang, Anda tidak perlu mendeklarasikan state="background" secara eksplisit; satu anggaran berlaku.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.radio">

    <application
        android:label="@string/app_name">

        <!-- Package baseline: main process with UI and audio playback -->
        <memory-budget android:maxMb="180" />

        <!-- Tighter budget when audio plays in the background -->
        <memory-budget
            android:maxMb="64"
            android:state="perceptible" />

        <processes>
            <!-- Default application process (uses the package baseline budgets above) -->
            <process />

            <!-- Dedicated background sync process -->
            <process android:process=":sync">
                <memory-budget android:maxMb="32" />
            </process>
        </processes>

        <service
            android:name=".playback.AudioPlayerService"
            android:foregroundServiceType="mediaPlayback"
            android:exported="false" />

        <service
            android:name=".sync.PlaylistSyncService"
            android:process=":sync"
            android:exported="false" />

    </application>
</manifest>

Saat Anda mendeklarasikan blok <processes> dalam manifes, sertakan elemen <memory-budget> penutup tepat di bawah <application>, dan cantumkan entri <process> untuk setiap proses yang digunakan oleh komponen dalam manifes gabungan Anda, termasuk <process /> untuk proses aplikasi default. Jika Anda hanya ingin menganggarkan subproses tertentu sambil membiarkan aplikasi lainnya berada pada batas sistem untuk perangkat, deklarasikan dasar pengukuran paket yang tinggi, seperti <memory-budget android:maxMb="1000000" /> di bagian <application>.

Penggunaan memori dalam subproses apa pun dihitung terhadap anggaran prosesnya dan anggaran paket yang mencakupnya. Proses mengalami tekanan memori saat runtime jika melanggar anggaran proses atau anggaran paket, batas mana pun yang dicapai terlebih dahulu.

Menskalakan anggaran untuk layar dengan kepadatan tinggi

Untuk aplikasi yang jejak memorinya meningkat secara signifikan seiring dengan jumlah piksel yang akan digambar di layar sekaligus—seperti aplikasi galeri foto yang meng-cache bitmap berukuran sesuai layar—Android menyediakan dua mekanisme alternatif untuk menskalakan anggaran secara dinamis dengan spesifikasi layar:

  • Skala menurut bucket kepadatan layar (android:additionalMbPerDensity): Menambahkan megabyte secara proporsional ke kuadrat rasio kepadatan layar relatif terhadap mdpi (1,0x / 160 dpi), karena drawable raster 2D dan aset UI diskalakan di seluruh lebar dan tinggi:

    <!-- Baseline 180MB + 16MB multiplied by (density ratio)^2 -->
    <memory-budget
        android:maxMb="180"
        android:additionalMbPerDensity="16" />
    

    Pada layar mdpi (1,0x), anggarannya adalah 180 + 16 × 12 = 196 MB. Pada layar xxhdpi (3,0x), anggaran diskalakan menjadi 180 + 16 × 32 = 324 MB.

  • Skala menurut resolusi layar fisik (android:additionalBytesPerDisplayPixel): Menambahkan byte secara langsung per piksel layar fisik (Lebar × Tinggi). Hal ini ideal untuk aplikasi yang mengalokasikan platform grafis layar penuh, buffer render, atau cache foto resolusi penuh yang konsumsi memorinya diskalakan langsung dengan jumlah piksel tampilan mentah, bukan bucket kepadatan UI:

    <!-- Baseline 128MB + 16 bytes per physical display pixel -->
    <!-- For example, a 4-byte RGBA full-screen buffer with double or quadruple buffering -->
    <memory-budget
        android:maxMb="128"
        android:additionalBytesPerDisplayPixel="16" />
    

    Pada layar 1080p (1080 × 2400 &approx; 2,59 juta piksel), hal ini menambahkan &approx; 41,4 MB ke anggaran dasar. Pada layar 1440p (1440 × 3120 &approx; 4,49 juta piksel), ini menambahkan &approx; 71,8 MB.

Kedua atribut ini adalah alternatif. Pilih atribut yang cocok dengan faktor penskalaan utama aplikasi Anda, dan hindari menggabungkan keduanya dalam klausa yang sama.

Mengkhususkan untuk faktor bentuk perangkat

Saat mengirimkan APK di ponsel, tablet, dan Wear OS, gunakan atribut android:feature untuk menyesuaikan anggaran untuk berbagai target hardware.

Di smartwatch Wear OS, RAM dibatasi, dan UI serta set fitur aplikasi jauh lebih sederhana. Anda dapat mendeklarasikan anggaran yang lebih ketat khusus untuk fitur watch:

<!-- General phone and tablet baseline -->
<memory-budget android:maxMb="180" />

<!-- Wear OS override: simpler UI and constrained hardware -->
<memory-budget
    android:maxMb="48"
    android:feature="watch" />

Aturan penyelesaian: klausul terakhir yang berlaku akan diterapkan

Saat menentukan beberapa elemen <memory-budget> untuk aplikasi atau proses, sistem akan mengevaluasinya sesuai urutan yang dideklarasikan dalam manifes. Klausa anggaran yang terakhir berlaku adalah klausa yang diterapkan.

Karena anggaran terakhir yang berlaku akan menang, urutan menjadi penting. Selalu tempatkan anggaran dasar yang paling umum terlebih dahulu, diikuti dengan penggantian yang lebih spesifik (seperti klausa khusus negara bagian atau khusus hardware).

Referensi atribut XML

Semua atribut ukuran memori dinyatakan dalam megabyte (MB) dan dipetakan ke biaya memory.current cgroup Linux (yang mengecualikan memori bersama seperti Zygote).

Atribut Format Default Deskripsi
android:maxMb Bilangan bulat (> 0) Wajib Batas anggaran memori residen dasar dalam MB.
android:state Enum Semua Status proses yang menerapkan anggaran ini: foreground, perceptible, atau background. Jika dihilangkan, klausa akan bertindak sebagai pengganti untuk status yang tidak ditentukan.
android:additionalMbPerDensity Bilangan bulat (≥ 0) 0 Megabyte tambahan dikalikan dengan kuadrat rasio kepadatan layar relatif terhadap mdpi (1,0x).
android:additionalBytesPerDisplayPixel Bilangan bulat (≥ 0) 0 Byte tambahan yang dialokasikan per piksel tampilan fisik (Lebar × Tinggi), berguna untuk buffer platform dan bitmap.
android:feature String Semua Membatasi klausa ke perangkat yang mendeklarasikan fitur hardware tertentu: watch, automotive, atau leanback.

Runtime API (opsi dinamis sekunder)

Mendeklarasikan anggaran secara statis di AndroidManifest.xml adalah solusi yang direkomendasikan untuk hampir semua aplikasi. Namun, untuk aplikasi dengan workload dinamis atau untuk eksperimen runtime, Android menyediakan API SDK dan NDK runtime sebagai opsi sekunder.

Runtime API memungkinkan Anda:

  • Membuat kueri penggunaan memori saat ini dan anggaran yang efektif.
  • Sesuaikan anggaran proses Anda secara dinamis ke bawah.
  • Memproses peristiwa melebihi anggaran untuk memangkas cache secara proaktif sebelum sistem operasi memicu reklamasi langsung.

Android SDK API (MemoryBudgetManager)

Layanan sistem MemoryBudgetManager tersedia untuk aplikasi yang ditulis dalam Kotlin dan Java mulai Android 17 QPR2 (rilis SDK minor, level API 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2).

Mengambil layanan

Sebelum Anda mengakses MemoryBudgetManager, pastikan perangkat tidak menjalankan versi yang lebih rendah dari Android 17 QPR2 menggunakan SDK_INT_FULL:

if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_2) {
    val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
}

Penggunaan dan anggaran kueri

// Query current memory charged to this process and the package UID
val processUsageBytes = budgetManager.processCurrentUsageBytes
val packageUsageBytes = budgetManager.packageCurrentUsageBytes

// Query effective budgets (returns LIMIT_IS_DISABLED if unconstrained)
val processBudgetBytes = budgetManager.processBudgetBytes
val packageBudgetBytes = budgetManager.packageBudgetBytes

Menetapkan atau menghapus anggaran secara dinamis

Anda dapat menetapkan anggaran yang lebih ketat saat runtime untuk membatasi memori selama tugas ringan, atau menghapusnya saat tugas selesai:

// Set a tighter dynamic budget on the current process (e.g., 96 MB)
try {
    budgetManager.processBudgetBytes = 96L * 1024L * 1024L
} catch (e: IllegalArgumentException) {
    // Thrown if the budget is <= 0 or exceeds the manifest ceiling or system limit
    Log.e(TAG, "Requested budget exceeds manifest or system ceiling", e)
}

// Clear the dynamic process budget to restore the manifest limit (or unconstrained baseline)
budgetManager.clearProcessBudget()

Tidak seperti klausa manifes <memory-budget>, yang otomatis mengalihkan anggaran setiap kali proses bertransisi antara status foreground, perceptible, dan background, anggaran dinamis yang ditetapkan melalui MemoryBudgetManager menerapkan satu batas hingga kode Anda memperbarui atau menghapusnya. Jika Anda menggunakan runtime API untuk menjalankan eksperimen lapangan atau mengelola batas khusus status, perbarui atau hapus anggaran dinamis di seluruh transisi siklus proses (misalnya, menggunakan ProcessLifecycleOwner atau callback siklus proses aktivitas, seperti yang ditunjukkan dalam Contoh editor gambar adaptif).

Performa dan idempotensi

Saat menggunakan MemoryBudgetManager saat runtime, perhatikan karakteristik berikut:

  • Idempotensi: Semua API mutasi anggaran bersifat idempoten. Memanggil clearProcessBudget() atau clearPackageBudget() berulang kali atau saat tidak ada anggaran dinamis yang aktif adalah no-op yang aman. Demikian pula, menetapkan setProcessBudgetBytes() atau setPackageBudgetBytes() ke nilai yang sama secara berturut-turut tidak akan memicu konfigurasi ulang sistem yang redundan.
  • Frekuensi panggilan: Menetapkan, memperbarui, atau menghapus anggaran akan melakukan panggilan lintas proses ke layanan sistem untuk memperbarui pengontrol memori sistem operasi untuk proses tersebut. Meskipun ringan, model ini tidak sepenuhnya gratis. Panggil API ini selama transisi siklus proses diskret dan pencapaian tugas (seperti memasuki atau meninggalkan aktivitas, memulai atau menyelesaikan pemrosesan latar belakang, atau menangani permintaan ucapan), dan hindari memanggilnya dalam loop ketat, thread pemrosesan audio, atau rutinitas render per frame.
  • Frekuensi kueri: Properti kueri processCurrentUsageBytes dan packageCurrentUsageBytes mengambil penggunaan memori langsung dari layanan sistem. Meskipun cepat, kueri harus dipanggil sesuai permintaan (seperti selama transisi tugas atau di dalam callback OnOverBudgetListener), bukan di-polling dalam loop berkelanjutan.

Memproses callback tekanan melebihi anggaran

Aplikasi dapat mendaftarkan pemroses untuk diberi tahu saat penggunaan memori melebihi batas anggaran. Hal ini memungkinkan aplikasi melakukan pembersihan tingkat aplikasi yang proaktif (seperti menghapus cache bitmap dalam memori) sebelum sistem operasi memicu latensi reklamasi langsung:

val listener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
    Log.w(TAG, "Process exceeded memory budget of $budgetBytes bytes")
    // Proactively evict caches to release memory
    imageTileCache.evictAll()
}

// Register on the main Looper
budgetManager.registerProcessOverBudgetListener(mainLooper, listener)

// When done (e.g., in onStop)
budgetManager.unregisterProcessOverBudgetListener(listener)

Praktik terbaik untuk callback anggaran berlebih:

  • Cepat: Operasi klaim kembali harus memberikan bantuan segera. Komputasi yang kompleks selama tekanan akan memperburuk performa.
  • Hindari alokasi: Jangan mengalokasikan objek baru atau memulai thread baru di dalam callback, karena hal ini dapat memicu klaim ulang langsung sistem operasi secara langsung.
  • Fokus pada target hasil tinggi: Mengeluarkan Bitmap besar, merender buffer, atau menutup file yang dipetakan memori jauh lebih efektif daripada merilis banyak objek kecil.

Native NDK API (<android/memory_budget_manager.h>)

Aplikasi native dapat menggunakan C NDK API yang diekspos oleh libandroid.so mulai di Android 17 QPR2 (API level 37.2).

Konfigurasi CMake

find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})

Menyertakan penggunaan header dan kueri

#include <android/memory_budget_manager.h>

// Query current memory usage
int64_t process_usage = AMemoryBudgetManager_getProcessCurrentUsageBytes();
int64_t package_usage = AMemoryBudgetManager_getPackageCurrentUsageBytes();

// Query current budget
int64_t process_budget = 0;
AMemoryBudgetResult result = AMemoryBudgetManager_getProcessBudget(&process_budget);
if (result == AMEMORY_BUDGET_RESULT_SUCCESS) {
    // Current budget available in process_budget
} else if (result == AMEMORY_BUDGET_RESULT_LIMIT_IS_DISABLED) {
    // No budget is currently active
}

Mengonfigurasi anggaran native secara dinamis

// Set a tighter process budget (e.g. 160MB)
AMemoryBudgetResult result = AMemoryBudgetManager_setProcessBudget(160LL * 1024 * 1024);
if (result != AMEMORY_BUDGET_RESULT_SUCCESS) {
    const char* error_msg = AMemoryBudgetManager_resultToString(result);
    // Handle error (e.g. AMEMORY_BUDGET_RESULT_ERROR_EXCEEDS_MANIFEST_LIMIT)
}

// Clear the dynamic budget to resume manifest limits (or unconstrained baseline)
AMemoryBudgetManager_clearProcessBudget();

Memantau peristiwa tekanan memori

NDK menyediakan dua cara untuk memantau peristiwa memori:

  1. High-Level Watcher (AMemoryBudgetManager_Watcher_create): Memantau peristiwa di ALooper dengan debounce otomatis.
  2. Deskriptor File Tingkat Rendah: AMemoryBudgetManager_getProcessMemoryPressureFd menampilkan deskriptor file native yang dapat diintegrasikan langsung ke dalam loop mesin epoll kustom.
void onMemoryPressure(int32_t event_mask, const AMemoryBudgetEvents* events, void* userdata) {
    // High-yield eviction of unused native textures or geometry caches
    purgeNativeTextureCaches();
}

// Register watcher on an ALooper with a 1000ms debounce interval
AMemoryBudgetManagerWatcher* watcher = AMemoryBudgetManager_Watcher_create(
    looper,
    AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
    1000 /* debounce_ms */,
    &onMemoryPressure,
    NULL /* userdata */
);

// When done:
AMemoryBudgetManager_Watcher_destroy(watcher);

Contoh Runtime API

Contoh berikut menunjukkan cara mengimplementasikan API runtime menggunakan Android SDK API (ditulis dalam Kotlin) dan Native NDK API (ditulis dalam C++).

Contoh Android SDK: Editor gambar adaptif

Contoh ini menunjukkan aplikasi pengeditan gambar (com.example.imageeditor) yang manifesnya mendeklarasikan batas 256 MB untuk mengakomodasi kanvas multi-lapisan:

<manifest ... >
    <application ... >
        <!-- Manifest ceiling accommodates the heaviest editing workload -->
        <memory-budget android:maxMb="256" />
    </application>
</manifest>

Saat pengguna menjelajahi galeri thumbnail ringan, aplikasi menggunakan Android SDK API di Kotlin untuk memperketat anggaran prosesnya secara dinamis menjadi 96 MB. Saat pengguna membuka kanvas pengeditan multi-lapisan, aplikasi akan menghapus anggaran dinamis untuk memulihkan batas manifes 256 MB penuh. Class ini juga mendaftarkan OnOverBudgetListener untuk mengeluarkan bitmap pratinjau yang di-cache saat berada dalam tekanan.

package com.example.imageeditor.ui

import android.app.Activity
import android.app.MemoryBudgetManager
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.util.LruCache

class ImageEditorActivity : Activity() {

    private lateinit var budgetManager: MemoryBudgetManager

    // In-memory cache for rendered preview tiles (32MB limit)
    private val previewCache = object : LruCache<String, Bitmap>(32 * 1024 * 1024) {
        override fun sizeOf(key: String, value: Bitmap): Int = value.byteCount
    }

    private val overBudgetListener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
        Log.w(TAG, "Process memory pressure detected (budget: ${budgetBytes / 1048576}MB). Evicting preview cache.")
        previewCache.evictAll()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        budgetManager = getSystemService(MemoryBudgetManager::class.java)
    }

    override fun onStart() {
        super.onStart()
        // Register listener for process-level memory breaches
        budgetManager.registerProcessOverBudgetListener(mainLooper, overBudgetListener)
        // Constrain memory during lightweight gallery browsing
        applyGalleryBudget()
    }

    override fun onStop() {
        super.onStop()
        budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
    }

    /**
     * Called when the user enters the high-resolution editing canvas.
     * Clears the dynamic budget, restoring the full 256MB manifest ceiling.
     */
    fun enterEditingCanvas() {
        // Clear the tighter dynamic budget to restore the full manifest ceiling (256MB)
        budgetManager.clearProcessBudget()
        Log.i(TAG, "Restored manifest budget ceiling (256MB) for editing canvas")
    }

    /**
     * Called when the user exits the editor back to the thumbnail gallery.
     * Re-applies the tighter dynamic budget.
     */
    fun exitToGallery() {
        previewCache.trimToSize(8 * 1024 * 1024)
        applyGalleryBudget()
    }

    private fun applyGalleryBudget() {
        try {
            // Dynamically tighten budget to 96MB for the lightweight gallery view
            budgetManager.processBudgetBytes = 96L * 1024L * 1024L
            Log.i(TAG, "Tighter dynamic budget applied for gallery: 96MB")
        } catch (e: IllegalArgumentException) {
            Log.e(TAG, "Could not apply dynamic budget", e)
        }
    }

    companion object {
        private const val TAG = "ImageEditor"
    }
}

Contoh NDK C++: Mesin 3D native

Contoh ini menunjukkan game engine C++ native yang mengelola anggaran memori berdasarkan tingkat kualitas grafis aktif, dengan asumsi manifes aplikasi mendeklarasikan batas 512 MB untuk mengakomodasi grafis kualitas Tinggi (android:maxMb="512"). Engine ini secara dinamis memperketat anggaran untuk preset kualitas yang lebih rendah dan menggunakan AMemoryBudgetManager_Watcher_create pada ALooper untuk menghapus mipmap tekstur saat melebihi anggaran.

#include <android/memory_budget_manager.h>
#include <android/looper.h>
#include <android/log.h>

#define LOG_TAG "Native3DEngineMemory"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

class MemoryGovernor {
public:
    MemoryGovernor() : mWatcher(nullptr) {}

    ~MemoryGovernor() {
        stopMonitoring();
    }

    // Configures process budget based on user graphics quality settings
    bool setQualityBudget(int qualityLevel) {
        int64_t targetBytes = 0;
        switch (qualityLevel) {
            case 0: // Low (budget: 128MB)
                targetBytes = 128LL * 1024 * 1024;
                break;
            case 1: // Medium (budget: 256MB)
                targetBytes = 256LL * 1024 * 1024;
                break;
            case 2: // High (budget: 512MB)
                targetBytes = 512LL * 1024 * 1024;
                break;
            default:
                // Clear dynamic override and restore manifest limit
                AMemoryBudgetManager_clearProcessBudget();
                return true;
        }

        AMemoryBudgetResult result = AMemoryBudgetManager_setProcessBudget(targetBytes);
        if (result != AMEMORY_BUDGET_RESULT_SUCCESS) {
            LOGW("Could not set quality budget: %s", AMemoryBudgetManager_resultToString(result));
            return false;
        }
        return true;
    }

    bool startMonitoring(ALooper* looper) {
        if (!looper) return false;

        // Monitor process budget events, debounced to at most once every 1000ms
        mWatcher = AMemoryBudgetManager_Watcher_create(
            looper,
            AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
            1000,
            &MemoryGovernor::onPressureEvent,
            this
        );
        return mWatcher != nullptr;
    }

    void stopMonitoring() {
        if (mWatcher) {
            AMemoryBudgetManager_Watcher_destroy(mWatcher);
            mWatcher = nullptr;
        }
    }

    void unloadUnusedTextures() {
        LOGW("Memory pressure callback triggered. Purging cached texture mipmaps...");
        // Fast, high-yield eviction without allocating memory
    }

private:
    static void onPressureEvent(
        int32_t event_mask,
        const AMemoryBudgetEvents* events,
        void* userdata
    ) {
        auto* governor = static_cast<MemoryGovernor*>(userdata);
        governor->unloadUnusedTextures();
    }

    AMemoryBudgetManagerWatcher* mWatcher;
};