ऐप्लिकेशन के लिए मेमोरी बजट सेट करना

ऐप्लिकेशन के मेमोरी बजट की मदद से, ऐप्लिकेशन अपने लिए मेमोरी बजट तय कर सकते हैं. इससे सिस्टम को यह पता चलता है कि जब ऐप्लिकेशन, सेट किए गए बजट से ज़्यादा मेमोरी का इस्तेमाल करता है, तो उसे मेमोरी का इस्तेमाल कम करना है. यह खास तौर पर सिस्टम और बंडल किए गए ऐप्लिकेशन के लिए या मेमोरी की कमी वाले डिवाइसों को टारगेट करने वाले ऐप्लिकेशन के लिए फ़ायदेमंद है. ऐसे मामलों में, डेवलपर को अपने ऐप्लिकेशन के लिए ज़रूरी मेमोरी के बारे में पता होता है. साथ ही, वह यह पक्का करना चाहता है कि उसका ऐप्लिकेशन, सिस्टम के शेयर किए गए रैम संसाधनों का बहुत ज़्यादा इस्तेमाल न करे.

बजट को संतुलित रखने के लिए, मेमोरी इविक्शन और स्वैप का इस्तेमाल किया जाता है. इससे उन मेमोरी पेजों को हटाया जाता है जिनका इस्तेमाल हाल ही में नहीं किया गया था. साथ ही, ऐप्लिकेशन की मेमोरी फ़ुटप्रिंट को उसके मौजूदा वर्किंग सेट पर फ़ोकस किया जाता है. जब कोई ऐप्लिकेशन, तय किए गए बजट से ज़्यादा खर्च करता है, तो ऑपरेटिंग सिस्टम इन टारगेट को वापस ले लेता है:

  1. फ़ाइल के साथ सेव किए गए पेजों को क्लीन किया जाता है. जैसे, इस्तेमाल न किया जा रहा कोड और मैप की गई ऐसेट. ऐसा इसलिए किया जाता है, क्योंकि ज़रूरत पड़ने पर इन्हें स्टोरेज से फिर से पढ़ा जा सकता है.
  2. फ़ाइल के साथ बैक किए गए डर्टी पेज को स्टोरेज में वापस लिखा जाता है और हटा दिया जाता है.
  3. बिना सोर्स फ़ाइल वाले मेमोरी पेजों (जैसे, हीप एलोकेशन) को कंप्रेस किया जाता है और zRAM में स्वैप किया जाता है.

जब तक बजट, वर्किंग सेट से ज़्यादा नहीं होता, तब तक ऐप्लिकेशन ठीक से काम करेगा. साथ ही, यह सेट किए गए बजट से ज़्यादा मेमोरी का इस्तेमाल नहीं करेगा. ऑपरेटिंग सिस्टम, इस्तेमाल नहीं की गई मेमोरी को हटा देता है. साथ ही, स्वैप करने के लिए, इस्तेमाल नहीं किए जा रहे हीप पेजों को कंप्रेस करता है, ताकि प्रोसेस को बंद किए बिना मेमोरी का बंटवारा किया जा सके.

Android मेनिफ़ेस्ट में बजट तय करना

अपने AndroidManifest.xml में मेमोरी बजट तय करना, बजट तय करने का मुख्य और सुझाया गया तरीका है. इसके लिए किसी रनटाइम कोड की ज़रूरत नहीं होती. यह प्रोसेस शुरू होने के तुरंत बाद लागू हो जाता है. साथ ही, यह ऑपरेटिंग सिस्टम के लिए एक साफ़ तौर पर कॉन्ट्रैक्ट उपलब्ध कराता है.

बेसलाइन बजट तय करना

ज़्यादातर ऐप्लिकेशन के लिए, ऐप्लिकेशन के लिए एक बजट तय करना ही काफ़ी होता है. <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="foreground" को शामिल करने की ज़रूरत नहीं है. android:state के बिना क्लॉज़, सभी राज्यों के लिए डिफ़ॉल्ट फ़ॉलबैक के तौर पर काम करता है. जब ऐप्लिकेशन perceptible या background स्थितियों में बदलता है, तो इसके नीचे दिए गए ज़्यादा पाबंदी वाले क्लॉज़, बजट को बदल देते हैं.

एक से ज़्यादा प्रोसेस वाले ऐप्लिकेशन

अगर आपका ऐप्लिकेशन, कई प्रोसेस में काम करता है, तो <processes> टैग के अंदर <process> टैग का इस्तेमाल करके, हर प्रोसेस के लिए बजट कॉन्फ़िगर करें.

उदाहरण के लिए, संगीत स्ट्रीम करने वाले किसी ऐप्लिकेशन (com.example.radio) के बारे में सोचें:

  1. मुख्य प्रोसेस: यह प्रोसेस, दिखने वाले यूज़र इंटरफ़ेस (यूआई) और ऑडियो प्लेबैक इंजन को होस्ट करती है (MediaSessionService के साथ mediaPlayback फ़ोरग्राउंड सेवा). जब यह प्रोसेस दिखती है, तब यह 180 एमबी के फ़ोरग्राउंड बजट के तहत काम करती है. जब उपयोगकर्ता, संगीत बजते समय ऐप्लिकेशन छोड़ देता है, तो प्रोसेस perceptible स्थिति में आ जाती है. इस स्थिति में, प्लेबैक इंजन और ऑडियो बफ़र के लिए 64 एमबी का बजट काफ़ी होता है.
  2. सिंक करने की प्रोसेस (: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 और &approx; 25.9 लाख पिक्सल) पर, इससे बेसलाइन बजट में &approx; 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, रनटाइम एसडीके और एनडीके एपीआई को दूसरे विकल्प के तौर पर उपलब्ध कराता है.

रनटाइम एपीआई की मदद से:

  • मौजूदा मेमोरी के इस्तेमाल और असरदार बजट के बारे में क्वेरी करें.
  • प्रोसेस के बजट को डाइनैमिक तरीके से कम करें.
  • बजट से ज़्यादा मेमोरी इस्तेमाल करने वाले इवेंट के बारे में सुनें, ताकि ऑपरेटिंग सिस्टम के डायरेक्ट रीक्लेम को ट्रिगर करने से पहले, कैश मेमोरी को कम किया जा सके.

Kotlin API (MemoryBudgetManager)

MemoryBudgetManager सिस्टम सेवा, Android 17 QPR2 (Android 26Q4 SDK रिलीज़, एपीआई लेवल 37.2 / 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-declared ceiling
    Log.e(TAG, "Requested budget exceeds manifest or system ceiling", e)
}

// Clear the dynamic process budget to restore the manifest limit
budgetManager.clearProcessBudget()

बजट से ज़्यादा खर्च होने पर, कॉल बैक के लिए सुनें

ऐप्लिकेशन, लिसनर को रजिस्टर कर सकते हैं, ताकि मेमोरी के इस्तेमाल का बजट थ्रेशोल्ड से ज़्यादा होने पर उन्हें सूचना मिल सके. इससे ऐप्लिकेशन को, ऐप्लिकेशन-लेवल पर पहले से ही क्लीनअप करने की अनुमति मिलती है. जैसे, ऑपरेटिंग सिस्टम के डायरेक्ट रिक्लेम लेटेंसी को ट्रिगर करने से पहले, इन-मेमोरी बिटमैप कैश को मिटाना:

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 का इस्तेमाल कर सकते हैं.

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
AMemoryBudgetManager_clearProcessBudget();

मेमोरी प्रेशर इवेंट मॉनिटर करना

एनडीके, मेमोरी इवेंट को मॉनिटर करने के दो तरीके उपलब्ध कराता है:

  1. हाई-लेवल वॉचर (AMemoryBudgetManager_Watcher_create): यह ALooper पर होने वाली इवेंट को मॉनिटर करता है. इसमें अपने-आप डीबाउंसिंग की सुविधा होती है.
  2. लो-लेवल फ़ाइल डिस्क्रिप्टर: 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 के उदाहरण

यहां दिए गए उदाहरणों में, Kotlin और C++ में रनटाइम एपीआई लागू करने का तरीका बताया गया है.

Kotlin का उदाहरण: अडैप्टिव इमेज एडिटर

इस उदाहरण में, इमेज में बदलाव करने वाला एक ऐप्लिकेशन (com.example.imageeditor) दिखाया गया है. जब उपयोगकर्ता कई लेयर वाले एडिटिंग कैनवस को खोलता है, तब यह ऐप्लिकेशन डाइनैमिक तरीके से अपने मेमोरी बजट को बढ़ाता है. साथ ही, थंबनेल गैलरी व्यू पर वापस आने पर, डाइनैमिक बजट को मिटा देता है. यह कुकी, 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)
    }

    override fun onStop() {
        super.onStop()
        budgetManager.unregisterProcessOverBudgetListener(overBudgetListener)
    }

    /**
     * Called when the user enters the high-resolution editing canvas.
     */
    fun enterEditingCanvas() {
        try {
            // Dynamically set budget to 256MB for the editing canvas
            budgetManager.processBudgetBytes = 256L * 1024L * 1024L
            Log.i(TAG, "Dynamic budget applied: 256MB")
        } catch (e: IllegalArgumentException) {
            Log.e(TAG, "Could not apply dynamic budget", e)
        }
    }

    /**
     * Called when the user exits the editor back to the thumbnail gallery.
     */
    fun exitToGallery() {
        previewCache.trimToSize(8 * 1024 * 1024)
        // Clear dynamic budget; restores the baseline manifest budget
        budgetManager.clearProcessBudget()
    }

    companion object {
        private const val TAG = "ImageEditor"
    }
}

NDK C++ का उदाहरण: नेटिव 3D इंजन

इस उदाहरण में, नेटिव C++ गेम इंजन को दिखाया गया है. यह इंजन, ग्राफ़िक्स क्वालिटी के चालू लेवल के आधार पर मेमोरी बजट को मैनेज करता है. साथ ही, बजट से ज़्यादा होने पर टेक्सचर मिपमैप को अनलोड करने के लिए, AMemoryBudgetManager_Watcher_createALooper का इस्तेमाल करता है.

#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;
};