Mit App-Arbeitsspeicherbudgets können Apps ein Arbeitsspeicherbudget für sich selbst deklarieren. Das System wird dann angewiesen, die Arbeitsspeichernutzung zu reduzieren, wenn die App mehr als das festgelegte Budget verwendet. Dies ist besonders nützlich für System- und gebündelte Apps oder für Apps, die auf Geräte mit wenig Arbeitsspeicher ausgerichtet sind. In diesen Fällen kennt der Entwickler den erwarteten Arbeitsspeicher-Working Set und möchte sicherstellen, dass seine App nicht zu viele der gemeinsamen RAM-Ressourcen des Systems verwendet.
Das Budget wird durch Entfernen und Auslagern von Arbeitsspeicher ausgeglichen, um Arbeitsspeicherseiten zu entfernen, die nicht kürzlich verwendet wurden. So wird der Arbeitsspeicherbedarf der App auf den aktuellen Arbeitsbereich konzentriert. Wenn eine App ihr deklariertes Budget überschreitet, richtet das Betriebssystem die Rückforderung speziell auf diese App aus:
- Bereinigte seitenbasierte Dateien (z. B. inaktiver Code und zugeordnete Assets) werden zuerst entfernt, da sie bei Bedarf aus dem Speicher neu gelesen werden können.
- Dirty file-backed pages (geänderte Seiten, die auf Dateien basieren) werden zurück in den Speicher geschrieben und entfernt.
- Anonyme Speicherseiten (z. B. Heap-Zuweisungen) werden komprimiert und in zRAM ausgelagert.
Solange das Budget das Working Set nicht überschreitet, funktioniert die App gut und verbraucht nicht mehr Arbeitsspeicher als im festgelegten Budget. Das Betriebssystem gibt nicht verwendeten Arbeitsspeicher frei und komprimiert inaktive Heap-Seiten, um sie auszulagern, damit die Arbeitsspeicherzuweisungen begrenzt bleiben, ohne den Prozess zu beenden.
Budgets im Android-Manifest deklarieren
Die Deklaration Ihrer Speicherbudgets in Ihrem AndroidManifest.xml ist die primäre und empfohlene Methode zum Definieren von Budgets. Es ist kein Laufzeitcode erforderlich, die Änderung wird sofort nach dem Start des Prozesses wirksam und es wird ein klarer Vertrag für das Betriebssystem bereitgestellt.
<memory-budget>-Deklarationen werden auf Geräten mit Android 17 QPR2 (API-Level 37.2) und höher wirksam. In niedrigeren Android-Versionen werden unbekannte XML-Elemente vom Manifest-Parser der Plattform sicher ignoriert. Sie können <memory-budget> also verwenden, ohne die Abwärtskompatibilität zu beeinträchtigen.
Ausgangsbudget deklarieren
Für die meisten Apps reicht es aus, ein einzelnes Budget für die Anwendung zu definieren. Deklarieren Sie ein <memory-budget>-Element direkt im <application>-Tag:
<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>
Dadurch wird ein Budget von 256 MB für den residenten Speicher für alle Prozesse und Status des Pakets festgelegt. Wenn der Speicherbedarf der App 256 MB überschreitet, werden inaktive Speicherseiten vom Betriebssystem durch Entfernen und Auslagern reduziert.
Budgets nach Prozessstatus variieren
Eine App benötigt je nach Sichtbarkeit für Nutzer unterschiedlich viel Speicherplatz:
- Vordergrund: Der Prozess hostet eine sichtbare Aktivität, die mit dem Nutzer interagiert. Dieser Status hat in der Regel den größten Speicherbedarf, da die Benutzeroberfläche und Grafiken aktiv sind.
- Wahrnehmbar: Der Prozess ist für den Nutzer wahrnehmbar, es wird aber kein sichtbares Fenster gehostet (z. B. wenn ein Dienst im Vordergrund für die Medienwiedergabe, eine detaillierte Routenführung oder eine aktive Eingabemethode gehostet wird).
- Hintergrund: Der Prozess führt Hintergrundjobs, Empfänger oder Datensynchronisierungen aus. Der Ressourcenverbrauch sollte minimal sein.
Sie können mehrere <memory-budget>-Klauseln deklarieren, um diesen Status zu entsprechen:
<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>
Eine Basisklausel ohne android:state ist nicht erforderlich. Wenn Sie nur bundesstaatsspezifische Klauseln angeben (z. B. android:state="background"), bleiben andere Bundesstaaten ohne App-Budget. Wenn eine Klausel ohne android:state enthalten ist, dient sie als Standard-Fallback für nicht angegebene Status (z. B. „foreground“). Nachfolgende restriktivere Klauseln überschreiben sie, wenn die App in den Status perceptible oder background wechselt.
Apps mit mehreren Prozessen
Wenn Ihre Anwendung die Arbeit auf mehrere Prozesse verteilt, konfigurieren Sie mit dem <process>-Tag in <processes> dedizierte Prozessbudgets.
Hier ein Beispiel für eine Musikstreaming-App (com.example.radio):
- Hauptprozess: Hier werden die sichtbare Benutzeroberfläche und die Audio-Wiedergabe-Engine gehostet (
MediaSessionServicemit einemmediaPlayback-Dienst im Vordergrund). Wenn der Prozess sichtbar ist, wird er mit dem Vordergrundbudget von 180 MB ausgeführt. Wenn der Nutzer die App verlässt, während die Musik weiter abgespielt wird, wechselt der Prozess in den Statusperceptible. In diesem Fall reichen 64 MB für die Wiedergabe-Engine und den Audio-Puffer aus. - Synchronisierungsprozess (
:sync): Ein dedizierter Prozess, der die Synchronisierung von Hintergrundmetadaten und die Indexierung von Downloads ausführt. Da dieser Prozess nur im Hintergrund aktiv ist, müssen Siestate="background"nicht explizit deklarieren. Es gilt ein einzelnes Budget.
<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>
Die Arbeitsspeichernutzung in einem beliebigen untergeordneten Prozess wird sowohl auf das Prozessbudget als auch auf das Budget des einschließenden Pakets angerechnet. Ein Prozess ist während der Laufzeit einem Speicherdruck ausgesetzt, wenn er entweder sein Prozessbudget oder das Paketbudget überschreitet, je nachdem, welcher Grenzwert zuerst erreicht wird.
Budgets für Displays mit hoher Pixeldichte skalieren
Für Anwendungen, deren Speicherbedarf sich erheblich danach richtet, wie viele Pixel gleichzeitig auf dem Display gerendert werden müssen, z. B. eine Fotogalerie-App, die Bitmaps in Bildschirmgröße im Cache speichert, bietet Android zwei alternative Mechanismen, um Budgets dynamisch an die Displayspezifikationen anzupassen:
Nach Bucket für Kompaktheitsgrad skalieren (
android:additionalMbPerDensity): Fügt Megabyte proportional zum Kompaktheitsgrad des Displays im Verhältnis zumdpi(1,0x / 160 dpi) hinzu. Dies ist geeignet, wenn die Speichernutzung mit den UI-Dichtebereichen skaliert wird, z. B. beim Caching von Raster-Drawables oder UI-Assets mit höherer Auflösung:<!-- Baseline 180MB + 16MB per 1.0x density ratio --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />Bei einem
mdpi-Display (1,0‑fach) beträgt das Budget 180 + 16 × 1 = 196 MB. Auf einemxxhdpi-Display (3,0x) wird das Budget auf 180 + 16 × 3 = 228 MB skaliert.Nach physischer Displayauflösung skalieren (
android:additionalBytesPerDisplayPixel): Fügt Bytes direkt pro physischem Displaypixel (Breite × Höhe) hinzu. Das ist ideal für Anwendungen, die Grafiken im Vollbildmodus, Renderpuffer oder Fotocaches in voller Auflösung zuweisen, bei denen der Speicherverbrauch direkt mit der Anzahl der Rohanzeigepixel und nicht mit den UI-Dichtebereichen skaliert wird:<!-- 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" />Auf einem 1080p-Display (1080 × 2400 ≈ 2,59 Mio. Pixel) werden dem Basisbudget so etwa 41,4 MB hinzugefügt. Auf einem 1440p-Display (1440 × 3120 Pixel, ca. 4,49 Mio. Pixel) werden ca. 71,8 MB hinzugefügt.
Diese beiden Attribute sind Alternativen. Wählen Sie das Attribut aus, das dem primären Skalierungsfaktor Ihrer App entspricht, und vermeiden Sie es, beide in derselben Klausel zu kombinieren.
Für Geräteformfaktoren optimieren
Wenn Sie ein APK für Smartphones, Tablets und Wear OS bereitstellen, verwenden Sie das Attribut android:feature, um Budgets für verschiedene Hardwareziele anzupassen.
Auf Wear OS-Smartwatches ist der RAM begrenzt und die Benutzeroberfläche und das Feature-Set der App sind viel einfacher. Sie können ein kleineres Budget speziell für die Funktion watch deklarieren:
<!-- 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" />
Regel zur Beilegung: Die letzte anwendbare Klausel tritt in Kraft
Wenn Sie mehrere <memory-budget>-Elemente für eine Anwendung oder einen Prozess definieren, werden sie vom System in der Reihenfolge ausgewertet, in der sie im Manifest deklariert sind. Die zuletzt anwendbare Budgetklausel wird durchgesetzt.
Da das letzte anwendbare Budget berücksichtigt wird, ist die Reihenfolge wichtig. Das allgemeinste Baseline-Budget sollte immer zuerst angegeben werden, gefolgt von spezifischeren Überschreibungen (z. B. klauseln für bestimmte Bundesstaaten oder Hardware).
Referenz zu XML-Attributen
Alle Attribute für die Arbeitsspeichergröße werden in Megabyte (MB) angegeben und entsprechen der Linux-Cgroup-Gebühr memory.current, die den gemeinsam genutzten Arbeitsspeicher wie den Zygote-Prozess ausschließt.
| Attribut | Format | Standard | Beschreibung |
|---|---|---|---|
android:maxMb |
Ganzzahl (> 0) | Erforderlich | Das residenter Speicher-Budgetlimit in MB. |
android:state |
Enum | Beliebig | Der Prozessstatus, für den dieses Budget gilt: foreground, perceptible oder background. Wird sie weggelassen, dient sie als Fallback für alle nicht angegebenen Status. |
android:additionalMbPerDensity |
Ganzzahl (≥ 0) | 0 |
Zusätzliche Megabyte, die pro Einheit des Verhältnisses der Anzeigedichte relativ zu mdpi (1,0x) hinzugefügt werden sollen. |
android:additionalBytesPerDisplayPixel |
Ganzzahl (≥ 0) | 0 |
Zusätzliche Byte, die pro physischem Displaypixel (Breite × Höhe) zugewiesen werden. Dies ist nützlich für Oberflächenpuffer und Bitmaps. |
android:feature |
String | Beliebig | Beschränkt die Klausel auf Geräte, die bestimmte Hardwarefunktionen deklarieren: watch, automotive oder leanback. |
Laufzeit-APIs (sekundäre dynamische Option)
Die statische Deklaration von Budgets in AndroidManifest.xml ist die bevorzugte Lösung für fast alle Apps. Für Anwendungen mit dynamischen Arbeitslasten oder für Laufzeit-Experimente bietet Android jedoch Laufzeit-SDK- und NDK-APIs als sekundäre Option.
Mit der Laufzeit-API haben Sie folgende Möglichkeiten:
- Aktuelle Arbeitsspeichernutzung und effektive Budgets abfragen
- Prozessbudget dynamisch nach unten anpassen
- Achten Sie auf Ereignisse, die das Budget überschreiten, um Caches proaktiv zu verkleinern, bevor das Betriebssystem die direkte Rückforderung auslöst.
Android SDK API (MemoryBudgetManager)
Der Systemdienst MemoryBudgetManager ist für Apps verfügbar, die in Kotlin und Java geschrieben wurden und auf Android 17 QPR2 (untergeordnete SDK-Version, API-Level 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) basieren.
Dienst abrufen
Bevor Sie auf MemoryBudgetManager zugreifen, prüfen Sie mit SDK_INT_FULL, ob auf dem Gerät eine niedrigere Version als Android 17 QPR2 ausgeführt wird:
if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_2) {
val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
}
Abfragenutzung und Budgets
// 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
Budgets dynamisch festlegen oder löschen
Sie können zur Laufzeit ein geringeres Budget festlegen, um den Arbeitsspeicher bei einfachen Aufgaben zu begrenzen, oder es nach Abschluss der Aufgabe löschen:
// 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()
Auf Anrufe zur Budgetüberschreitung reagieren
Apps können einen Listener registrieren, um benachrichtigt zu werden, wenn die Arbeitsspeichernutzung den Budgetschwellenwert überschreitet. So kann die App proaktiv Bereinigungen auf Anwendungsebene durchführen (z. B. das Leeren von Bitmap-Caches im Arbeitsspeicher), bevor das Betriebssystem die Latenz für die direkte Rückforderung auslöst:
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)
Best Practices für Rückrufe bei Überschreitung des Budgets:
- Schnell reagieren: Bei Rückforderungsmaßnahmen muss sofortige Abhilfe geschaffen werden. Komplexe Berechnungen während des Drucks verschlechtern die Leistung.
- Zuweisungen vermeiden: Weisen Sie im Callback keine neuen Objekte zu und starten Sie keine neuen Threads, da dies dazu führen kann, dass das Betriebssystem sofort direkt zurückfordert.
- Auf Objekte mit hoher Ausbeute konzentrieren: Das Entfernen großer Bitmaps, Renderpuffer oder das Schließen von speicherabgebildeten Dateien ist viel effektiver als das Freigeben vieler kleiner Objekte.
Native NDK API (<android/memory_budget_manager.h>)
Native Apps können die C-NDK-API verwenden, die von libandroid.so ab Android 17 QPR2 (API-Level 37.2) bereitgestellt wird.
CMake-Konfiguration
find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})
Header- und Abfragenutzung einschließen
#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
}
Natives Budget dynamisch konfigurieren
// 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();
Arbeitsspeicherdruckereignisse beobachten
Das NDK bietet zwei Möglichkeiten, Speicherereignisse zu beobachten:
- High-Level Watcher (
AMemoryBudgetManager_Watcher_create): Überwacht Ereignisse in einemALoopermit automatischer Entprellung. - Dateideskriptor auf niedriger Ebene:
AMemoryBudgetManager_getProcessMemoryPressureFdgibt einen nativen Dateideskriptor zurück, der direkt in eine benutzerdefinierteepoll-Engine-Schleife eingebunden werden kann.
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);
Beispiele für die Runtime API
Die folgenden Beispiele zeigen, wie Sie die Laufzeit-APIs mit der Android SDK API (in Kotlin geschrieben) und der Native NDK API (in C++ geschrieben) implementieren.
Android SDK-Beispiel: Adaptiver Bildeditor
In diesem Beispiel wird eine Bildbearbeitungs-App (com.example.imageeditor) gezeigt, in deren Manifest eine Obergrenze von 256 MB für den mehrschichtigen Canvas deklariert wird:
<manifest ... >
<application ... >
<!-- Manifest ceiling accommodates the heaviest editing workload -->
<memory-budget android:maxMb="256" />
</application>
</manifest>
Wenn der Nutzer die Miniaturansicht-Galerie durchsucht, verwendet die App die Android SDK API in Kotlin, um das Prozessbudget dynamisch auf 96 MB zu reduzieren.
Wenn der Nutzer den mehrschichtigen Bearbeitungsbereich öffnet, löscht die App das dynamische Budget, um das Manifestlimit von 256 MB wiederherzustellen. Außerdem wird ein OnOverBudgetListener registriert, um zwischengespeicherte Vorschaubits bei Bedarf zu entfernen.
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"
}
}
NDK C++-Beispiel: Native 3D-Engine
In diesem Beispiel wird gezeigt, wie eine native C++-Game-Engine Speicherbudgets basierend auf dem aktiven Grafikqualitätsniveau verwaltet. Dabei wird davon ausgegangen, dass im Manifest der Anwendung eine Obergrenze von 512 MB für Grafiken in hoher Qualität (android:maxMb="512") festgelegt ist. Die Engine schränkt das Budget für Voreinstellungen mit niedrigerer Qualität dynamisch ein und verwendet AMemoryBudgetManager_Watcher_create für ein ALooper, um Texture-Mipmaps zu entladen, wenn das Budget überschritten wird.
#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;
};