Budżety pamięci aplikacji umożliwiają aplikacjom deklarowanie własnego budżetu pamięci, co informuje system o konieczności ograniczenia zużycia pamięci, gdy aplikacja wykorzystuje więcej niż określony budżet. Jest to szczególnie przydatne w przypadku aplikacji systemowych i dołączonych lub aplikacji przeznaczonych na urządzenia o ograniczonej ilości pamięci, w których deweloper zna oczekiwany zestaw roboczy pamięci i chce mieć pewność, że aplikacja nie będzie zużywać zbyt dużo wspólnych zasobów pamięci RAM systemu.
Budżet jest utrzymywany w równowadze dzięki usuwaniu z pamięci i wymianie stron pamięci, które nie były ostatnio używane. Dzięki temu wykorzystanie pamięci przez aplikację może skupić się na bieżącym zestawie roboczym. Gdy aplikacja przekroczy zadeklarowany budżet, system operacyjny odzyska środki przeznaczone na kierowanie na tę aplikację:
- Strony oparte na plikach (takie jak nieaktywny kod i mapowane zasoby) są usuwane w pierwszej kolejności, ponieważ w razie potrzeby można je ponownie odczytać z pamięci.
- Zmodyfikowane strony z pliku są zapisywane z powrotem w pamięci i usuwane.
- Anonimowe strony pamięci (np. przydzielone obszary pamięci) są kompresowane i przenoszone do zRAM.
Dopóki budżet nie przekracza zestawu roboczego, aplikacja będzie działać prawidłowo, nie zużywając więcej pamięci niż określono w budżecie. System operacyjny zwalnia nieużywaną pamięć i kompresuje nieaktywne strony sterty do pliku wymiany, aby przydziały pamięci pozostawały ograniczone bez przerywania procesu.
Deklarowanie budżetów w pliku manifestu Androida
Deklarowanie budżetów pamięci w AndroidManifest.xml to podstawowa i zalecana metoda definiowania budżetów. Nie wymaga kodu w czasie działania, zaczyna działać natychmiast po uruchomieniu procesu i zapewnia jasną umowę z systemem operacyjnym.
Deklaracje <memory-budget> obowiązują na urządzeniach z Androidem 17 QPR2 (poziom interfejsu API 37.2) i nowszym. W starszych wersjach Androida parser pliku manifestu platformy bezpiecznie ignoruje nierozpoznane elementy XML, więc możesz zastosować <memory-budget> bez wpływu na zgodność wsteczną.
Deklarowanie budżetu podstawowego
W przypadku większości aplikacji wystarczy określić jeden budżet. Zadeklaruj element <memory-budget> bezpośrednio w tagu <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>
Ustawia budżet pamięci rezydentnej w wysokości 256 MB dla wszystkich procesów i stanów pakietu. Gdy wykorzystanie pamięci przez aplikację przekracza 256 MB, system operacyjny przycina nieaktywne strony pamięci, używając eksmisji i zamiany.
Zmieniaj budżety w zależności od stanu procesu
Aplikacja wymaga różnej ilości pamięci w zależności od tego, czy jest widoczna dla użytkownika:
- Pierwszy plan: proces hostuje widoczną aktywność, która wchodzi w interakcję z użytkownikiem. Ten stan zwykle zajmuje najwięcej miejsca ze względu na aktywny interfejs i grafikę.
- Zauważalny: proces jest zauważalny dla użytkownika, ale nie ma widocznego okna (np. hostuje usługę na pierwszym planie odtwarzającą multimedia, nawigację krok po kroku lub aktywną metodę wprowadzania).
- Background: proces uruchamia zadania w tle, odbiorniki lub synchronizacje danych. Powinien on zajmować minimalną ilość miejsca.
Możesz zadeklarować wiele klauzul <memory-budget>, aby dopasować je do tych stanów:
<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>
Klauzula podstawowa bez android:state nie jest wymagana. Jeśli określisz tylko klauzule dotyczące poszczególnych stanów (np. android:state="background"), inne stany nie będą podlegać ograniczeniom budżetu aplikacji. Jeśli klauzula without
android:state jest uwzględniona, działa ona jako domyślna opcja rezerwowa w przypadku nieokreślonych stanów (np. foreground), które są zastępowane przez kolejne, bardziej restrykcyjne klauzule, gdy aplikacja przechodzi w stan perceptible lub background.
Aplikacje wieloprocesowe
Jeśli aplikacja dzieli pracę na wiele procesów, skonfiguruj dedykowane budżety procesów za pomocą tagu <process> w tagu <processes>.
Rozważmy na przykład aplikację do streamingu muzyki (com.example.radio):
- Główny proces: hostuje widoczny interfejs i silnik odtwarzania dźwięku (
MediaSessionServicezmediaPlaybackusługą na pierwszym planie). Gdy jest widoczny, proces działa w ramach budżetu na pierwszym planie wynoszącego 180 MB. Gdy użytkownik opuści aplikację, a muzyka będzie nadal odtwarzana, proces przejdzie w stanperceptible, w którym 64 MB wystarczy na silnik odtwarzania i bufor audio. - Proces synchronizacji (
:sync): dedykowany proces działający w tle, który synchronizuje metadane i pobiera indeksowanie. Ten proces jest zawsze aktywny w tle, więc nie musisz go wyraźnie deklarowaćstate="background". Obowiązuje jeden budżet.
<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>
Wykorzystanie pamięci w dowolnym podprocesie jest wliczane zarówno do budżetu procesu, jak i do budżetu pakietu, w którym ten proces jest zawarty. Proces odczuwa presję pamięci w czasie działania, jeśli przekroczy budżet procesu lub budżet pakietu (w zależności od tego, który próg zostanie osiągnięty jako pierwszy).
Skalowanie budżetów na wyświetlaczach o dużej gęstości
W przypadku aplikacji, których zużycie pamięci zależy w znacznym stopniu od liczby pikseli wyświetlanych jednocześnie na ekranie (np. aplikacji galerii zdjęć, która buforuje mapy bitowe o rozmiarze ekranu), Android udostępnia 2 alternatywne mechanizmy dynamicznego skalowania budżetów w zależności od specyfikacji wyświetlacza:
Skaluj według zakresu gęstości wyświetlacza (
android:additionalMbPerDensity): dodaje megabajty proporcjonalnie do współczynnika gęstości wyświetlacza w stosunku domdpi(1,0x / 160 dpi). Jest to odpowiednie, gdy zużycie pamięci skaluje się w zależności od przedziałów gęstości interfejsu, np. w przypadku buforowania rysunków rastrowych lub zasobów interfejsu o wyższej rozdzielczości:<!-- Baseline 180MB + 16MB per 1.0x density ratio --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />Na wyświetlaczu
mdpi(1,0x) budżet wynosi 180 + 16 × 1 = 196 MB. Na wyświetlaczuxxhdpi(3,0x) budżet zostanie zwiększony do 180 + 16 × 3 = 228 MB.Skaluj według rozdzielczości fizycznej wyświetlacza (
android:additionalBytesPerDisplayPixel): dodaje bajty bezpośrednio na piksel fizyczny wyświetlacza (szerokość × wysokość). Jest to idealne rozwiązanie w przypadku aplikacji, które przydzielają powierzchnie graficzne na pełnym ekranie, bufory renderowania lub pamięci podręczne zdjęć w pełnej rozdzielczości, w których zużycie pamięci jest bezpośrednio proporcjonalne do liczby pikseli na wyświetlaczu, a nie do zakresów gęstości interfejsu:<!-- 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" />Na wyświetlaczu 1080p (1080 × 2400 pikseli, czyli ok.2,59 mln pikseli) zwiększa to budżet bazowy o ok.41,4 MB. Na wyświetlaczu o rozdzielczości 1440p (1440 × 3120 pikseli, czyli ok.4,49 M pikseli) dodaje ok.71,8 MB.
Te 2 atrybuty są alternatywne. Wybierz atrybut, który pasuje do głównego współczynnika skalowania aplikacji, i unikaj łączenia obu w tej samej klauzuli.
Dostosowywanie do formatów urządzeń
Podczas wysyłania pliku APK na telefony, tablety i urządzenia z Wear OS użyj atrybutu
android:feature, aby dostosować budżety do różnych docelowych urządzeń.
Na zegarkach z Wear OS pamięć RAM jest ograniczona, a interfejs i funkcje aplikacji są znacznie prostsze. Możesz zadeklarować mniejszy budżet przeznaczony specjalnie na 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" />
Reguła rozstrzygania: obowiązuje ostatnia odpowiednia klauzula
Podczas definiowania wielu elementów <memory-budget> dla aplikacji lub procesu system ocenia je w kolejności, w jakiej są zadeklarowane w pliku manifestu. Obowiązuje ostatnia obowiązująca klauzula budżetowa.
Ponieważ wygrywa ostatni odpowiedni budżet, kolejność ma znaczenie. Zawsze umieszczaj najpierw najbardziej ogólny budżet podstawowy, a potem bardziej szczegółowe zastąpienia (np. klauzule dotyczące konkretnego stanu lub sprzętu).
Odniesienie do atrybutu XML
Wszystkie atrybuty rozmiaru pamięci są wyrażone w megabajtach (MB) i odpowiadają obciążeniu grupy kontrolnej Linuxmemory.current (z wyłączeniem pamięci współdzielonej, takiej jak Zygote).
| Atrybut | Format | Domyślny | Opis |
|---|---|---|---|
android:maxMb |
Liczba całkowita (> 0) | Wymagany | Podstawowy limit budżetu pamięci rezydentnej w MB. |
android:state |
Typ wyliczeniowy | Dowolna | Stan procesu, do którego ma zastosowanie ten budżet: foreground, perceptible lub background. Jeśli zostanie pominięta, klauzula będzie działać jako rezerwa dla każdego nieokreślonego stanu. |
android:additionalMbPerDensity |
Liczba całkowita (≥ 0) | 0 |
Dodatkowe megabajty do dodania na jednostkę współczynnika gęstości wyświetlania w stosunku do mdpi (1,0x). |
android:additionalBytesPerDisplayPixel |
Liczba całkowita (≥ 0) | 0 |
Dodatkowe bajty przydzielone na piksel wyświetlacza fizycznego (szerokość × wysokość), przydatne w przypadku buforów powierzchni i bitmap. |
android:feature |
Ciąg znaków | Dowolna | Ogranicza klauzulę do urządzeń deklarujących określone funkcje sprzętowe: watch, automotive lub leanback. |
Interfejsy API środowiska wykonawczego (dodatkowa opcja dynamiczna)
Statyczne deklarowanie budżetów w AndroidManifest.xml jest preferowanym rozwiązaniem w przypadku niemal wszystkich aplikacji. W przypadku aplikacji z dynamicznymi zadaniami lub eksperymentów w czasie działania Android udostępnia jednak interfejsy API pakietu SDK i NDK w czasie działania jako opcję dodatkową.
Interfejs API środowiska wykonawczego umożliwia:
- Sprawdzanie bieżącego wykorzystania pamięci i efektywnych budżetów.
- Dynamicznie zmniejszaj budżet procesu.
- Nasłuchuj zdarzeń przekroczenia budżetu, aby proaktywnie przycinać pamięć podręczną, zanim system operacyjny wywoła bezpośrednie odzyskiwanie.
Android SDK API (MemoryBudgetManager)
Usługa systemowa MemoryBudgetManager jest dostępna w aplikacjach napisanych w językach Kotlin i Java od Androida 17 QPR2 (pomniejsza wersja pakietu SDK, poziom API 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2).
Pobieranie usługi
Zanim uzyskasz dostęp do MemoryBudgetManager, sprawdź, czy na urządzeniu nie jest zainstalowana wersja starsza niż Android 17 QPR2. W tym celu użyj SDK_INT_FULL:
if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_2) {
val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
}
Wykorzystanie zapytań i budżety
// 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
Dynamiczne ustawianie i usuwanie budżetów
Możesz ustawić mniejszy budżet w czasie działania programu, aby ograniczyć pamięć podczas wykonywania prostych zadań, lub wyczyścić go po zakończeniu zadania:
// 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()
Słuchaj wywołań zwrotnych dotyczących przekroczenia budżetu
Aplikacje mogą zarejestrować detektor, który będzie powiadamiał o przekroczeniu progu budżetu wykorzystania pamięci. Dzięki temu aplikacja może aktywnie czyścić pamięć na poziomie aplikacji (np. usuwać pamięci podręczne bitmap) zanim system operacyjny wywoła bezpośrednie odzyskiwanie pamięci:
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)
Sprawdzone metody dotyczące oddzwaniania w przypadku przekroczenia budżetu:
- Szybkie działanie: operacje odzyskiwania muszą przynosić natychmiastową ulgę. Złożone obliczenia podczas obciążenia pogarszają wydajność.
- Unikaj przydzielania pamięci: nie przydzielaj nowych obiektów ani nie uruchamiaj nowych wątków w funkcji zwrotnej, ponieważ może to spowodować natychmiastowe odzyskanie pamięci przez system operacyjny.
- Skup się na celach o wysokiej wydajności: usuwanie dużych map bitowych, buforów renderowania lub zamykanie plików mapowanych w pamięci jest znacznie skuteczniejsze niż zwalnianie wielu małych obiektów.
Native NDK API (<android/memory_budget_manager.h>)
Aplikacje natywne mogą korzystać z interfejsu C NDK API udostępnianego przez libandroid.so od Androida 17 QPR2 (poziom API 37.2).
Konfiguracja CMake
find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})
Uwzględnij nagłówek i użycie zapytania
#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
}
Dynamiczne konfigurowanie budżetu na reklamy natywne
// 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();
Monitorowanie zdarzeń związanych z obciążeniem pamięci
NDK udostępnia 2 sposoby monitorowania zdarzeń związanych z pamięcią:
- Obserwator wysokiego poziomu (
AMemoryBudgetManager_Watcher_create): monitoruje zdarzenia naALooperz automatycznym usuwaniem powtórzeń. - Deskryptor pliku niskiego poziomu: zwraca natywny deskryptor pliku, który można zintegrować bezpośrednio z niestandardową pętlą silnika.
AMemoryBudgetManager_getProcessMemoryPressureFdepoll
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);
Przykłady interfejsu Runtime API
Poniższe przykłady pokazują, jak zaimplementować interfejsy API środowiska wykonawczego za pomocą interfejsu Android SDK API (napisanego w Kotlinie) i natywnego interfejsu NDK API (napisanego w C++).
Przykład pakietu Android SDK: adaptacyjny edytor obrazów
Ten przykład przedstawia aplikację do edycji obrazów (com.example.imageeditor), której plik manifestu deklaruje limit 256 MB, aby pomieścić wielowarstwowy obszar roboczy:
<manifest ... >
<application ... >
<!-- Manifest ceiling accommodates the heaviest editing workload -->
<memory-budget android:maxMb="256" />
</application>
</manifest>
Gdy użytkownik przegląda galerię lekkich miniatur, aplikacja używa interfejsu API pakietu Android SDK w języku Kotlin, aby dynamicznie zmniejszyć budżet procesu do 96 MB.
Gdy użytkownik otworzy wielowarstwową przestrzeń do edycji, aplikacja wyczyści dynamiczny budżet, aby przywrócić pełny limit 256 MB w pliku manifestu. Rejestruje też
OnOverBudgetListener, aby w razie potrzeby usunąć z pamięci podręcznej mapy bitowe podglądu.
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"
}
}
Przykład NDK C++: natywny silnik 3D
Ten przykład pokazuje, jak natywny silnik gry w C++ zarządza budżetami pamięci na podstawie aktywnego poziomu jakości grafiki, przy założeniu, że plik manifestu aplikacji deklaruje limit 512 MB na potrzeby grafiki wysokiej jakości (android:maxMb="512"). Silnik dynamicznie ogranicza budżet w przypadku ustawień wstępnych niższej jakości i używa AMemoryBudgetManager_Watcher_create na ALooper, aby zwalniać mapy mipmap tekstur, gdy budżet jest przekroczony.
#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;
};