ऐप्लिकेशन मेमोरी बजट की मदद से, ऐप्लिकेशन अपने लिए मेमोरी बजट तय कर सकते हैं. इससे सिस्टम को पता चलता है कि जब ऐप्लिकेशन अपने तय बजट से ज़्यादा मेमोरी का इस्तेमाल करता है, तो उसे मेमोरी के इस्तेमाल को कम करना है. यह सुविधा, सिस्टम और बंडल किए गए ऐप्लिकेशन या कम मेमोरी वाले डिवाइसों को टारगेट करने वाले ऐप्लिकेशन के लिए खास तौर पर मददगार है. इन ऐप्लिकेशन के डेवलपर को पता होता है कि उनके ऐप्लिकेशन को मेमोरी वर्किंग सेट (ऐप्लिकेशन को उपयोगकर्ता की मौजूदा गतिविधि के लिए, कितनी मेमोरी की ज़रूरत है) के लिए कितनी मेमोरी की ज़रूरत है. साथ ही, वे यह पक्का करना चाहते हैं कि उनका ऐप्लिकेशन, सिस्टम के शेयर किए गए रैम संसाधनों का ज़्यादा इस्तेमाल न करे.
बजट को संतुलित रखने के लिए, मेमोरी को खाली करने और स्वैप करने की सुविधा का इस्तेमाल किया जाता है. इससे, हाल ही में इस्तेमाल नहीं किए गए मेमोरी पेजों को हटाया जाता है. साथ ही, ऐप्लिकेशन के मेमोरी फ़ुटप्रिंट को उसके मौजूदा वर्किंग सेट पर फ़ोकस किया जाता है. जब कोई ऐप्लिकेशन तय किए गए बजट से ज़्यादा मेमोरी का इस्तेमाल करता है, तो ऑपरेटिंग सिस्टम खास तौर पर उस ऐप्लिकेशन को टारगेट करता है:
- क्लीन फ़ाइल-बैक्ड पेज (जैसे, इनऐक्टिव कोड और मैप की गई ऐसेट) को सबसे पहले हटाया जाता है, क्योंकि ज़रूरत पड़ने पर उन्हें स्टोरेज से फिर से पढ़ा जा सकता है.
- डर्टी फ़ाइल-बैक्ड पेज को स्टोरेज में वापस लिखा जाता है और हटा दिया जाता है.
- बिना सोर्स फ़ाइल वाले मेमोरी पेज (जैसे, हीप ऐलोकेशन) कंप्रेस किए जाते हैं और zRAM में स्वैप किए जाते हैं.
जब तक वर्किंग सेट, बजट से ज़्यादा नहीं होता, तब तक ऐप्लिकेशन अच्छी तरह से काम करेगा. साथ ही, वह तय किए गए बजट से ज़्यादा मेमोरी का इस्तेमाल नहीं करेगा. ऑपरेटिंग सिस्टम, इस्तेमाल न की गई मेमोरी को हटा देता है और इनऐक्टिव हीप पेजों को कंप्रेस करके स्वैप कर देता है. इससे प्रोसेस को खत्म किए बिना, मेमोरी का बंटवारा सीमित रहता है.
जब किसी ऐप्लिकेशन के लिए तय किया गया बजट खत्म हो जाता है, तब क्या होता है
मेमोरी बजट से ज़्यादा मेमोरी इस्तेमाल करने पर, सिस्टम आपके ऐप्लिकेशन को बंद या क्रैश नहीं करता. इसके बजाय, जब कोई ऐप्लिकेशन अपने बजट से ज़्यादा मेमोरी इस्तेमाल करता है, तो ओएस उस मेमोरी का पता लगाता है जिसे ऐप्लिकेशन ने कुछ समय से इस्तेमाल नहीं किया है. इसके बाद, वह उस मेमोरी को सुरक्षित तरीके से हटा देता है, ताकि ज़रूरत पड़ने पर उसे फिर से इस्तेमाल किया जा सके. इससे नए ऐप्लिकेशन के लिए मेमोरी का बंटवारा किया जा सकता है. साथ ही, ऐप्लिकेशन के लिए मेमोरी के इस्तेमाल को बजट के अंदर रखा जा सकता है.
जब तक ऐप्लिकेशन को किसी भी समय ज़रूरत पड़ने वाली मेमोरी (इसका वर्किंग सेट) और तय किए गए बजट के बीच हेडरूम मौजूद रहता है, तब तक ऐप्लिकेशन सामान्य रूप से चलता रहता है. अगर कोई ऐप्लिकेशन, अपने वर्किंग सेट से कम बजट सेट करता है, तो रनटाइम के दौरान ऐप्लिकेशन की परफ़ॉर्मेंस धीमी हो सकती है.
ओएस, मेमोरी को कैसे ट्रैक करता है और उसे कैसे वापस लेता है, यह जानने के लिए मेमोरी आर्किटेक्चर की गाइड देखें:
- मेमोरी को फिर से हासिल करना और स्वैप करना: इससे पता चलता है कि ओएस, आपके ऐप्लिकेशन के लिए निजी अनाम हीप और फ़ाइल-बैक्ड पेजों को कैसे चार्ज करता है. साथ ही, बजट से ज़्यादा होने पर कोल्ड पेजों को फिर से कैसे हासिल करता है.
- बुनियादी सिद्धांत: इसमें बताया गया है कि शेयर की गई Zygote मेमोरी को आपके ऐप्लिकेशन के बजट से क्यों बाहर रखा गया है. साथ ही, यह भी बताया गया है कि फ़ील्ड टेलीमेट्री में, रेज़िडेंट बजट का एनॉनिमस आरएसएस + स्वैप से क्या संबंध है.
- बिट मैप और मेमोरी: इसमें बताया गया है कि जीपीयू की मदद से काम करने वाले हार्डवेयर बिट मैप और डीएमए-बीयूएफ़ ऐलोकेशन को आपके ऐप्लिकेशन के हीप और मेमोरी बजट से बाहर कैसे रखा जाता है.
Android मेनिफ़ेस्ट में बजट तय करना
AndroidManifest.xml में मेमोरी बजट तय करना, बजट तय करने का मुख्य और सुझाया गया तरीका है. इसके लिए, किसी रनटाइम कोड की ज़रूरत नहीं होती. साथ ही, प्रोसेस शुरू होते ही यह लागू हो जाता है. इसके अलावा, यह ऑपरेटिंग सिस्टम के लिए एक साफ़ तौर पर कॉन्ट्रैक्ट उपलब्ध कराता है.
<memory-budget> के एलान, Android 17 QPR2 (एपीआई लेवल 37.2) और इसके बाद के वर्शन वाले डिवाइसों पर लागू होते हैं. Android के पुराने वर्शन पर, प्लैटफ़ॉर्म मेनिफ़ेस्ट पार्सर, एक्सएमएल के उन एलिमेंट को सुरक्षित तरीके से अनदेखा कर देता है जिनकी पहचान नहीं हो पाती. इससे, पुराने सिस्टम के साथ काम करने की सुविधा पर असर डाले बिना, <memory-budget> को अपनाया जा सकता है.
बेसलाइन बजट तय करना
ज़्यादातर ऐप्लिकेशन के लिए, सिर्फ़ एक बजट तय करना ही काफ़ी होता है. <application> टैग के अंदर सीधे तौर पर <memory-budget> एलिमेंट को शामिल करें:
<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 स्थितियों में ट्रांज़िशन करता है, तो ज़्यादा पाबंदी वाले क्लॉज़, डिफ़ॉल्ट फ़ॉलबैक को बदल देते हैं.
हर स्टेट के लिए बजट तय करते समय, उस स्टेट के ऐक्टिव वर्किंग सेट को टारगेट करें, न कि पीक रेज़िडेंट मेमोरी (आरएसएस हाई-वॉटर मार्क) को:
- फ़्रेम रेंडरिंग को बेहतर बनाने के लिए,
foregroundसाइज़ के बजट तय करें: फ़ोरग्राउंड यूज़र इंटरफ़ेस (यूआई) को उसके ऐक्टिव वर्किंग सेट के लिए ज़रूरत के मुताबिक हेडरूम दें, ताकि सिंक्रोनस रिक्लेम की वजह से फ़्रेम रेंडरिंग में देरी न हो. अगर आपके ऐप्लिकेशन में ज़्यादा मेमोरी इस्तेमाल करने वाला स्टैंडअलोन वर्कफ़्लो (जैसे, हाई रिज़ॉल्यूशन वाली इमेज या वीडियो एडिटर) शामिल है, तो उस गतिविधि को खास प्रोसेस में ले जाएं (मल्टी-प्रोसेस ऐप्लिकेशन देखें), ताकि उसके लिए अलग से बजट तय किया जा सके और उपयोगकर्ता के बाहर निकलने पर उसे बंद किया जा सके. - हेडलेस वर्क के लिए,
backgroundसाइज़ के बजट को टाइट रखें: फ़ील्ड टेलीमेट्री में ज़्यादा बैकग्राउंड आरएसएस से अक्सर यह पता चलता है कि फ़ोरग्राउंड से बाहर निकलने के बाद भी यूआई के लिए तय की गई मेमोरी का इस्तेमाल किया जा रहा है. इस बारे में ज़्यादा जानने के लिए, ज़्यादा मेमोरी का इस्तेमाल करने वाले यूआई के साथ बाउंड सर्विस को को-लोकेट करना और इवेंट के जवाब में मेमोरी रिलीज़ करना देखें. इसके अलावा, बैकग्राउंड इंडेक्सिंग और सिंक करने के दौरान, एक बार में फ़ाइलें पढ़ने से भी ऐसा हो सकता है. बैकग्राउंड में बिना यूज़र इंटरफ़ेस के होने वाले काम के लिए, फ़्रेम की कोई समयसीमा नहीं होती. इसलिए, बैकग्राउंड में सिंक होने की प्रोसेस के दौरान, बजट से ज़्यादा मेमोरी का इस्तेमाल होने पर, एक बार इस्तेमाल होने वाले फ़ाइल कैश पेजों को हटाना और इस्तेमाल न होने वाले हीप पेजों को zRAM में स्वैप करना, उपयोगकर्ता को दिखने वाली कोई रुकावट पैदा नहीं करता. साथ ही, बैकग्राउंड में होने वाली प्रोसेस की वजह से, उपयोगकर्ता के फ़ोरग्राउंड ऐप्लिकेशन को हटाने से रोकता है.
बजट की स्थितियों को प्रोसेस की स्थितियों से कैसे मैप किया जाता है
प्लैटफ़ॉर्म, मेनिफ़ेस्ट बजट की स्थितियों को रनटाइम प्रोसेस की स्थितियों से मैप करता है. यह मैपिंग, RunningAppProcessInfo.importance के आधार पर की जाती है. इसे ऐक्टिविटी मैनेजर, प्रोसेस के अपने चालू कॉम्पोनेंट और अन्य ऐप्लिकेशन से आने वाली सेवा आबद्धकारी या कॉन्टेंट देने वाले की क्वेरी, दोनों से कंप्यूट करता है:
foreground: उपयोगकर्ता के साथ होने वाले ऐक्टिव और विज़िबल इंटरैक्शन. जैसे, फिर से शुरू की गई गतिविधि को होस्ट करना, स्क्रीन बंद होने पर टॉप ऐप्लिकेशन की स्थिति को बनाए रखना या किसी ऐसी सेवा या कॉन्टेंट देने वाले को होस्ट करना जो टॉप फ़ोरग्राउंड ऐप्लिकेशन से ऐक्टिव तौर पर जुड़ा हो या उससे क्वेरी की गई हो.perceptible: ऐसे वर्कलोड जो उपयोगकर्ता को बिना किसी विंडो के दिखते हैं. जैसे, ऐक्टिव मीडिया प्लेबैक, रास्ते के दिशा-निर्देश, कैमरा या माइक्रोफ़ोन से कैप्चर करना, बैकग्राउंड में ऐक्टिव डाउनलोड, डेटा सिंक करने वाली फ़ोरग्राउंड सेवाएं याBIND_NOT_FOREGROUNDजैसे फ़्लैग का इस्तेमाल करके, फ़ोरग्राउंड क्लाइंट से जुड़ी सेवाएं (BIND फ़्लैग की मदद से इनहेरिटेंस को कंट्रोल करना देखें). हालांकि, प्लैटफ़ॉर्म की इंटरनल मेट्रिक, इनमें से कुछ वर्कलोड कोPROCESS_STATE_IMPORTANT_FOREGROUNDके तौर पर लेबल कर सकती हैं. हालांकि, यह इंटरनल कॉन्स्टेंट, किसी विज़िबल विंडो को नहीं दिखाता है. साथ ही, इसेperceptibleबजट के हिसाब से कंट्रोल किया जाता है.background: ऐसा काम जो उपयोगकर्ता को तुरंत नहीं दिखता. जैसे, बैकग्राउंड में होने वाले काम, अलार्म, ब्रॉडकास्ट रिसीवर या उपयोगकर्ता के किसी दूसरी गतिविधि पर जाने के बाद, पिछली गतिविधि.
यहां दी गई टेबल से पता चलता है कि रनटाइम के दौरान अहमियत के लेवल, मेनिफ़ेस्ट की स्थितियों से कैसे मैप होते हैं:
मेनिफ़ेस्ट android:state |
रनटाइम की अहमियत (RunningAppProcessInfo) |
सामान्य कॉम्पोनेंट |
|---|---|---|
foreground |
IMPORTANCE_FOREGROUNDIMPORTANCE_TOP_SLEEPING |
ऐसी ऐक्टिविटी जो फिर से शुरू की गई हो और दिख रही हो, स्क्रीन लॉक होने पर सबसे ऊपर दिखने वाला ऐप्लिकेशन, सबसे ऊपर दिखने वाले ऐप्लिकेशन से जुड़ी सेवा या कॉन्टेंट देने वाला |
perceptible |
IMPORTANCE_FOREGROUND_SERVICEIMPORTANCE_VISIBLE |
ऐक्टिव मीडिया प्लेबैक, नेविगेशन, डाउनलोड या सिंक करने वाली फ़ोरग्राउंड सेवाएं; फ़ोरग्राउंड-सेवा या विज़िबल फ़्लैग से जुड़ी सेवा |
background |
IMPORTANCE_PERCEPTIBLEIMPORTANCE_CANT_SAVE_STATEIMPORTANCE_SERVICE |
बैकग्राउंड में चलने वाले जॉब, रिसीवर, बैकग्राउंड सिंक, पिछली गतिविधि या बैकग्राउंड में चलने वाला होम ऐप्लिकेशन |
क्लाइंट बाइंडिंग, सेवा या सेवा देने वाली कंपनी की होस्ट प्रोसेस को कैसे बेहतर बनाती है, इस बारे में ज़्यादा जानकारी के लिए, सेवा बाइंडिंग और प्रोसेस की स्थितियां देखें. इसमें यह भी बताया गया है कि क्लाइंट के अनबाइंड होने या स्क्रीन से हटने के बाद, प्रोसेस वापस background या cached पर कैसे ट्रांज़िशन करती है.
जब कोई प्रोसेस, बिना किसी ऐक्टिव कॉम्पोनेंट के कैश मेमोरी वाली स्थिति (IMPORTANCE_CACHED) में चली जाती है, तो प्लैटफ़ॉर्म अपनी ऐक्टिव मेमोरी का बजट रिलीज़ कर देता है. साथ ही, कैश मेमोरी में सेव की गई प्रोसेस को वापस पाने की स्टैंडर्ड प्रोसेस के ज़रिए, अपनी मेमोरी को मैनेज करता है.
अपने ऐप्लिकेशन की प्रोसेस की स्थिति और बजट की जांच करना
डेवलपमेंट के दौरान, अपने ऐप्लिकेशन की ऐक्टिव प्रोसेस की स्थिति और अहमियत की जांच करने का एक तरीका है, एडीबी का इस्तेमाल करके ऐक्टिविटी मैनेजर से क्वेरी करना:
adb shell dumpsys activity processes <package-name>
यहां दिए गए उदाहरण में, फ़ोरग्राउंड सेवा चलाने वाले ऐप्लिकेशन के लिए, प्रोसेस रिकॉर्ड और ओओएम कंट्रोल की एंट्री दिखाई गई हैं:
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
इस आउटपुट में:
curProcState=4औरstate: cur=FGSसे पता चलता है कि प्रोसेस, फ़ोरग्राउंड सेवा की स्थिति में है. अगर आपका ऐप्लिकेशन कोई ऐसी गतिविधि होस्ट कर रहा है जो चालू है और दिख रही है, तो यहTOPके तौर पर दिखेगी. अगर यह किसी ज़रूरी फ़ोरग्राउंड कॉम्पोनेंट (जैसे, डाउनलोड या सिंक) को चला रहा है, तो यहIMPFके तौर पर दिखता है.- प्रोसेस ओओएम कंट्रोल टेबल में,
prcpसे पता चलता है कि प्रोसेस का आकलन, ध्यान देने लायक प्राथमिकता वाले टियर के तहत किया जाता है. यह टियर,perceptibleबजट से जुड़ा होता है.
फ़िलहाल लागू किए गए मेमोरी बजट और इस्तेमाल की जा रही मेमोरी की जांच करने के लिए:
adb shell dumpsys meminfo <package-name>
Android 17 QPR2 और इसके बाद के वर्शन में, इस आउटपुट में मेमोरी बजट सेक्शन शामिल होता है.
इसमें, ऐक्टिव लिमिट की सीमा, लिमिट का सोर्स (जैसे,
AndroidManifest या MemoryBudgetManager), और मौजूदा रेज़िडेंट मेमोरी दिखती है.
एक से ज़्यादा प्रोसेस वाले ऐप्लिकेशन
अगर आपका ऐप्लिकेशन अपने काम को कई प्रोसेस में बांटता है, तो <processes> के अंदर <process> टैग का इस्तेमाल करके, खास प्रोसेस के बजट कॉन्फ़िगर करें.
उदाहरण के लिए, संगीत स्ट्रीम करने वाले किसी ऐप्लिकेशन (com.example.radio) के बारे में सोचें:
- मुख्य प्रोसेस: यह दिखने वाले यूज़र इंटरफ़ेस (यूआई) और ऑडियो प्लेबैक इंजन को होस्ट करती है
(
MediaSessionServicemediaPlaybackफ़ोरग्राउंड सेवा के साथ). दिखने पर, प्रोसेस 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" />
<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>
अपने मेनिफ़ेस्ट में <processes> ब्लॉक का एलान करते समय, सिर्फ़ उन प्रोसेस के लिए <process> एंट्री की सूची बनाएं जो <memory-budget> का एलान करती हैं. अगर आपको सिर्फ़ कुछ सबप्रोसेस के लिए बजट सेट करना है और ऐप्लिकेशन के बाकी हिस्से को डिवाइस के लिए सिस्टम की तय सीमा पर छोड़ना है, तो <application> के ठीक नीचे मौजूद <memory-budget> एलिमेंट को छोड़ा जा सकता है.
किसी भी सबप्रोसेस में मेमोरी के इस्तेमाल को, प्रोसेस बजट और पैकेज बजट, दोनों में गिना जाता है. अगर कोई प्रोसेस, प्रोसेस बजट या पैकेज बजट में से किसी एक का उल्लंघन करती है, तो उसे रनटाइम के दौरान मेमोरी प्रेशर का सामना करना पड़ता है. इनमें से जो भी थ्रेशोल्ड पहले पहुंचता है उसके हिसाब से मेमोरी प्रेशर का सामना करना पड़ता है.
हाई-डेंसिटी डिसप्ले के लिए बजट बढ़ाना
कुछ ऐप्लिकेशन के लिए, मेमोरी फ़ुटप्रिंट इस बात पर निर्भर करता है कि डिसप्ले पर एक बार में कितने पिक्सल ड्रॉ करने हैं. जैसे, स्क्रीन के साइज़ के हिसाब से बिटमैप को कैश मेमोरी में सेव करने वाला फ़ोटो गैलरी ऐप्लिकेशन. ऐसे ऐप्लिकेशन के लिए, Android दो वैकल्पिक तरीके उपलब्ध कराता है. इनकी मदद से, डिसप्ले की खास बातों के हिसाब से बजट को डाइनैमिक तरीके से स्केल किया जा सकता है:
डिसप्ले डेंसिटी बकेट (
android:additionalMbPerDensity) के हिसाब से स्केल करें:mdpi(1.0x / 160 डीपीआई) के हिसाब से, डिसप्ले के डेंसिटी रेशियो के स्क्वेयर के अनुपात में मेगाबाइट जोड़ता है. ऐसा इसलिए होता है, क्योंकि 2D रास्टर ड्रॉएबल और यूज़र इंटरफ़ेस (यूआई) ऐसेट, चौड़ाई और ऊंचाई, दोनों के हिसाब से स्केल होती हैं:<!-- Baseline 180MB + 16MB multiplied by (density ratio)^2 --> <memory-budget android:maxMb="180" android:additionalMbPerDensity="16" />mdpiडिसप्ले (1.0x) पर, बजट 180 + 16 × 12 = 196 एमबी है.xxhdpiडिसप्ले (3.0x) पर, बजट 180 + 16 × 32 = 324 एमबी तक बढ़ जाता है.फ़िज़िकल डिसप्ले रिज़ॉल्यूशन के हिसाब से स्केल करना (
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 लाख पिक्सल) पर, इससे बेसलाइन बजट में ≈ 4.14 करोड़ बाइट जुड़ जाते हैं. 1440p डिसप्ले (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 को ऐक्सेस करने से पहले, पक्का करें कि डिवाइस पर Android 17 QPR2 या इसके बाद का वर्शन हो. इसके लिए, SDK_INT_FULL पर जाएं. अगर आपके ऐप्लिकेशन का minSdk 36 से कम है, तो पहले SDK_INT देखें, ताकि Android 15 (एपीआई लेवल 35) या इससे पहले के वर्शन वाले डिवाइसों पर SDK_INT_FULL को पढ़ते समय NoSuchFieldError न दिखे:
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)
}
क्वेरी के इस्तेमाल और बजट के बारे में जानकारी
// 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()
<memory-budget> मेनिफ़ेस्ट क्लॉज़, प्रोसेस के foreground, perceptible, और background स्थितियों के बीच ट्रांज़िशन होने पर, बजट को अपने-आप स्विच कर देते हैं. हालांकि, MemoryBudgetManager के ज़रिए सेट किया गया डाइनैमिक बजट, तब तक एक ही सीमा लागू करता है, जब तक कि आपका कोड उसे अपडेट या मिटा नहीं देता. अगर फ़ील्ड एक्सपेरिमेंट चलाने या राज्य के हिसाब से तय की गई सीमाओं को मैनेज करने के लिए, रनटाइम एपीआई का इस्तेमाल किया जाता है, तो लाइफ़साइकल ट्रांज़िशन के दौरान डाइनैमिक बजट को अपडेट करें या मिटाएं. उदाहरण के लिए, ProcessLifecycleOwner या गतिविधि पूरी होने की प्रोसेस के कॉलबैक का इस्तेमाल करके, जैसा कि अडैप्टिव इमेज एडिटर के उदाहरण में दिखाया गया है.
परफ़ॉर्मेंस और आइडमपोटेंसी
रनटाइम के दौरान MemoryBudgetManager के साथ काम करते समय, इन बातों का ध्यान रखें:
- Idempotency: बजट में बदलाव करने वाले सभी एपीआई, idempotent होते हैं.
clearProcessBudget()याclearPackageBudget()को बार-बार कॉल करना या डाइनैमिक बजट चालू न होने पर कॉल करना, एक सुरक्षित नो-ऑप है. इसी तरह,setProcessBudgetBytes()याsetPackageBudgetBytes()को लगातार एक ही वैल्यू पर सेट करने से, सिस्टम के ग़ैर-ज़रूरी रीकॉन्फ़िगरेशन ट्रिगर नहीं होते. - कॉल करने की फ़्रीक्वेंसी: बजट सेट करने, अपडेट करने या हटाने से, सिस्टम सर्विस को क्रॉस-प्रोसेस कॉल किया जाता है. इससे, प्रोसेस के लिए ऑपरेटिंग सिस्टम के मेमोरी कंट्रोलर को अपडेट किया जाता है. यह हल्का है, लेकिन पूरी तरह से मुफ़्त नहीं है. इन एपीआई को लाइफ़साइकल के अलग-अलग ट्रांज़िशन और टास्क के माइलस्टोन के दौरान कॉल करें. जैसे, किसी गतिविधि में शामिल होने या उससे बाहर निकलने, बैकग्राउंड में प्रोसेसिंग शुरू करने या पूरा करने या बोली के अनुरोधों को मैनेज करने के दौरान. इन्हें टाइट लूप, ऑडियो प्रोसेसिंग थ्रेड या हर फ़्रेम के रेंडर रूटीन में कॉल करने से बचें.
- क्वेरी की फ़्रीक्वेंसी: क्वेरी प्रॉपर्टी
processCurrentUsageBytesऔरpackageCurrentUsageBytes, सिस्टम सर्विस से मेमोरी के इस्तेमाल का लाइव डेटा फ़ेच करती हैं. क्वेरी तेज़ी से काम करती हैं, लेकिन उन्हें लगातार लूप में पोल करने के बजाय, ज़रूरत पड़ने पर ही शुरू किया जाना चाहिए. जैसे, टास्क ट्रांज़िशन के दौरान याOnOverBudgetListenerकॉलबैक के अंदर.
बजट से ज़्यादा खर्च होने पर, प्रेशर कॉलबैक सुनें
ऐप्लिकेशन, लिसनर को रजिस्टर कर सकते हैं, ताकि मेमोरी का इस्तेमाल तय सीमा से ज़्यादा होने पर सूचना मिल सके. इससे ऐप्लिकेशन को ऐप्लिकेशन-लेवल पर क्लीनअप करने की अनुमति मिलती है. जैसे, इन-मेमोरी बिटमैप कैश मेमोरी को मिटाना. ऐसा तब होता है, जब ऑपरेटिंग सिस्टम मेमोरी को फिर से हासिल करता है, ताकि प्रोसेस या पैकेज को उसके बजट में रखा जा सके:
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)
बजट से ज़्यादा खर्च होने पर कॉलबैक के लिए सबसे सही तरीके:
- बैकग्राउंड
Looperका इस्तेमाल करें: जब भी संभव हो,registerProcessOverBudgetListenerयाregisterPackageOverBudgetListenerको बैकग्राउंडHandlerThreadLooperपास करें. बैकग्राउंड थ्रेड पर कैश मेमोरी को खाली करने या फ़ाइल को मिटाने की प्रोसेस चलाने से, यूज़र इंटरफ़ेस (यूआई) थ्रेड पर फ़्रेम रेंडरिंग में रुकावट नहीं आती. ऐसा तब होता है, जब प्रोसेस के दौरान मेमोरी कम हो.Looper.getMainLooperको उन लिसनर के लिए रिज़र्व करें जो सिर्फ़ मुख्य थ्रेड से जुड़ी यूज़र इंटरफ़ेस (यूआई) की स्थिति को ऐक्सेस करते हैं. - बार-बार कॉलबैक मिलने की उम्मीद करें: बजट से ज़्यादा खर्च होने पर मिलने वाले कॉलबैक, एक बार मिलने वाली सूचनाएं नहीं होती हैं. जब मेमोरी का इस्तेमाल बजट से ज़्यादा हो जाता है, तो ऑपरेटिंग सिस्टम सबसे पहले कोल्ड पेजों को वापस लेता है. अगर मेमोरी का इस्तेमाल ज़्यादा बना रहता है, तो सिस्टम मेमोरी को वापस लेता रहता है और आपके लिसनर को हर सेकंड में ज़्यादा से ज़्यादा एक बार कॉल करता है. अपने कॉलबैक को आइडेंपोटेंट बनाएं. अगर आपके ऐप्लिकेशन को सिर्फ़ एक सूचना की ज़रूरत है या क्लीनअप पास के बीच ज़्यादा कूलडाउन चाहिए, तो कॉलबैक में
unregisterProcessOverBudgetListener(याunregisterPackageOverBudgetListener) को कॉल करें. इसके अलावा, कूलडाउन टाइमस्टैंप याAtomicBooleanकी मदद से, क्लीनअप को सुरक्षित रखें. - बजट को कम करने से पहले रजिस्टर करें: अगर
setProcessBudgetBytesयाsetPackageBudgetBytesको कॉल करने पर, मेमोरी का मौजूदा इस्तेमाल पहले से ही नए बजट से ज़्यादा है, तो नया बजट लागू होते ही बजट से ज़्यादा खर्च होने वाला इवेंट ट्रिगर हो जाता है. बजट सेट करने से पहले, अपने लिसनर को रजिस्टर करें, ताकि आपके ऐप्लिकेशन को बजट से ज़्यादा खर्च होने की शुरुआती सूचना मिल सके. - तेज़ी से काम करें: रिक्लेम ऑपरेशन से तुरंत राहत मिलनी चाहिए. दबाव में जटिल गणनाएं करने से परफ़ॉर्मेंस खराब हो जाती है.
- ऑब्जेक्ट या थ्रेड असाइन करने से बचें: कॉलबैक के अंदर नए ऑब्जेक्ट असाइन न करें या नए थ्रेड शुरू न करें. ऐसा करने से, ऑपरेटिंग सिस्टम तुरंत मेमोरी को वापस ले सकता है.
- ज़्यादा फ़ायदा देने वाले टारगेट पर फ़ोकस करें: बड़े बिटमैप, रेंडर बफ़र को हटाना या मेमोरी-मैप की गई फ़ाइलों को बंद करना, कई छोटे ऑब्जेक्ट को रिलीज़ करने से ज़्यादा असरदार होता है.
नेटिव एनडीके एपीआई (<android/memory_budget_manager.h>)
खास ऐप्लिकेशन, Android 17 QPR2 (एपीआई लेवल 37.2) से libandroid.so के C NDK API का इस्तेमाल कर सकते हैं.
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 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);
वॉचर को अपने मुख्य यूज़र इंटरफ़ेस (यूआई) या रेंडर थ्रेड के बजाय, बैकग्राउंड वर्कर थ्रेड के ALooper से बाइंड करें. इससे, कैश मेमोरी को खाली करने की प्रोसेस, फ़्रेम रेंडरिंग की प्रोसेस में रुकावट नहीं डालती. AMemoryBudgetManager_setProcessBudget या AMemoryBudgetManager_setPackageBudget का इस्तेमाल करके बजट को कम करने से पहले, वॉचर बनाएं. इससे आपके ऐप्लिकेशन को बजट से ज़्यादा खर्च होने वाले शुरुआती इवेंट की जानकारी मिल पाएगी.
जब तक प्रोसेस या पैकेज बजट से ज़्यादा रहता है और ऑपरेटिंग सिस्टम मेमोरी को वापस लेता रहता है, तब तक वॉचर बार-बार कॉलबैक को शुरू करता है. यह हर debounce_ms इंटरवल में ज़्यादा से ज़्यादा एक बार होता है. अपने कॉलबैक को आइडेंपोटेंट बनाएं या AMemoryBudgetManager_Watcher_destroy को कॉल करें (जिसे कॉलबैक के अंदर से कॉल करना सुरक्षित है), अगर आपके ऐप्लिकेशन को सिर्फ़ एक बार सूचना देने की ज़रूरत है.
रनटाइम एपीआई के उदाहरण
यहां दिए गए उदाहरणों से पता चलता है कि Android SDK API (Kotlin में लिखा गया) और नेटिव एनडीके एपीआई (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 एमबी की पूरी मेनिफ़ेस्ट सीमा को वापस लाया जा सके. यह बैकग्राउंड HandlerThread पर OnOverBudgetListener को भी रजिस्टर करता है. ऐसा Looper पर गैलरी बजट लागू करने से पहले किया जाता है, ताकि यूज़र इंटरफ़ेस (यूआई) थ्रेड को ब्लॉक किए बिना, कैश मेमोरी में सेव किए गए झलक वाले बिटमैप को हटाया जा सके.
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"
}
}
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;
}
// 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;
};