Uygulama bellek bütçeleri, uygulamaların kendileri için bir bellek bütçesi bildirmesine olanak tanır. Bu, uygulama belirlenen bütçesinden daha fazla bellek kullandığında sisteme bellek kullanımını azaltmasını söyler. Bu özellik özellikle sistem ve paketlenmiş uygulamalar veya geliştiricinin beklenen bellek çalışma kümesini bildiği ve uygulamasının sistemin paylaşılan RAM kaynaklarının çok fazla kullanmamasını sağlamak istediği, bellek açısından kısıtlı cihazları hedefleyen uygulamalar için yararlıdır.
Bütçe, bellek çıkarma ve takas kullanılarak dengede tutulur. Bu sayede, yakın zamanda kullanılmayan bellek sayfaları kaldırılır ve uygulamanın bellek ayak izi, mevcut çalışma kümesine odaklanır. Bir uygulama, beyan edilen bütçesini aştığında işletim sistemi hedefleri özellikle bu uygulamayı geri kazanır:
- Dosya destekli temiz sayfalar (ör. etkin olmayan kod ve eşlenmiş öğeler) gerekirse depolamadan yeniden okunabildikleri için önce çıkarılır.
- Değiştirilmiş dosya destekli sayfalar depolamaya geri yazılır ve çıkarılır.
- Anonim bellek sayfaları (ör. yığın ayırmaları) sıkıştırılır ve zRAM'e takas edilir.
Bütçe, çalışma kümesini aşmadığı sürece uygulama, belirlenen bütçesindeki bellekten daha fazlasını kullanmadan iyi çalışır. İşletim sistemi, kullanılmayan belleği boşaltır ve etkin olmayan yığın sayfalarını takas için sıkıştırır. Böylece bellek ayırmaları, işlemi sonlandırmadan sınırlı kalır.
Android manifest dosyasında bütçeleri tanımlama
AndroidManifest.xml içinde bellek bütçelerinizi beyan etmek, bütçeleri tanımlamak için kullanılan birincil ve önerilen yöntemdir. Çalışma zamanı kodu gerektirmez, işlem başlatıldıktan hemen sonra geçerli olur ve işletim sistemi için net bir sözleşme sağlar.
Referans bütçe beyan etme
Çoğu uygulama için, uygulama için tek bir bütçe tanımlamanız yeterlidir. <memory-budget> öğesini doğrudan <application> etiketinin içinde bildirin:
<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>
Bu, paket için tüm işlemler ve durumlar genelinde 256 MB kalıcı bellek bütçesi belirler. Uygulamanın bellekte kapladığı yer 256 MB'ı aştığında işletim sistemi, etkin olmayan bellek sayfalarını çıkarma ve takas kullanarak kırpar.
Bütçeleri işleme durumuna göre değiştirme
Bir uygulamanın ihtiyaç duyduğu bellek miktarı, kullanıcı görünürlüğüne bağlı olarak değişir:
- Ön plan: İşlem, kullanıcıyla etkileşimde bulunan görünür bir Etkinliğe ev sahipliği yapıyor. Bu durum, etkin kullanıcı arayüzü ve grafikler nedeniyle genellikle en büyük ayak izine sahiptir.
- Algılanabilir: İşlem, kullanıcı tarafından algılanabilir ancak görünür bir pencere barındırmaz (ör. medya oynatma ön plan hizmeti, adım adım navigasyon veya etkin bir giriş yöntemi barındırma).
- Arka plan: İşlem, arka plan işlerini, alıcılarını veya veri senkronizasyonlarını çalıştırıyor. Minimum yer kaplaması beklenir.
Aşağıdaki durumlarla eşleşmek için birden fazla <memory-budget> madde bildirebilirsiniz:
<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>
İlk maddede android:state="foreground" belirtmeniz gerekmez. android:state içermeyen bir madde, tüm eyaletler için varsayılan yedek olarak işlev görür. Uygulama perceptible veya background durumlarına geçtiğinde, daha kısıtlayıcı olan aşağıdaki maddeler bütçeyi geçersiz kılar.
Çoklu işlem uygulamaları
Uygulamanız işini birden fazla süreç arasında bölüyorsa <processes> içinde <process> etiketini kullanarak özel süreç bütçeleri yapılandırın.
Örneğin, bir müzik yayını uygulamasını (com.example.radio) ele alalım:
- Ana işlem: Görünür kullanıcı arayüzünü ve ses oynatma motorunu barındırır
(
MediaSessionServiceilemediaPlaybackön plan hizmeti). Görünür olduğunda işlem, 180 MB ön plan bütçesiyle çalışır. Kullanıcı, müzik çalmaya devam ederken uygulamadan ayrıldığında işlemperceptibledurumuna girer. Bu durumda, oynatma motoru ve ses arabelleği için 64 MB'lık bir bütçe yeterlidir. - Senkronizasyon süreci (
:sync): Arka planda meta veri senkronizasyonu ve indirme dizine ekleme işlemlerini yürüten özel süreç. Bu işlem yalnızca arka planda etkin olduğundanstate="background"değerini açıkça belirtmeniz gerekmez. Tek bir bütçe geçerlidir.
<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" />
<!-- Dedicated background sync process -->
<processes>
<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>
Herhangi bir alt süreçteki bellek kullanımı hem kendi süreç bütçesine hem de kapsayan paket bütçesine göre hesaplanır. Bir işlem, hangisi önce gerçekleşirse işlem bütçesini veya paket bütçesini aştığında çalışma zamanında bellek baskısı yaşar.
Yüksek yoğunluklu ekranlar için bütçeleri ölçeklendirme
Bellek ayak izi, ekranda aynı anda çizilecek piksel sayısına göre önemli ölçüde ölçeklenen uygulamalar (ör. ekrana göre boyutlandırılmış bit eşlemleri önbelleğe alan bir fotoğraf galerisi uygulaması) için Android, bütçeleri ekran özelliklerine göre dinamik olarak ölçeklemek üzere iki alternatif mekanizma sağlar:
Görüntü yoğunluğu grubuna göre ölçeklendirme (
android:additionalMbPerDensity): Megabaytları, ekranınmdpi(1,0x / 160 dpi) değerine göre yoğunluk oranıyla orantılı olarak ekler. Bu, bellek kullanımı, daha yüksek çözünürlüklü raster çizilebilir öğeleri veya kullanıcı arayüzü öğelerini önbelleğe alma gibi kullanıcı arayüzü yoğunluğu gruplarıyla ölçeklendiğinde uygundur:<!-- Baseline 180MB + 16MB per 1.0x density ratio --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />mdpiekranında (1,0x) bütçe 180 + 16 × 1 = 196 MB olur.xxhdpiekranında (3,0x), bütçe 180 + 16 × 3 = 228 MB olarak ölçeklendirilir.Fiziksel ekran çözünürlüğüne göre ölçeklendirme (
android:additionalBytesPerDisplayPixel): Baytları doğrudan fiziksel ekran pikseli başına (Genişlik × Yükseklik) ekler. Bu özellik, bellek tüketiminin kullanıcı arayüzü yoğunluğu grupları yerine doğrudan ham ekran piksel sayısı ile ölçeklendiği aşağıdaki durumlarda idealdir: Tam ekran grafik yüzeyleri, oluşturma arabellekleri veya tam çözünürlüklü fotoğraf önbellekleri ayıran uygulamalar:<!-- 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" />1080p ekranda (1080 × 2400 ≈ 2,59 milyon piksel) bu, temel bütçeye yaklaşık 41,4 MB ekler. 1440p ekranda (1440 × 3120 ve yaklaşık 4,49 milyon piksel) yaklaşık 71,8 MB ekler.
Bu iki özellik alternatiflidir. Uygulamanızın birincil ölçeklendirme faktörüyle eşleşen özelliği seçin ve her ikisini de aynı maddede birleştirmeyin.
Cihaz form faktörlerine göre uzmanlaşma
Telefonlarda, tabletlerde ve Wear OS'te APK gönderirken farklı donanım hedefleri için bütçeleri ayarlamak üzere android:feature özelliğini kullanın.
Wear OS kol saatlerinde RAM sınırlıdır ve uygulamanın kullanıcı arayüzü ile özellik seti çok daha basittir. watch özelliği için daha sıkı bir bütçe belirleyebilirsiniz:
<!-- 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" />
Çözüm kuralı: Geçerli olan son madde yürürlüğe girer
Bir uygulama veya süreç için birden fazla <memory-budget> öğesi tanımlarken sistem bunları manifest dosyasında bildirildikleri sırayla değerlendirir. Geçerli olan son bütçe maddesi uygulanır.
Son geçerli bütçe kazandığı için sıralama önemlidir. En genel temel bütçeyi her zaman önce, ardından daha spesifik geçersiz kılma işlemleri (ör. eyalete özgü veya donanıma özgü maddeler) gelecek şekilde yerleştirin.
XML özelliği referansı
Tüm bellek boyutu özellikleri megabayt (MB) cinsinden ifade edilir ve Linux cgroup memory.current ücretiyle (Zygote gibi paylaşılan bellek hariç) eşlenir.
| Özellik | Biçim | Varsayılan | Açıklama |
|---|---|---|---|
android:maxMb |
Tamsayı (> 0) | Zorunlu | MB cinsinden temel yerleşik bellek bütçe sınırı. |
android:state |
Enum | Tümü | Bu bütçenin geçerli olduğu süreç durumu: foreground, perceptible veya background. |
android:additionalMbPerDensity |
Tam sayı (≥ 0) | 0 |
mdpi (1,0x) değerine kıyasla görüntüleme yoğunluğu oranı birimi başına eklenecek ek megabayt. |
android:additionalBytesPerDisplayPixel |
Tam sayı (≥ 0) | 0 |
Yüzey arabellekleri ve bit eşlemler için yararlı olan, fiziksel ekran pikseli başına (Genişlik × Yükseklik) ayrılan ek baytlar. |
android:feature |
Dize | Tümü | Koşulu, belirli donanım özelliklerini (watch, automotive veya leanback) bildiren cihazlarla kısıtlar. |
Çalışma zamanı API'leri (ikincil dinamik seçenek)
AndroidManifest.xml içinde bütçeleri statik olarak bildirmek, neredeyse tüm uygulamalar için tercih edilen çözümdür. Ancak, dinamik iş yüklerine sahip uygulamalar veya çalışma zamanı denemeleri için Android, ikincil seçenek olarak çalışma zamanı SDK'sı ve NDK API'leri sağlar.
Çalışma zamanı API'si şunları yapmanıza olanak tanır:
- Mevcut bellek kullanımını ve etkin bütçeleri sorgulayın.
- İşlem bütçenizi dinamik olarak aşağı yönlü ayarlayın.
- İşletim sistemi doğrudan geri alma işlemini tetiklemeden önce önbellekleri proaktif olarak temizlemek için bütçeyi aşan etkinlikleri dinleyin.
Kotlin API'si (MemoryBudgetManager)
MemoryBudgetManager sisteme ait hizmet, Android 17 QPR2'den (Android 26Q4 SDK sürümü, API düzeyi 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) itibaren kullanılabilir.
Hizmeti alma
val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
Sorgu kullanımı ve bütçeler
// 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
Bütçeleri dinamik olarak ayarlama veya temizleme
Hafif görevler sırasında belleği kısıtlamak için çalışma zamanında daha sıkı bir bütçe belirleyebilir veya görev tamamlandığında bütçeyi temizleyebilirsiniz:
// 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-declared ceiling
Log.e(TAG, "Requested budget exceeds manifest or system ceiling", e)
}
// Clear the dynamic process budget to restore the manifest limit
budgetManager.clearProcessBudget()
Bütçe aşımı baskısı geri çağırmalarını dinleme
Uygulamalar, bellek kullanımı bütçe eşiğini aştığında bildirim almak için bir dinleyici kaydedebilir. Bu, işletim sistemi doğrudan geri kazanma gecikmesini tetiklemeden önce uygulamanın proaktif uygulama düzeyinde temizlik (ör. bellek içi bit eşlem önbelleklerini temizleme) yapmasına olanak tanır:
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)
Bütçeyi aşan geri aramalarla ilgili en iyi uygulamalar:
- Hızlı olun: Geri kazanma operasyonları anında rahatlama sağlamalıdır. Bilişim yükü sırasında karmaşık hesaplamalar performansı düşürür.
- Ayırmalardan kaçının: Geri çağırma içinde yeni nesneler ayırmayın veya yeni iş parçacıkları başlatmayın. Aksi takdirde, işletim sistemi doğrudan geri isteme işlemi hemen tetiklenebilir.
- Yüksek verimli hedeflere odaklanın: Büyük bit eşlemleri, oluşturma arabelleklerini kaldırmak veya belleğe eşlenmiş dosyaları kapatmak, çok sayıda küçük nesneyi serbest bırakmaktan çok daha etkilidir.
Native NDK API'si (<android/memory_budget_manager.h>)
Yerel uygulamalar, libandroid.so tarafından kullanıma sunulan C NDK API'sini kullanabilir.
CMake yapılandırması
find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})
Başlık ve sorgu kullanımını dahil etme
#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
}
Yerel bütçeyi dinamik olarak yapılandırma
// 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
AMemoryBudgetManager_clearProcessBudget();
Bellek baskısı etkinliklerini izleme
NDK, bellek etkinliklerini izlemek için iki yöntem sunar:
- Üst Düzey İzleyici (
AMemoryBudgetManager_Watcher_create): Otomatik ardışık tekrar önleme ileALooperüzerindeki etkinlikleri izler. - Düşük Düzeyli Dosya Tanımlayıcısı:
AMemoryBudgetManager_getProcessMemoryPressureFd, doğrudan özel birepollmotor döngüsüne entegre edilebilen yerel bir dosya tanımlayıcısı döndürür.
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);
Çalışma zamanı API'si örnekleri
Aşağıdaki örneklerde, çalışma zamanı API'lerinin Kotlin ve C++'ta nasıl uygulanacağı gösterilmektedir.
Kotlin örneği: Uyarlanabilir resim düzenleyici
Bu örnekte, kullanıcının çok katmanlı bir düzenleme tuvali açtığında bellek bütçesini dinamik olarak artıran (com.example.imageeditor) ve küçük resim galerisi görünümüne döndüğünde dinamik bütçeyi temizleyen bir resim düzenleme uygulaması gösterilmektedir. Ayrıca, önbelleğe alınmış önizleme bit eşlemlerini baskı altında çıkarmak için bir OnOverBudgetListener kaydeder.
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)
}
override fun onStop() {
super.onStop()
budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
}
/**
* Called when the user enters the high-resolution editing canvas.
*/
fun enterEditingCanvas() {
try {
// Dynamically set budget to 256MB for the editing canvas
budgetManager.processBudgetBytes = 256L * 1024L * 1024L
Log.i(TAG, "Dynamic budget applied: 256MB")
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Could not apply dynamic budget", e)
}
}
/**
* Called when the user exits the editor back to the thumbnail gallery.
*/
fun exitToGallery() {
previewCache.trimToSize(8 * 1024 * 1024)
// Clear dynamic budget; restores the baseline manifest budget
budgetManager.clearProcessBudget()
}
companion object {
private const val TAG = "ImageEditor"
}
}
NDK C++ örneği: Yerel 3D motor
Bu örnekte,
etkin grafik kalite seviyesine göre bellek bütçelerini yöneten, bütçeyi aşması durumunda doku mipmap'lerini kaldırmak için AMemoryBudgetManager_Watcher_create
ALooper kullanan yerel bir C++ oyun motoru gösterilmektedir.
#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;
};