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:
- 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.
- Halaman kotor yang didukung file ditulis kembali ke penyimpanan dan dihapus.
- 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:
- Pengambilan kembali dan pertukaran memori: Menjelaskan cara OS mengenakan biaya heap anonim pribadi dan halaman yang didukung file ke aplikasi Anda dan mengambil kembali halaman dingin saat melebihi anggaran.
- Konsep dasar: Menjelaskan alasan memori Zygote bersama dikecualikan dari anggaran aplikasi Anda dan bagaimana anggaran residen terkait dengan RSS anonim + swap dalam telemetri lapangan.
- Bitmap dan memori: Menjelaskan cara bitmap hardware yang didukung GPU dan alokasi DMA-BUF diperhitungkan di luar heap dan anggaran memori aplikasi Anda.
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
foregrounduntuk 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
backgroundsecara 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 sepertiBIND_NOT_FOREGROUND(lihat Mengontrol pewarisan dengan flag BIND). Meskipun metrik platform internal mungkin melabeli beberapa workload ini sebagaiPROCESS_STATE_IMPORTANT_FOREGROUND, konstanta internal tersebut tidak menandakan jendela yang terlihat dan diatur oleh anggaranperceptible.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_FOREGROUNDIMPORTANCE_TOP_SLEEPING |
Aktivitas terlihat yang dilanjutkan, aplikasi teratas dengan layar terkunci, layanan atau penyedia konten yang terikat oleh aplikasi teratas |
perceptible |
IMPORTANCE_FOREGROUND_SERVICEIMPORTANCE_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_PERCEPTIBLEIMPORTANCE_CANT_SAVE_STATEIMPORTANCE_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=4danstate: cur=FGSmencerminkan bahwa proses berada dalam status layanan latar depan. Jika aplikasi Anda menghosting aktivitas terlihat yang aktif, aktivitas ini akan muncul sebagaiTOP. Jika menjalankan komponen latar depan yang penting (seperti download atau sinkronisasi), aplikasi akan muncul sebagaiIMPF.- Dalam tabel kontrol OOM Proses,
prcpmenunjukkan bahwa proses dievaluasi berdasarkan tingkat prioritas yang terlihat, yang sesuai dengan anggaranperceptible.
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):
- Proses utama: Menghosting UI yang terlihat dan mesin pemutaran audio
(
MediaSessionServicedengan layanan latar depanmediaPlayback). Saat terlihat, proses beroperasi di bawah anggaran latar depan 180 MB. Saat pengguna keluar dari aplikasi saat musik terus diputar, proses akan memasuki statusperceptible, dengan anggaran 64 MB yang cukup untuk mesin pemutaran dan buffering audio. - 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 mendeklarasikanstate="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>
<!-- 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, cantumkan entri <process>
hanya untuk proses yang mendeklarasikan <memory-budget>. Jika Anda hanya ingin
menganggarkan subproses tertentu sambil membiarkan aplikasi lainnya pada
batas sistem untuk perangkat, Anda dapat menghilangkan elemen <memory-budget>
langsung di bawah <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 terhadapmdpi(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 layarxxhdpi(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 ≈ 2,59 juta piksel), hal ini menambahkan ≈ 41,4 MB ke anggaran dasar. Pada layar 1440p (1440 × 3120 ≈ 4,49 juta piksel), ini menambahkan ≈ 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 mengakses MemoryBudgetManager, pastikan perangkat menjalankan
Android 17 QPR2 atau yang lebih baru dengan memeriksa SDK_INT_FULL. Jika
minSdk aplikasi Anda lebih rendah dari 36, periksa SDK_INT terlebih dahulu agar
perangkat yang menjalankan Android 15 (level API 35) atau yang lebih rendah tidak menampilkan
NoSuchFieldError saat membaca SDK_INT_FULL:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA &&
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()atauclearPackageBudget()berulang kali atau saat tidak ada anggaran dinamis yang aktif adalah no-op yang aman. Demikian pula, menetapkansetProcessBudgetBytes()atausetPackageBudgetBytes()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
processCurrentUsageBytesdanpackageCurrentUsageBytesmengambil penggunaan memori langsung dari layanan sistem. Meskipun cepat, kueri harus dipanggil sesuai permintaan (seperti selama transisi tugas atau di dalam callbackOnOverBudgetListener), 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 proaktif di tingkat aplikasi (seperti menghapus cache bitmap dalam memori) saat sistem operasi mengklaim kembali memori untuk menjaga proses atau paket tetap sesuai dengan anggarannya:
val cleanupThread = HandlerThread("MemoryBudgetCleanup").apply { start() }
val listener = MemoryBudgetManager.OnOverBudgetListener { budgetBytes ->
Log.w(TAG, "Process exceeded memory budget of $budgetBytes bytes")
// Proactively evict thread-safe caches on the background Looper
imageTileCache.evictAll()
}
// Register on a background Looper before tightening a dynamic budget
budgetManager.registerProcessOverBudgetListener(cleanupThread.looper, listener)
// When done (for example, in onStop)
budgetManager.unregisterProcessOverBudgetListener(listener)
Praktik terbaik untuk callback anggaran berlebih:
- Gunakan
Looperlatar belakang: TeruskanHandlerThreadLooperlatar belakang keregisterProcessOverBudgetListeneratauregisterPackageOverBudgetListenerjika memungkinkan. Menjalankan penghapusan cache atau pembersihan file di thread latar belakang akan menghindari pertentangan dengan rendering frame di UI thread saat proses berada di bawah tekanan memori. CadangkanLooper.getMainLooperuntuk pemroses yang hanya menyentuh status UI yang terikat thread utama. - Perkirakan callback berulang: Callback melebihi anggaran bukanlah notifikasi satu kali. Jika penggunaan melebihi anggaran, sistem operasi akan mengklaim kembali
halaman cold terlebih dahulu, dan jika tekanan berlanjut, sistem akan terus mengklaim kembali
memori dan memanggil pemroses Anda maksimal sekali setiap detik. Buat callback Anda idempoten. Jika aplikasi Anda hanya memerlukan satu notifikasi atau
menginginkan cooldown yang lebih lama di antara proses pembersihan, panggil
unregisterProcessOverBudgetListener(atauunregisterPackageOverBudgetListener) di dalam callback, atau lindungi pembersihan Anda dengan stempel waktu cooldown atauAtomicBoolean. - Daftarkan sebelum memperketat anggaran: Jika penggunaan memori saat ini sudah
melebihi anggaran baru saat Anda memanggil
setProcessBudgetBytesatausetPackageBudgetBytes, peristiwa kelebihan anggaran akan dipicu segera setelah anggaran baru diterapkan. Daftarkan pemroses sebelum menetapkan anggaran agar aplikasi Anda tidak melewatkan notifikasi awal melebihi anggaran tersebut. - 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:
- High-Level Watcher (
AMemoryBudgetManager_Watcher_create): Memantau peristiwa diALooperdengan debounce otomatis. - Deskriptor File Tingkat Rendah:
AMemoryBudgetManager_getProcessMemoryPressureFdmenampilkan deskriptor file native yang dapat diintegrasikan langsung ke dalam loop mesinepollkustom.
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 a background worker ALooper with a 1000ms debounce interval
AMemoryBudgetManagerWatcher* watcher = AMemoryBudgetManager_Watcher_create(
background_looper,
AMEMORY_BUDGET_MANAGER_EVENT_PROCESS,
1000 /* debounce_ms */,
&onMemoryPressure,
NULL /* userdata */
);
// When done:
AMemoryBudgetManager_Watcher_destroy(watcher);
Ikat watcher ke ALooper thread pekerja latar belakang, bukan UI utama atau thread render, sehingga penghapusan cache tidak bersaing dengan rendering frame. Buat watcher sebelum memperketat anggaran dengan
AMemoryBudgetManager_setProcessBudget atau
AMemoryBudgetManager_setPackageBudget agar aplikasi Anda tidak melewatkan peristiwa
kelebihan anggaran awal.
Meskipun proses atau paket tetap melebihi anggaran dan sistem operasi
terus mengklaim kembali memori, watcher akan memanggil callback berulang kali (paling
banyak sekali per interval debounce_ms). Buat callback Anda idempoten, atau panggil
AMemoryBudgetManager_Watcher_destroy (yang aman untuk dipanggil dari dalam
callback) jika aplikasi Anda hanya memerlukan notifikasi satu kali.
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 di HandlerThread
Looper latar belakang sebelum menerapkan anggaran galeri untuk mengeluarkan bitmap pratinjau yang di-cache
di bawah tekanan tanpa memblokir UI thread.
package com.example.imageeditor.ui
import android.app.Activity
import android.app.MemoryBudgetManager
import android.graphics.Bitmap
import android.os.Bundle
import android.os.HandlerThread
import android.util.Log
import android.util.LruCache
class ImageEditorActivity : Activity() {
private lateinit var budgetManager: MemoryBudgetManager
private lateinit var cleanupThread: HandlerThread
// Thread-safe 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)
cleanupThread = HandlerThread("MemoryBudgetCleanup").apply { start() }
}
override fun onStart() {
super.onStart()
// Register listener on a background Looper before tightening the budget
budgetManager.registerProcessOverBudgetListener(cleanupThread.looper, overBudgetListener)
// Constrain memory during lightweight gallery browsing
applyGalleryBudget()
}
override fun onStop() {
super.onStop()
budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
}
override fun onDestroy() {
cleanupThread.quitSafely()
super.onDestroy()
}
/**
* 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 berkualitas Tinggi (android:maxMb="512"). Engine
memulai AMemoryBudgetManager_Watcher_create pada ALooper thread pekerja
latar belakang sebelum memperketat anggaran untuk preset kualitas yang lebih rendah, dan
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;
}
// Call on a background worker ALooper before tightening the budget with setQualityBudget
bool startMonitoring(ALooper* backgroundLooper) {
if (!backgroundLooper) return false;
// Monitor process budget events, debounced to at most once every 1000ms
mWatcher = AMemoryBudgetManager_Watcher_create(
backgroundLooper,
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, idempotent, 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;
};