ऐप्लिकेशन के मेमोरी बजट की मदद से, ऐप्लिकेशन अपने लिए मेमोरी बजट तय कर सकते हैं. इससे सिस्टम को यह पता चलता है कि जब ऐप्लिकेशन, सेट किए गए बजट से ज़्यादा मेमोरी का इस्तेमाल करता है, तो उसे मेमोरी का इस्तेमाल कम करना है. यह खास तौर पर सिस्टम और बंडल किए गए ऐप्लिकेशन के लिए काम का है. इसके अलावा, यह उन ऐप्लिकेशन के लिए भी काम का है जो कम मेमोरी वाले डिवाइसों को टारगेट करते हैं. ऐसे मामलों में, डेवलपर को अपने ऐप्लिकेशन के लिए ज़रूरी मेमोरी के बारे में पता होता है. साथ ही, वह यह पक्का करना चाहता है कि उसका ऐप्लिकेशन, सिस्टम की शेयर की गई रैम के संसाधनों का बहुत ज़्यादा इस्तेमाल न करे.
बजट को संतुलित रखने के लिए, मेमोरी इविक्शन और स्वैप का इस्तेमाल किया जाता है. इससे उन मेमोरी पेजों को हटाया जाता है जिनका इस्तेमाल हाल ही में नहीं किया गया था. साथ ही, ऐप्लिकेशन की मेमोरी फ़ुटप्रिंट को उसके मौजूदा वर्किंग सेट पर फ़ोकस किया जाता है. जब कोई ऐप्लिकेशन, तय किए गए बजट से ज़्यादा खर्च करता है, तो ऑपरेटिंग सिस्टम इन टारगेट को वापस ले लेता है:
- फ़ाइल के साथ सेव किए गए पेजों को क्लीन किया जाता है. जैसे, इस्तेमाल न किया जा रहा कोड और मैप की गई ऐसेट. ऐसा इसलिए किया जाता है, क्योंकि ज़रूरत पड़ने पर इन्हें स्टोरेज से फिर से पढ़ा जा सकता है.
- डर्टी फ़ाइल-बैक किए गए पेजों को स्टोरेज में वापस लिखा जाता है और हटा दिया जाता है.
- बिना सोर्स फ़ाइल वाली मेमोरी के पेजों (जैसे, हीप एलोकेशन) को कंप्रेस किया जाता है और zRAM में स्वैप किया जाता है.
जब तक बजट, वर्किंग सेट से ज़्यादा नहीं होता, तब तक ऐप्लिकेशन ठीक से काम करेगा. साथ ही, यह सेट किए गए बजट से ज़्यादा मेमोरी का इस्तेमाल नहीं करेगा. ऑपरेटिंग सिस्टम, इस्तेमाल नहीं की गई मेमोरी को हटा देता है. साथ ही, स्वैप करने के लिए, इस्तेमाल नहीं किए जा रहे हीप पेजों को कंप्रेस करता है, ताकि प्रोसेस को बंद किए बिना मेमोरी का बंटवारा किया जा सके.
Android मेनिफ़ेस्ट में बजट तय करना
अपने AndroidManifest.xml में मेमोरी के बजट तय करना, बजट तय करने का मुख्य और सुझाया गया तरीका है. इसके लिए किसी रनटाइम कोड की ज़रूरत नहीं होती. यह प्रोसेस शुरू होने के तुरंत बाद लागू हो जाता है. साथ ही, यह ऑपरेटिंग सिस्टम के लिए एक साफ़ तौर पर कॉन्ट्रैक्ट उपलब्ध कराता है.
<memory-budget> के एलान, Android 17 QPR2 (एपीआई लेवल 37.2) और इसके बाद के वर्शन पर चल रहे डिवाइसों पर लागू होते हैं. Android के पुराने वर्शन पर, प्लैटफ़ॉर्म मेनिफ़ेस्ट पार्सर, न पहचाने गए एक्सएमएल एलिमेंट को सुरक्षित तरीके से अनदेखा कर देता है. इसलिए, <memory-budget> को अपनाया जा सकता है. इससे पुराने वर्शन के साथ काम करने की सुविधा पर कोई असर नहीं पड़ता.
बेसलाइन बजट तय करना
ज़्यादातर ऐप्लिकेशन के लिए, ऐप्लिकेशन के लिए एक बजट तय करना ही काफ़ी होता है. <memory-budget> एलिमेंट को सीधे तौर पर <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>
इससे पैकेज के लिए, सभी प्रोसेस और राज्यों में 256 एमबी का रेज़िडेंट मेमोरी बजट सेट हो जाता है. जब ऐप्लिकेशन के चलने के दौरान, इस्तेमाल की जाने वाली मेमोरी (मेमोरी फ़ुटप्रिंट) 256 एमबी से ज़्यादा हो जाती है, तो ऑपरेटिंग सिस्टम, इविक्शन और स्वैप का इस्तेमाल करके, इस्तेमाल नहीं किए जा रहे मेमोरी पेजों को ट्रिम कर देता है.
प्रोसेस की स्थिति के हिसाब से बजट में बदलाव करना
ऐप्लिकेशन को मेमोरी की अलग-अलग मात्रा की ज़रूरत होती है. यह इस बात पर निर्भर करता है कि ऐप्लिकेशन को कितने लोगों को दिखाया जा रहा है:
- फ़ोरग्राउंड: प्रोसेस, ऐसी ऐक्टिविटी को होस्ट कर रही है जो उपयोगकर्ता को दिख रही है और उसके साथ इंटरैक्ट कर रही है. आम तौर पर, इस स्थिति में सबसे ज़्यादा मेमोरी इस्तेमाल होती है. इसकी वजह यह है कि इसमें यूज़र इंटरफ़ेस (यूआई) और ग्राफ़िक ऐक्टिव होते हैं.
- उपयोगकर्ता को दिखने वाली: इस प्रोसेस के बारे में उपयोगकर्ता को पता होता है, लेकिन इसमें कोई विंडो नहीं दिखती. उदाहरण के लिए, मीडिया चलाने वाली फ़ोरग्राउंड सेवा, रास्ते के दिशा-निर्देश या चालू इनपुट मेथड को होस्ट करना.
- बैकग्राउंड: प्रोसेस, बैकग्राउंड में काम कर रही है. जैसे, रिसीवर या डेटा सिंक करना. इससे कम से कम असर पड़ने की उम्मीद है.
इन स्थितियों से मेल खाने के लिए, एक से ज़्यादा <memory-budget> क्लॉज़ तय किए जा सकते हैं:
<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>
android:state के बिना बेस क्लॉज़ की ज़रूरत नहीं होती. अगर सिर्फ़ राज्य के हिसाब से क्लॉज़ (उदाहरण के लिए, android:state="background") तय किए जाते हैं, तो अन्य राज्यों पर ऐप्लिकेशन के बजट का कोई असर नहीं पड़ता. शामिल किए जाने पर, android:state के बिना वाला क्लॉज़, उन स्थितियों के लिए डिफ़ॉल्ट फ़ॉलबैक के तौर पर काम करता है जिनके बारे में नहीं बताया गया है. जैसे, फ़ोरग्राउंड. जब ऐप्लिकेशन perceptible या background स्थितियों में ट्रांज़िशन करता है, तब बाद के ज़्यादा पाबंदी वाले क्लॉज़, इस क्लॉज़ को बदल देते हैं.
एक से ज़्यादा प्रोसेस वाले ऐप्लिकेशन
अगर आपका ऐप्लिकेशन, काम को कई प्रोसेस में बांटता है, तो <processes> के अंदर <process> टैग का इस्तेमाल करके, प्रोसेस के लिए अलग-अलग बजट कॉन्फ़िगर करें.
उदाहरण के लिए, संगीत स्ट्रीम करने वाले किसी ऐप्लिकेशन (com.example.radio) के बारे में सोचें:
- मुख्य प्रोसेस: यह प्रोसेस, दिखने वाले यूज़र इंटरफ़ेस (यूआई) और ऑडियो प्लेबैक इंजन को होस्ट करती है
(
MediaSessionServiceके साथmediaPlaybackफ़ोरग्राउंड सेवा). दिखने पर, यह प्रोसेस 180 एमबी के फ़ोरग्राउंड बजट के तहत काम करती है. जब उपयोगकर्ता, संगीत बजते समय ऐप्लिकेशन छोड़ देता है, तो प्रोसेसperceptibleस्थिति में आ जाती है. इस स्थिति में, प्लेबैक इंजन और ऑडियो बफ़र के लिए 64 एमबी का बजट काफ़ी होता है. - सिंक करने की प्रोसेस (
:sync): यह एक खास प्रोसेस है, जो बैकग्राउंड में मेटाडेटा को सिंक करती है. साथ ही, डाउनलोड किए गए डेटा को इंडेक्स करती है. यह प्रोसेस सिर्फ़ बैकग्राउंड में चालू रहती है. इसलिए, आपकोstate="background"के बारे में साफ़ तौर पर बताने की ज़रूरत नहीं है. इस पर एक ही बजट लागू होता है.
<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>
किसी भी सबप्रोसेस में मेमोरी के इस्तेमाल को, उसकी प्रोसेस के बजट और उसे शामिल करने वाले पैकेज के बजट, दोनों में गिना जाता है. अगर कोई प्रोसेस, रनटाइम के दौरान अपने प्रोसेस बजट या पैकेज बजट में से किसी एक का भी उल्लंघन करती है, तो उस पर मेमोरी प्रेशर पड़ता है. उल्लंघन उस थ्रेशोल्ड का होता है जो पहले पहुंचता है.
ज़्यादा डेंसिटी वाले डिसप्ले के लिए बजट बढ़ाना
जिन ऐप्लिकेशन के लिए मेमोरी फ़ुटप्रिंट, इस बात पर निर्भर करता है कि डिसप्ले पर एक साथ कितने पिक्सल ड्रा किए जाने हैं उनके लिए Android, बजट को डिसप्ले की खास बातों के हिसाब से डाइनैमिक तरीके से स्केल करने के दो अन्य तरीके उपलब्ध कराता है. जैसे, फ़ोटो गैलरी ऐप्लिकेशन, स्क्रीन के साइज़ के हिसाब से बिटमैप को कैश मेमोरी में सेव करता है:
डिसप्ले सघनता बकेट (
android:additionalMbPerDensity) के हिसाब से स्केल करें: यह डिसप्ले के सघनता अनुपात के हिसाब से,mdpi(1.0x / 160 डीपीआई) के मुकाबले मेगाबाइट जोड़ता है. यह तब सही होता है, जब मेमोरी का इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) डेंसिटी बकेट के साथ बढ़ता है. जैसे, ज़्यादा रिज़ॉल्यूशन वाले रास्टर ड्रॉएबल या यूज़र इंटरफ़ेस (यूआई) ऐसेट को कैश मेमोरी में सेव करना:<!-- Baseline 180MB + 16MB per 1.0x density ratio --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />mdpiडिसप्ले (1.0x) पर, बजट 180 + 16 × 1 = 196 एमबी है.xxhdpiडिसप्ले (3.0x) पर, बजट 180 + 16 × 3 = 228 एमबी तक बढ़ जाता है.फ़िज़िकल डिसप्ले रिज़ॉल्यूशन के हिसाब से स्केल करें (
android:additionalBytesPerDisplayPixel): यह फ़िज़िकल डिसप्ले पिक्सल (चौड़ाई × ऊंचाई) के हिसाब से सीधे तौर पर बाइट जोड़ता है. यह इन ऐप्लिकेशन के लिए सबसे सही है: ऐसे ऐप्लिकेशन जो फ़ुल-स्क्रीन ग्राफ़िक सर्फ़ेस, रेंडर बफ़र या फ़ुल-रिज़ॉल्यूशन फ़ोटो कैश मेमोरी को असाइन करते हैं. इनमें मेमोरी का इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) डेंसिटी बकेट के बजाय, सीधे तौर पर रॉ डिसप्ले पिक्सल की संख्या के हिसाब से होता है:<!-- 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" />1080 पिक्सल डिसप्ले (1080 × 2400 और ≈ 25.9 लाख पिक्सल) पर, इससे बेसलाइन बजट में ≈ 41.4 एमबी जुड़ जाता है. 1440 पिक्सल वाले डिसप्ले (1440 × 3120 और करीब 44.9 लाख पिक्सल) पर, यह करीब 71.8 एमबी जोड़ता है.
ये दोनों एट्रिब्यूट एक-दूसरे के विकल्प हैं. वह एट्रिब्यूट चुनें जो आपके ऐप्लिकेशन के प्राइमरी स्केलिंग फ़ैक्टर से मेल खाता हो. साथ ही, दोनों को एक ही क्लॉज़ में शामिल न करें.
डिवाइस के नाप या आकार के हिसाब से विशेषज्ञता हासिल करना
फ़ोन, टैबलेट, और Wear OS पर APK शिप करते समय, अलग-अलग हार्डवेयर टारगेट के लिए बजट अडजस्ट करने के लिए, android:feature एट्रिब्यूट का इस्तेमाल करें.
Wear OS वाली स्मार्टवॉच में रैम सीमित होती है. साथ ही, ऐप्लिकेशन का यूज़र इंटरफ़ेस (यूआई) और सुविधाओं का सेट बहुत आसान होता है. 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" />
नियम लागू करने का तरीका: सबसे हाल ही में लागू होने वाला क्लॉज़ लागू होता है
किसी ऐप्लिकेशन या प्रोसेस के लिए एक से ज़्यादा <memory-budget> एलिमेंट तय करते समय, सिस्टम उनका आकलन उसी क्रम में करता है जिस क्रम में उन्हें मेनिफ़ेस्ट में बताया गया है. बजट से जुड़ा सबसे हाल ही में लागू किया गया क्लॉज़ लागू होता है.
लागू होने वाला आखिरी बजट ही मान्य होता है. इसलिए, बजट का क्रम मायने रखता है. सबसे पहले, सामान्य बेसलाइन बजट सेट करें. इसके बाद, ज़्यादा खास जानकारी वाले बजट सेट करें. जैसे, राज्य के हिसाब से या हार्डवेयर के हिसाब से तय किए गए क्लॉज़.
एक्सएमएल एट्रिब्यूट का रेफ़रंस
मेमोरी के साइज़ से जुड़े सभी एट्रिब्यूट, मेगाबाइट (एमबी) में दिखाए जाते हैं. साथ ही, ये Linux cgroup memory.current चार्ज पर मैप करते हैं. इसमें Zygote जैसी शेयर की गई मेमोरी शामिल नहीं होती.
| एट्रिब्यूट | फ़ॉर्मैट | डिफ़ॉल्ट | ब्यौरा |
|---|---|---|---|
android:maxMb |
पूर्णांक (> 0) | ज़रूरी है | रेज़िडेंट मेमोरी के लिए तय की गई बेसलाइन बजट सीमा, एमबी में. |
android:state |
Enum | कोई भी | यह बजट, प्रोसेस की किस स्थिति पर लागू होता है: foreground, perceptible या background. अगर इसे शामिल नहीं किया जाता है, तो यह क्लॉज़, सहमति की स्थिति के बारे में जानकारी न देने पर फ़ॉलबैक के तौर पर काम करता है. |
android:additionalMbPerDensity |
पूर्णांक (≥ 0) | 0 |
mdpi (1.0x) के मुकाबले, डिस्प्ले डेंसिटी रेशियो की हर यूनिट के लिए जोड़े जाने वाले अतिरिक्त मेगाबाइट. |
android:additionalBytesPerDisplayPixel |
पूर्णांक (≥ 0) | 0 |
हर फ़िज़िकल डिसप्ले पिक्सल (चौड़ाई × ऊंचाई) के लिए, अतिरिक्त बाइट असाइन की जाती हैं. यह सर्फ़ेस बफ़र और बिटमैप के लिए काम की होती है. |
android:feature |
स्ट्रिंग | कोई भी | इस क्लॉज़ को उन डिवाइसों तक सीमित करता है जो हार्डवेयर की खास सुविधाओं के बारे में बताते हैं: watch, automotive या leanback. |
रनटाइम एपीआई (सेकंडरी डाइनैमिक विकल्प)
AndroidManifest.xml में बजट को स्टैटिक तौर पर सेट करना, ज़्यादातर ऐप्लिकेशन के लिए सबसे सही तरीका है. हालांकि, डाइनैमिक वर्कलोड वाले ऐप्लिकेशन या रनटाइम एक्सपेरिमेंट के लिए, Android रनटाइम एसडीके और एनडीके एपीआई को दूसरे विकल्प के तौर पर उपलब्ध कराता है.
रनटाइम एपीआई की मदद से:
- मौजूदा मेमोरी के इस्तेमाल और असरदार बजट के बारे में क्वेरी करें.
- प्रोसेस के बजट को डाइनैमिक तरीके से कम करें.
- बजट से ज़्यादा मेमोरी इस्तेमाल करने वाले इवेंट के बारे में सुनें, ताकि ऑपरेटिंग सिस्टम के डायरेक्ट रीक्लेम को ट्रिगर करने से पहले, कैश मेमोरी को कम किया जा सके.
Android SDK API (MemoryBudgetManager)
MemoryBudgetManager सिस्टम सेवा, Kotlin और Java में लिखे गए ऐप्लिकेशन के लिए उपलब्ध है. यह Android 17 QPR2 (एसडीके का माइनर रिलीज़, एपीआई लेवल 37.2 / Build.VERSION_CODES_FULL.CINNAMON_BUN_2) से शुरू होती है.
सेवा को वापस पाना
MemoryBudgetManager को ऐक्सेस करने से पहले, SDK_INT_FULL का इस्तेमाल करके यह देख लें कि डिवाइस में Android 17 QPR2 से पहले का वर्शन न चल रहा हो:
if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.CINNAMON_BUN_2) {
val budgetManager = context.getSystemService(MemoryBudgetManager::class.java)
}
क्वेरी के इस्तेमाल और बजट की जानकारी
// 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
बजट को डाइनैमिक तरीके से सेट या हटाया जा सकता है
रनटाइम के दौरान, मेमोरी को सीमित करने के लिए कम बजट सेट किया जा सकता है. इससे हल्के-फुल्के टास्क के दौरान मेमोरी का इस्तेमाल कम होगा. इसके अलावा, टास्क पूरा होने पर मेमोरी को खाली किया जा सकता है:
// 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()
बजट से ज़्यादा खर्च होने पर, कॉल बैक के लिए सुनें
ऐप्लिकेशन, लिसनर को रजिस्टर कर सकते हैं. इससे उन्हें तब सूचना मिलती है, जब मेमोरी का इस्तेमाल बजट थ्रेशोल्ड से ज़्यादा हो जाता है. इससे ऐप्लिकेशन को, ऐप्लिकेशन-लेवल पर पहले से ही क्लीनअप करने की अनुमति मिलती है. जैसे, ऑपरेटिंग सिस्टम के डायरेक्ट रिक्लेम लेटेंसी को ट्रिगर करने से पहले, इन-मेमोरी बिटमैप कैश को मिटाना:
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)
बजट से ज़्यादा खर्च होने पर कॉल बैक की सुविधा इस्तेमाल करने के सबसे सही तरीके:
- जल्दी कार्रवाई करें: दावा वापस पाने की प्रोसेस में, खरीदार को तुरंत राहत मिलनी चाहिए. प्रेशर के दौरान जटिल कैलकुलेशन करने से परफ़ॉर्मेंस खराब हो जाती है.
- नए ऑब्जेक्ट असाइन न करें: कॉलबैक के अंदर नए ऑब्जेक्ट असाइन न करें या नई थ्रेड शुरू न करें. ऐसा करने से, ऑपरेटिंग सिस्टम तुरंत मेमोरी वापस ले सकता है.
- ज़्यादा मेमोरी इस्तेमाल करने वाले टारगेट पर फ़ोकस करें: कई छोटे ऑब्जेक्ट रिलीज़ करने के बजाय, बड़े बिटमैप, रेंडर बफ़र या मेमोरी-मैप की गई फ़ाइलों को बंद करना ज़्यादा असरदार होता है.
नेटिव NDK API (<android/memory_budget_manager.h>)
नेटिव ऐप्लिकेशन, libandroid.so के ज़रिए उपलब्ध कराए गए C NDK API का इस्तेमाल कर सकते हैं. यह सुविधा, Android 17 QPR2 (एपीआई लेवल 37.2) से शुरू होती है.
CMake कॉन्फ़िगरेशन
find_library(android-lib android)
target_link_libraries(my_native_engine PRIVATE ${android-lib})
हेडर और क्वेरी के इस्तेमाल की जानकारी शामिल करना
#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
}
नेटिव बजट को डाइनैमिक तरीके से कॉन्फ़िगर करना
// 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();
मेमोरी प्रेशर इवेंट मॉनिटर करना
एनडीके, मेमोरी इवेंट को मॉनिटर करने के दो तरीके उपलब्ध कराता है:
- हाई-लेवल वॉचर (
AMemoryBudgetManager_Watcher_create): यहALooperपर इवेंट को मॉनिटर करता है. इसमें अपने-आप डीबाउंस होने की सुविधा होती है. - लो-लेवल फ़ाइल डिस्क्रिप्टर:
AMemoryBudgetManager_getProcessMemoryPressureFdएक नेटिव फ़ाइल डिस्क्रिप्टर दिखाता है, जिसे सीधे तौर पर कस्टमepollइंजन लूप में इंटिग्रेट किया जा सकता है.
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);
Runtime API के उदाहरण
यहां दिए गए उदाहरणों में, Android SDK API (Kotlin में लिखा गया) और Native NDK API (C++ में लिखा गया) का इस्तेमाल करके, रनटाइम एपीआई को लागू करने का तरीका बताया गया है.
Android SDK का उदाहरण: अडैप्टिव इमेज एडिटर
इस उदाहरण में, इमेज में बदलाव करने वाला एक ऐप्लिकेशन (com.example.imageeditor) दिखाया गया है. इसके मेनिफ़ेस्ट में, मल्टी-लेयर कैनवस के लिए 256 एमबी की सीमा तय की गई है:
<manifest ... >
<application ... >
<!-- Manifest ceiling accommodates the heaviest editing workload -->
<memory-budget android:maxMb="256" />
</application>
</manifest>
जब उपयोगकर्ता, हल्की थंबनेल गैलरी ब्राउज़ कर रहा होता है, तब ऐप्लिकेशन, Kotlin में Android SDK API का इस्तेमाल करता है. इससे प्रोसेस के बजट को डाइनैमिक तरीके से कम करके 96 एमबी किया जा सकता है.
जब उपयोगकर्ता मल्टी-लेयर एडिटिंग कैनवस खोलता है, तो ऐप्लिकेशन डाइनैमिक बजट को मिटा देता है, ताकि 256 एमबी के पूरे मेनिफ़ेस्ट सीलिंग को वापस लाया जा सके. यह कुकी, दबाव में कैश मेमोरी में सेव किए गए झलक वाले बिटमैप को हटाने के लिए, OnOverBudgetListener को भी रजिस्टर करती है.
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++ का उदाहरण: नेटिव 3D इंजन
इस उदाहरण में, नेटिव C++ गेम इंजन को दिखाया गया है. यह इंजन, ग्राफ़िक्स की क्वालिटी के चालू लेवल के आधार पर मेमोरी बजट को मैनेज करता है. इसमें यह माना गया है कि ऐप्लिकेशन के मेनिफ़ेस्ट में, अच्छी क्वालिटी वाले ग्राफ़िक्स (android:maxMb="512") के लिए 512 एमबी की सीमा तय की गई है. इंजन, कम क्वालिटी वाले प्रीसेट के लिए बजट को डाइनैमिक तरीके से कम करता है. साथ ही, बजट से ज़्यादा होने पर, टेक्सचर मिपमैप को अनलोड करने के लिए ALooper पर AMemoryBudgetManager_Watcher_create का इस्तेमाल करता है.
#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;
};