बैकग्राउंड में होने वाले काम को आसान प्रोसेस के साथ जारी रखना

कई ऐप्लिकेशन, यूज़र इंटरफ़ेस (यूआई) न दिखने पर भी ज़रूरी काम करते हैं. जैसे, बड़े ऐसेट पैक डाउनलोड करना, लोकल डेटाबेस को सिंक करना, ऑडियो स्ट्रीम करना, कसरत रिकॉर्ड करना या कनेक्ट किए गए डिवाइस का सेशन बनाए रखना. जब यह बैकग्राउंड प्रोसेस, आपके फ़ोरग्राउंड यूज़र इंटरफ़ेस (यूआई) और ग्राफ़िक्स इंजन की तरह ही ओएस प्रोसेस में चलती है, तो पूरी प्रोसेस ज़्यादा अहम लेवल (0 और 250 के बीच oom_score_adj) पर रहती है. साथ ही, यूज़र इंटरफ़ेस (यूआई) को असाइन की गई पूरी मेमोरी भी इसी लेवल पर रहती है.

इस पेज पर बताया गया है कि इससे आपके ऐप्लिकेशन के साथ-साथ डिवाइस के बाकी हिस्सों को कैसे नुकसान पहुंचता है. साथ ही, बैकग्राउंड में होने वाले काम को android:process का इस्तेमाल करके, कम मेमोरी इस्तेमाल करने वाली प्रोसेस में कैसे ट्रांसफ़र किया जा सकता है. इसके अलावा, adb shell और Perfetto का इस्तेमाल करके, मेमोरी की बचत की पुष्टि करने का तरीका भी बताया गया है.

सिंगल-प्रोसेस वर्क, यूज़र इंटरफ़ेस (यूआई) मेमोरी को क्यों बनाए रखता है

Android, मेमोरी और प्रोसेस को खत्म करने की प्रोसेस को ओएस प्रोसेस (pid) के लेवल पर मैनेज करता है, न कि अलग-अलग कॉम्पोनेंट (Activity, Service या BroadcastReceiver) के लेवल पर. Activity Manager (OomAdjuster) हर प्रोसेस को एक oom_score_adj वैल्यू असाइन करता है. इससे उस प्रोसेस में सबसे अहम ऐक्टिव कॉम्पोनेंट या इनकमिंग बाइंडिंग का पता चलता है:

  • जब उपयोगकर्ता आपके Activity से इंटरैक्ट करता है, तो प्रोसेस TOP स्टेट (oom_score_adj = 0) में चलती है.
  • जब उपयोगकर्ता ऐप्लिकेशन से बाहर निकल जाता है और आपके ऐप्लिकेशन का कोई भी कॉम्पोनेंट बैकग्राउंड में चालू नहीं होता है, तो प्रोसेस PREV (oom_score_adj = 700) या CACHED (oom_score_adj >= 900) बकेट में चली जाती है. कैश मेमोरी में सेव की गई बकेट में, प्लैटफ़ॉर्म cgroup v2 फ़्रीज़र की मदद से प्रोसेस को फ़्रीज़ करता है. साथ ही, CachedAppOptimizer की मदद से इसकी मेमोरी को कंपैक्ट करता है. इसके अलावा, मेमोरी कम होने पर lmkd को प्रोसेस को पहले खत्म करने की अनुमति देता है.
  • हालांकि, अगर वही प्रोसेस किसी फ़ोरग्राउंड सेवा को भी चलाती है (जैसे, oom_score_adj = 200 पर dataSync, mediaPlayback, connectedDevice या location टाइप) या किसी ऐसी सेवा को होस्ट करती है जिससे सिस्टम या कोई अन्य ज़रूरी क्लाइंट जुड़ा हुआ है, तो Activity के बंद होने के बाद भी पूरी प्रोसेस को प्राथमिकता मिलती रहती है.
सिंगल-प्रोसेस को-लोकेशन की तुलना, जिसमें यूज़र इंटरफ़ेस (यूआई) मेमोरी को oom_score_adj 200 पर पिन किया गया है. वहीं, स्प्लिट-प्रोसेस आइसोलेशन में सिर्फ़ वर्कर को oom_score_adj 200 पर रखा गया है. साथ ही, यूज़र इंटरफ़ेस (यूआई) प्रोसेस को कैश मेमोरी में सेव किया गया है.
पहली इमेज. बैकग्राउंड में काम करने के दौरान, सिंगल-प्रोसेस को-लोकेशन की तुलना, स्प्लिट-प्रोसेस आइसोलेशन से की गई है.

जब कोई हैवी यूज़र इंटरफ़ेस (यूआई) प्रोसेस oom_score_adj = 200 पर बनी रहती है, तो मेमोरी की कई बड़ी कैटगरी मौजूद रहती हैं:

  • रुकी हुई Activity और व्यू हैरारकी (व्यू और व्यू ग्रुप के लेआउट का क्रम): जब तक Activity को साफ़ तौर पर बंद नहीं किया जाता, तब तक इसके Window, व्यू ट्री, लेआउट स्ट्रक्चर, और व्यू स्टेट को Java हीप पर ऐक्सेस किया जा सकता है. भले ही, उपयोगकर्ता किसी दूसरे ऐप्लिकेशन का इस्तेमाल कर रहा हो.
  • नेटिव ग्राफ़िक्स कॉन्टेक्स्ट और जीपीयू ऐलोकेशन: स्टैंडर्ड HardwareRenderer अपने जीपीयू कॉन्टेक्स्ट और कैश मेमोरी को सेव रखता है. वहीं, बंद किया गया Activity ऐसी प्रोसेस में रहता है जिसे कैश मेमोरी में सेव नहीं किया जाता. ऐसे ऐप्लिकेशन और गेम इंजन जो अपना Vulkan (VkDevice, VkSwapchainKHR, डिस्क्रिप्टर पूल, पाइपलाइन कैश मेमोरी) या OpenGL ES (EGLContext, टेक्सचर, वर्टेक्स बफ़र) कॉन्टेक्स्ट बनाते हैं वे अपने ड्राइवर स्ट्रक्चर, शेडर प्रोग्राम, और ग्राफ़िक बफ़र को भी बनाए रखते हैं. हालांकि, ऐसा तब तक होता है, जब तक कि इंजन, बंद होने पर ग्राफ़िक डिवाइस को साफ़ तौर पर बंद न कर दे.
  • हीप फ़्रैगमेंटेशन और रनटाइम ओवरहेड: भले ही कोई ऐप्लिकेशन onTrimMemory में कैश मेमोरी रिलीज़ करता हो, लेकिन आंशिक रूप से भरे हुए नेटिव ऐलोकेटर पेज, डर्टी Zygote कॉपी-ऑन-राइट पेज, लोड की गई शेयर की गई लाइब्रेरी (.so), और रनटाइम मेटाडेटा, प्रोसेस की निजी मेमोरी में मौजूद रहते हैं. प्रोसेस, कैश मेमोरी में सेव किए गए थ्रेशोल्ड (oom_score_adj < 900) से ज़्यादा है. इसलिए, प्लैटफ़ॉर्म इसे फ़्रीज़ नहीं करता या कैश मेमोरी में सेव किए गए ऐप्लिकेशन को कंपैक्ट नहीं करता. सीमित सेवाओं पर इस असर को करीब से देखने के लिए, ज़्यादा मेमोरी वाले यूज़र इंटरफ़ेस (यूआई) के साथ सीमित सेवा को एक ही जगह पर रखना लेख पढ़ें.

बैकग्राउंड में चल रही कम मेमोरी वाली प्रोसेस, आपके ऐप्लिकेशन को कैसे सुरक्षित रखती है

बैकग्राउंड में चलने की स्थिति में, फ़ोरग्राउंड के साइज़ की मेमोरी को होल्ड करने से, बैकग्राउंड में चल रहे आपके काम पर असर पड़ सकता है:

  • बैकग्राउंड में मेमोरी इस्तेमाल करने की सीमाएं, फ़ोरग्राउंड में मेमोरी इस्तेमाल करने की सीमाओं से ज़्यादा सख्त होती हैं. Android 17 और इसके बाद के वर्शन में, मेमोरी लिमिटर आपके ऐप्लिकेशन पर मेमोरी की न दिखने वाली छोटी सीमा लागू करता है. ऐसा तब होता है, जब ऐप्लिकेशन की कोई भी प्रोसेस, यूज़र इंटरफ़ेस (यूआई) न दिखा रही हो. इसमें फ़ोरग्राउंड सेवा के चलने के दौरान भी यूज़र इंटरफ़ेस (यूआई) न दिखना शामिल है. यह सीमा, आपके ऐप्लिकेशन की सभी प्रोसेस को एक साथ कवर करती है. अगर फ़ोरग्राउंड सेवा को चलाने वाली प्रोसेस में अब भी यूज़र इंटरफ़ेस (यूआई) और ग्राफ़िक्स मेमोरी मौजूद है, तो उस मेमोरी को कम सीमा के हिसाब से गिना जाता है. जब आपका ऐप्लिकेशन तय सीमा से ज़्यादा मेमोरी का इस्तेमाल करता है, तो कर्नल उसके पेजों को वापस लेते समय ऐप्लिकेशन की परफ़ॉर्मेंस को धीमा कर देता है. इससे डाउनलोड, सिंक या वीडियो चलाने की प्रोसेस धीमी हो जाती है. अगर स्वैप मेमोरी खत्म होने के बाद भी आपका ऐप्लिकेशन मेमोरी का इस्तेमाल करता रहता है, तो सिस्टम उसे बंद कर देता है. यह देखने के लिए कि आपके ऐप्लिकेशन पर असर पड़ा है या नहीं, ऐप्लिकेशन के लिए मेमोरी की सीमाएं देखें.
  • मेमोरी का दबाव होने पर, बड़ी प्रोसेस को पहले बंद कर दिया जाता है. जब lmkd को मेमोरी खाली करनी होती है और उसे अहमियत वाली बकेट (ro.lmk.kill_heaviest_task) में सबसे ज़्यादा मेमोरी इस्तेमाल करने वाली प्रोसेस को चुनने के लिए कॉन्फ़िगर किया जाता है, तो फ़ोरग्राउंड सर्विस की ऐसी प्रोसेस को पहले बंद कर दिया जाता है जो यूज़र इंटरफ़ेस (यूआई) की सैकड़ों एमबी मेमोरी का इस्तेमाल करती है. इसके बाद, उसी अहमियत वाली छोटी प्रोसेस को बंद किया जाता है. इससे बैकग्राउंड में चल रहा आपका काम रुक जाता है. kswapd और lmkd इंटरैक्शन देखें.
  • कैश मेमोरी में सेव किए गए यूज़र इंटरफ़ेस (यूआई) की स्थिति को फ़्रीज़ किया जा सकता है, कंपैक्ट किया जा सकता है, और फ़्लैश मेमोरी में वापस सेव किया जा सकता है. फ़ोरग्राउंड यूआई की स्थिति को कैश मेमोरी में सेव की गई प्रोसेस में ले जाने की अनुमति देने से, ओएस को प्रोसेस को फ़्रीज़ करने, उसकी मेमोरी को zRAM में कंपैक्ट करने, और उसके कोल्ड पेजों को फ़्लैश स्टोरेज में वापस लिखने की अनुमति मिलती है. यूज़र इंटरफ़ेस (यूआई) की स्थिति में कोई बदलाव नहीं होता, लेकिन इसकी रेज़िडेंट मेमोरी फ़ुटप्रिंट कम हो जाती है. इससे आपके बैकग्राउंड में चल रहे काम और कैश मेमोरी में सेव किए गए यूज़र इंटरफ़ेस (यूआई), दोनों को उपयोगकर्ता के वापस आने तक बने रहने का सबसे अच्छा मौका मिलता है.
  • हर प्रोसेस के लिए मेमोरी बजट तय करना आसान हो जाता है. सिर्फ़ बैकग्राउंड में काम करने वाली प्रोसेस में, काम करने का एक छोटा और अनुमानित सेट होता है. इसलिए, यूज़र इंटरफ़ेस (यूआई) को सीमित किए बिना, उसे कम मेमोरी बजट दिया जा सकता है.

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

बैकग्राउंड में होने वाले काम को एक खास प्रोसेस में अलग करें

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

प्रोसेस मेनिफ़ेस्ट में एलान होस्ट किए गए कॉम्पोनेंट बैकग्राउंड में काम करने के दौरान, उपयोगकर्ता के यूज़र इंटरफ़ेस (यूआई) से बाहर निकलने पर दिखने वाली स्थिति
मुख्य यूज़र इंटरफ़ेस (यूआई) प्रोसेस डिफ़ॉल्ट (com.example.app) Activity, यूज़र इंटरफ़ेस (यूआई) व्यू, इमेज कैश, Vulkan या OpenGL ES रेंडरर, ऑडियो, और सीन इंजन PREV और फिर CACHED (oom_score_adj >= 900) तक कम हो जाता है. फ़्रीज़ और कंपैक्ट किया जाता है. इसके बाद, फ़्लैश मेमोरी में वापस लिखा जाता है.
बैकग्राउंड में काम करने वाली प्रोसेस android:process=":bg" फ़ोरग्राउंड Service, बैकग्राउंड में ऐसेट डाउनलोड करने वाला टूल, सिंक करने वाला वर्कर या कनेक्ट किए गए डिवाइस को मैनेज करने वाला टूल यह FGS (oom_score_adj = 200) पर चलता है. इसमें सिर्फ़ वर्कर का हीप और कोड होता है. इसमें यूज़र इंटरफ़ेस (यूआई) व्यू या जीपीयू कॉन्टेक्स्ट नहीं होते.

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

AndroidManifest.xml में android:process=":bg" को सेट करके, अपने बैकग्राउंड Service को किसी निजी सबप्रोसेस को असाइन करें. शुरुआती कोलन (:) एक निजी प्रोसेस बनाता है, जो आपके ऐप्लिकेशन के यूआईडी तक सीमित होती है.

Android 17 QPR2 (एपीआई लेवल 37.2) और इसके बाद के वर्शन में, प्रोसेस को अलग-अलग करने की सुविधा को हर प्रोसेस के लिए ऐप्लिकेशन के मेमोरी बजट के साथ जोड़ें. इसके लिए, <processes> ब्लॉक का इस्तेमाल करें, ताकि ऑपरेटिंग सिस्टम, बैकग्राउंड में काम करने वाले वर्कर पर वर्किंग-सेट की सीमा को सख्ती से लागू करे. इससे फ़ोरग्राउंड यूज़र इंटरफ़ेस (यूआई) की प्रोसेस पर कोई असर नहीं पड़ेगा:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:name=".HeavyApplication"
        android:label="@string/app_name">

        <!-- App-wide budgets that cover every process, including :bg -->
        <memory-budget android:maxMb="512" />
        <memory-budget
            android:maxMb="128"
            android:state="background" />

        <processes>
            <!-- Tight budget for the headless background worker process -->
            <process android:process=":bg">
                <memory-budget android:maxMb="32" />
            </process>
        </processes>

        <activity
            android:name=".MainActivity"
            android:exported="true" />

        <service
            android:name=".AssetDownloadService"
            android:process=":bg"
            android:foregroundServiceType="dataSync"
            android:exported="false" />

    </application>
</manifest>

:bg में इस्तेमाल की गई मेमोरी, इसके अपने बजट और ऐप्लिकेशन के बजट, दोनों में गिनी जाती है. Android 17 QPR2 से पहले के वर्शन वाले डिवाइसों पर, सिस्टम <memory-budget> को अनदेखा कर देता है. हालांकि, android:process=":bg" सभी एपीआई लेवल पर काम करता है.

Application.onCreate में गेट को शुरू करना

Android, आपकी Application क्लास को इंस्टैंशिएट करता है और आपके पैकेज के लिए जनरेट की गई हर प्रोसेस में Application.onCreate को कॉल करता है. android:process=":bg" को अपनाते समय, एक आम गलती यह होती है कि भारी यूज़र इंटरफ़ेस (यूआई) टूलकिट, इमेज-लोडिंग पूल, Analytics SDK टूल या नेटिव इंजन को शुरू करने की प्रोसेस (System.loadLibrary) को बिना सुरक्षा वाले Application.onCreate तरीके में छोड़ दिया जाता है. ऐसा करने से, :bg प्रोसेस का साइज़ बढ़ जाता है. इसमें वही बेसलाइन फ़ुटप्रिंट होता है जिससे बचने की कोशिश की जा रही है.

onCreate में Application.getProcessName को देखें. यह Android 9 (एपीआई लेवल 28) और इसके बाद के वर्शन में उपलब्ध है. साथ ही, सिर्फ़ मौजूदा प्रोसेस के लिए ज़रूरी डिपेंडेंसी को शुरू करें:

Kotlin

class HeavyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val processName = Application.getProcessName()
        if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
            initBackgroundWorkerProcess()
            return
        }
        initMainUiProcess()
    }

    private fun initBackgroundWorkerProcess() {
        // Initialize only lightweight HTTP, storage, and sync primitives.
        // Avoid loading native rendering libraries or allocating UI caches.
    }

    private fun initMainUiProcess() {
        // Initialize UI frameworks, image caches, and native engines.
        System.loadLibrary("heavy_engine_jni")
    }

    companion object {
        private const val BG_PROCESS_SUFFIX = ":bg"
    }
}

Java

public final class HeavyApplication extends Application {
    private static final String BG_PROCESS_SUFFIX = ":bg";

    @Override
    public void onCreate() {
        super.onCreate();
        String processName = Application.getProcessName();
        if (processName != null && processName.endsWith(BG_PROCESS_SUFFIX)) {
            initBackgroundWorkerProcess();
            return;
        }
        initMainUiProcess();
    }

    private void initBackgroundWorkerProcess() {
        // Initialize only lightweight HTTP, storage, and sync primitives.
        // Avoid loading native rendering libraries or allocating UI caches.
    }

    private void initMainUiProcess() {
        // Initialize UI frameworks, image caches, and native engines.
        System.loadLibrary("heavy_engine_jni");
    }
}

कॉन्टेंट देने वाले के ज़रिए शुरू होने वाली लाइब्रेरी, जैसे कि Jetpack App Startup, सिर्फ़ मुख्य प्रोसेस में शुरू होती हैं. ऐसा इसलिए होता है, क्योंकि android:process के बिना एलान किया गया कॉन्टेंट देने वाला, मुख्य प्रोसेस में चलता है. अगर :bg में मौजूद कोड ऐसी किसी लाइब्रेरी पर निर्भर करता है, तो उसे initBackgroundWorkerProcess में साफ़ तौर पर शुरू करें.

अगर Unity जैसे गेम इंजन का इस्तेमाल करके गेम बनाया जा रहा है, तो बैकग्राउंड में चलने वाली सेवा को Java या Kotlin में लिखी गई किसी सामान्य Android लाइब्रेरी या प्लगिन में लागू करें. इसमें UnityPlayer जैसी इंजन क्लास का रेफ़रंस नहीं होना चाहिए. इसके बाद, :bg प्रोसेस, इंजन की नेटिव लाइब्रेरी को कभी लोड नहीं करती. पुष्टि करने के लिए, देखें कि :bg प्रोसेस के लिए adb shell cat /proc/<pid>/maps में libunity.so या libil2cpp.so की सूची न हो.

यूआई प्रोसेस को पिन किए बिना कम्यूनिकेट करना

जब उपयोगकर्ता के पास MainActivity खुला हो और AssetDownloadService, :bg में चल रहा हो, तो यूज़र इंटरफ़ेस (यूआई) को अक्सर लाइव डाउनलोड की प्रोग्रेस दिखानी पड़ती है. प्रोसेस की अहमियत, क्लाइंट से उस प्रोसेस तक जाती है जो सेवा या कॉन्टेंट देने वाले को होस्ट करती है. इसलिए, हर डिपेंडेंसी की दिशा मायने रखती है:

  • यूज़र इंटरफ़ेस (यूआई) को वर्कर से बाइंड करना सुरक्षित है. जब MainActivity, AssetDownloadService से बाइंड होता है, तो सिस्टम :bg को विज़िबल यूज़र इंटरफ़ेस (यूआई) की अहमियत के हिसाब से बढ़ाता है. जब उपयोगकर्ता ऐप्लिकेशन को बंद कर देता है, तो यूज़र इंटरफ़ेस (यूआई) प्रोसेस को कैश मेमोरी में सेव कर लिया जाता है. साथ ही, बाइंडिंग अब किसी भी चीज़ को प्राथमिकता नहीं देती.
  • यूज़र इंटरफ़ेस (यूआई) प्रोसेस पर निर्भर वर्कर सुरक्षित नहीं है. अगर :bg में मौजूद कोड, किसी ऐसी सेवा से बाइंड होता है या कॉन्टेंट देने वाले किसी ऐसे व्यक्ति या कंपनी से क्वेरी करता है जो मुख्य प्रोसेस में चलती है, तो सिस्टम मुख्य प्रोसेस को वर्कर की ज़्यादा अहमियत वाली प्रोसेस में बदल देता है. साथ ही, यूज़र इंटरफ़ेस (यूआई) की मेमोरी फिर से पिन हो जाती है. इसके बजाय, यूज़र इंटरफ़ेस (यूआई) को वर्कर के साथ कॉलबैक रजिस्टर करने दें. जैसे, Messenger या एआईडीएल कॉलबैक इंटरफ़ेस. कॉलबैक के तौर पर बाइंडर ऑब्जेक्ट को पास करने से, किसी भी प्रोसेस की अहमियत में कोई बदलाव नहीं होता.

अनबाइंड करने से पहले, कॉलबैक को अनरजिस्टर करें, ताकि सिस्टम के फ़्रीज़ होने के बाद, वर्कर कभी भी यूज़र इंटरफ़ेस (यूआई) प्रोसेस को कॉल न करे. नीचे दिया गया उदाहरण, वर्कर से सिर्फ़ तब बाइंड होता है, जब यूज़र इंटरफ़ेस (यूआई) दिखता है:

Kotlin

class MainActivity : Activity() {
    private var downloadService: IBinder? = null

    private val downloadConnection = object : ServiceConnection {
        override fun onServiceConnected(
                name: ComponentName,
                service: IBinder
        ) {
            downloadService = service
            // Register a Messenger or AIDL callback for progress updates.
        }

        override fun onServiceDisconnected(name: ComponentName) {
            // The binding stays active and reconnects if the worker restarts.
            downloadService = null
        }
    }

    override fun onStart() {
        super.onStart()
        val intent = Intent(this, AssetDownloadService::class.java)
        bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        if (downloadService != null) {
            // Unregister the progress callback here, before unbinding.
            downloadService = null
        }
        // Unbind the ServiceConnection even if onServiceConnected hasn't run
        // yet.
        unbindService(downloadConnection)
    }
}

Java

public final class MainActivity extends Activity {
    private IBinder downloadService;

    private final ServiceConnection downloadConnection =
            new ServiceConnection() {
                @Override
                public void onServiceConnected(
                        ComponentName name, IBinder service) {
                    downloadService = service;
                    // Register a Messenger or AIDL callback for progress
                    // updates.
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    // The binding stays active and reconnects if the worker
                    // restarts.
                    downloadService = null;
                }
            };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, AssetDownloadService.class);
        bindService(intent, downloadConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (downloadService != null) {
            // Unregister the progress callback here, before unbinding.
            downloadService = null;
        }
        // Unbind the ServiceConnection even if onServiceConnected hasn't run
        // yet.
        unbindService(downloadConnection);
    }
}

बाइंडिंग फ़्लैग, अलग-अलग प्रोसेस में oom_score_adj पर कैसे असर डालते हैं, इस बारे में ज़्यादा जानने के लिए, सेवा बाइंडिंग और प्रोसेस की स्थितियां देखें.

डिस्क पर शेयर की गई स्थिति को सुरक्षित तरीके से मैनेज करना

ऐसा इसलिए, क्योंकि मुख्य प्रोसेस और :bg, अलग-अलग रनटाइम और फ़ाइल डिस्क्रिप्टर के साथ अलग-अलग अड्रेस स्पेस में चलते हैं:

  • SQLite और Room: राइट-अहेड लॉगिंग (ज़्यादातर डिवाइसों पर Room में डिफ़ॉल्ट जर्नल मोड) का इस्तेमाल करें, ताकि दोनों प्रोसेस एक साथ डेटाबेस को पढ़ सकें और उसमें डेटा लिख सकें. :bg से enableMultiInstanceInvalidation पर कॉल न करें. Room, अमान्य करने की अपनी सेवा को मुख्य प्रोसेस में होस्ट करता है. इसलिए, वर्कर मुख्य प्रोसेस से जुड़ जाएगा और यूज़र इंटरफ़ेस (यूआई) की मेमोरी को वर्कर की ज़्यादा अहमियत पर बनाए रखेगा. इसके बजाय, वर्कर को यूज़र इंटरफ़ेस (यूआई) के रजिस्टर किए गए कॉलबैक के ज़रिए बदलावों की जानकारी देनी चाहिए. साथ ही, यूज़र इंटरफ़ेस (यूआई) को शुरू होने पर डेटाबेस से फिर से क्वेरी करनी चाहिए.
  • प्राथमिकताएं: SharedPreferences को अलग-अलग प्रोसेस के साथ शेयर न करें. मल्टी-प्रोसेस कोड में DataStore का इस्तेमाल करें या आईपीसी के ज़रिए साफ़ तौर पर स्टेट पास करें.
  • ऐसेट फ़ाइलें: डाउनलोड किए गए ऐसेट पैक को :bg में मौजूद स्टेजिंग फ़ाइल में लिखें. इसके बाद, :bg में fdatasync और फिर posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED) को कॉल करें (जैसा कि ओएस को मेमोरी पेज वापस करें में दिखाया गया है). इसके बाद, फ़ाइल का नाम बदलकर उसे सही जगह पर सेव करें. यूज़र इंटरफ़ेस (यूआई) को अगली बार शुरू होने और वर्कर से बाइंड होने पर नतीजा चुनने दें. इसके बजाय, यूज़र इंटरफ़ेस (यूआई) की ऐसी प्रोसेस को कॉल न करें जो फ़्रीज़ हो सकती है.

adb शेल में असर देखें

यह पुष्टि की जा सकती है कि फ़ोरग्राउंड और बैकग्राउंड प्रोसेस को अलग-अलग करने से, मेमोरी के ज़्यादा इस्तेमाल को अलग किया जा सकता है. इसके लिए, होम स्क्रीन पर जाने के बाद, Activity Manager की प्रोसेस की स्थितियों और dumpsys meminfo की जांच करें. इस सेक्शन में दिया गया सैंपल आउटपुट, MemoryLab के सैंपल के तौर पर मिले ऐप्लिकेशन से लिया गया है. यह ऐप्लिकेशन, दोनों कॉन्फ़िगरेशन में अपनी यूज़र इंटरफ़ेस (यूआई) प्रोसेस में, 64 एमबी का नेटिव ऐलोकेशन चलाता है. साथ ही, अपनी स्प्लिट फ़ोरग्राउंड सेवा को :remote प्रोसेस में होस्ट करता है.

1. प्रोसेस की स्थितियों और ओओएम स्कोर की तुलना करना

बैकग्राउंड में वर्कलोड शुरू करें. इसके बाद, होम बटन दबाएं, ताकि Activity बंद हो जाए. इसके बाद, Activity Manager से क्वेरी करें:

adb shell dumpsys activity processes com.example.app | grep -E "Proc #|oom:|state:"

सिंगल-प्रोसेस आर्किटेक्चर में, मुख्य प्रोसेस तब भी एलिवेटेड रहती है, जब उसकी Activity बंद हो जाती है. उपयोगकर्ता के ऐप्लिकेशन छोड़ने के बाद, करीब 15 सेकंड तक ऐक्टिविटी मैनेजर, फ़ोरग्राउंड सेवा को चलाने वाली प्रोसेस को oom_score_adj = 50 (fg-service-act) पर रखता है. इसके बाद, इसे 200 (fg-service) पर ले जाता है. होम बटन दबाने के 20 सेकंड बाद, यह आउटपुट कैप्चर किया गया:

    Proc #1: prcp     F/S/FGS  ---NFUATI  t: 0 13794:com.android.memorylab/u0a284 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS   lastRss=0.00 lastCachedRss=0.00

स्प्लिट-प्रोसेस आर्किटेक्चर में, सिर्फ़ वर्कर प्रोसेस oom_score_adj = 200 पर रहती है. वहीं, मुख्य यूज़र इंटरफ़ेस (यूआई) प्रोसेस PREV (oom_score_adj = 700) पर और बाद में कैश मेमोरी वाली बकेट (oom_score_adj >= 900) पर चली जाती है:

    Proc #3: prcp     F/S/FGS  ---NFUATI  t: 0 13544:com.android.memorylab:remote/u0a284 (fg-service)
        oom: max=1001 curRaw=200 setRaw=200 cur=200 set=200
        state: cur=FGS  set=FGS   lastRss=0.00 lastCachedRss=0.00
    Proc #1: prev     b/ /LAST -------TI  t: 0 13312:com.android.memorylab/u0a284 (previous)
        oom: max=1001 curRaw=700 setRaw=700 cur=700 set=700
        state: cur=LAST set=LAST  lastRss=0.00 lastCachedRss=0.00

पिछले ऐप्लिकेशन के टाइम आउट की समयसीमा खत्म होने के बाद, मुख्य प्रोसेस, कैश मेमोरी में सेव की गई बकेट (cch, oom_score_adj = 900, previous-expired) में चली जाती है. साथ ही, वह अपनी सभी प्रोसेस क्षमताओं (---------) को खो देती है. इनमें इंप्लिसिट सीपीयू समय (I) भी शामिल है. इस समय, सिस्टम इसे फ़्रीज़ कर देता है. साथ ही, वर्कर प्रोसेस में रुकावट डाले बिना, इसे किसी भी समय कंपैक्ट या वापस लाया जा सकता है.

2. अलग-अलग प्रोसेस के लिए, dumpsys meminfo की तुलना करना

यूज़र इंटरफ़ेस (यूआई) प्रोसेस के साथ, एलिवेटेड वर्कर की dumpsys meminfo खास जानकारी की तुलना करें:

adb shell dumpsys meminfo com.example.app:bg
adb shell dumpsys meminfo com.example.app

oom_score_adj = 200 पर MemoryLab वर्कर प्रोसेस के लिए, सैंपल App Summary आउटपुट:

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:      380                          13840
         Native Heap:     1548                           2832
                Code:      132                          55816
               Stack:      200                            208
            Graphics:        0                              0
       Private Other:      496
              System:     1926
             Unknown:                                    4168

           TOTAL PSS:     4682            TOTAL RSS:    76864       TOTAL SWAP PSS:      441

oom_score_adj = 700 पर MemoryLab यूज़र इंटरफ़ेस (यूआई) प्रोसेस के लिए सैंपल App Summary आउटपुट, जिसमें Private Other में 64 एमबी नेटिव ऐलोकेशन शामिल है:

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:     1316                          19816
         Native Heap:    30408                          31596
                Code:     3148                         115796
               Stack:      656                            664
            Graphics:        0                              0
       Private Other:    67044
              System:    13687
             Unknown:                                   71092

           TOTAL PSS:   116259            TOTAL RSS:   238964       TOTAL SWAP PSS:      259

वर्कर प्रोसेस को पांच एमबी से कम पीएसएस की ज़रूरत होती है, क्योंकि यह अपने ज़्यादातर कोड और रनटाइम पेजों को Zygote के साथ शेयर करती है. Vulkan या OpenGL ES की मदद से रेंडर होने वाले ऐप्लिकेशन में, यूआई प्रोसेस की Graphics लाइन में जीपीयू मेमोरी भी शामिल होती है. यह सैंपल, सॉफ़्टवेयर रेंडरिंग की सुविधा वाले Cuttlefish वर्चुअल डिवाइस पर कैप्चर किया गया था. इसलिए, दोनों प्रोसेस में Graphics, 0 है.

3. हर प्रोसेस के लिए cgroup अकाउंटिंग की जांच करना

आपके यूआईडी के तहत आने वाली हर प्रोसेस की अपनी cgroup v2 डायरेक्ट्री होती है. यह डायरेक्ट्री, आपके ऐप्लिकेशन के लिए पैरंट cgroup में मौजूद होती है. यहां दिए गए कमांड से, आपके ऐप्लिकेशन की हर प्रोसेस के लिए oom_score_adj, memory.current, और ऐप्लिकेशन-वाइड memory.high की सूची दिखती है:

adb shell '
  for PID in $(pidof com.example.app com.example.app:bg); do
    CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
    echo "pid=$PID ($(tr -d "\0" < /proc/$PID/cmdline))" \
      "adj=$(cat /proc/$PID/oom_score_adj)" \
      "memory.current=$(cat $CG/memory.current)" \
      "app.memory.high=$(cat ${CG%/*}/memory.high)"
  done
'

होम बटन दबाने के बाद, MemoryLab के लिए आउटपुट का सैंपल:

pid=15301 (com.android.memorylab) adj=700 memory.current=103260160 app.memory.high=966787072
pid=16518 (com.android.memorylab:remote) adj=200 memory.current=3608576 app.memory.high=966787072

वर्कर प्रोसेस के लिए 4 एमबी से कम मेमोरी का इस्तेमाल किया जाता है. वहीं, यूज़र इंटरफ़ेस (यूआई) प्रोसेस के लिए करीब 98 एमबी मेमोरी का इस्तेमाल किया जाता है. हर प्रोसेस का अपना memory.current होता है. हालांकि, मेमोरी लिमिटर,पैरंट cgroup पर अपनी सीमा सेट करता है. इसमें आपके ऐप्लिकेशन की सभी प्रोसेस शामिल होती हैं. दोनों लाइनों में 966,787,072 बाइट का एक ही app.memory.high दिखता है. यह 922 एमबी की वह सीमा है जो यहां दिए गए आउटपुट में नहीं दिखती.

डिवाइस पर मेमोरी लिमिटर की ओर से लागू की गई, दिखने वाली और न दिखने वाली सीमाएं देखने के लिए, यह कमांड चलाएं:

adb shell am memory-limiter status

चार जीबी रैम वाले डिवाइस से सैंपल आउटपुट, जहां न दिखने वाली सीमा, दिखने वाली सीमा की आधी है:

Memory limiter
  enabled                  limits=true              killing=true             ignored=none
  visible:                 memHigh=1843MB           memMax=2048MB            swapMax=1024MB
  not-visible:             memHigh=922MB            memMax=1024MB            swapMax=1024MB

Perfetto में असर देखना

Perfetto की मदद से, oom_score_adj और आरएसएस की टाइमलाइन को दोनों प्रोसेस में विज़ुअलाइज़ किया जा सकता है. ऐसा तब होता है, जब उपयोगकर्ता फ़ोरग्राउंड यूज़र इंटरफ़ेस (यूआई) से होम स्क्रीन पर जाता है और बैकग्राउंड में काम जारी रहता है.

1. ट्रेस रिकॉर्ड करना

15 सेकंड का ट्रेस रिकॉर्ड करें, जो प्रोसेस मेमोरी काउंटर, जीपीयू मेमोरी, और oom_score_adj में हुए बदलावों को कैप्चर करता है. ऐसा तब होता है, जब आपके ऐप्लिकेशन में बैकग्राउंड जॉब शुरू की जाती है और होम स्क्रीन पर नेविगेट किया जाता है:

adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/process-splitting.perfetto-trace <<'EOF'
buffers: {
  size_kb: 32768
  fill_policy: DISCARD
}
data_sources: {
  config {
    name: "linux.process_stats"
    process_stats_config {
      scan_all_processes_on_start: true
      proc_stats_poll_ms: 250
    }
  }
}
data_sources: {
  config {
    name: "android.gpu.memory"
  }
}
data_sources: {
  config {
    name: "linux.ftrace"
    ftrace_config {
      ftrace_events: "oom/oom_score_adj_update"
      ftrace_events: "gpu_mem/gpu_mem_total"
    }
  }
}
duration_ms: 15000
EOF
adb pull /data/misc/perfetto-traces/process-splitting.perfetto-trace

2. Perfetto यूज़र इंटरफ़ेस (यूआई) में प्रोसेस ट्रैक की जांच करना

Perfetto यूज़र इंटरफ़ेस (यूआई) में ट्रेस खोलें और com.example.app और com.example.app:bg के लिए प्रोसेस ग्रुप को बड़ा करें:

  1. दोनों प्रोसेस के लिए, oom_score_adj और mem.rss.anon ट्रैक पिन करें, ताकि वे टाइमलाइन में सबसे ऊपर, एक-दूसरे के बगल में दिखें.
  2. वह टाइमस्टैंप ढूंढें जहां उपयोगकर्ता ने होम बटन दबाया है:
    • वर्कर प्रोसेस में, oom_score_adj 200 पर रहता है और mem.rss.anon फ़्लैट और छोटा रहता है.
    • यूज़र इंटरफ़ेस (यूआई) की प्रोसेस में, oom_score_adj 0 से बढ़कर 200 हो जाता है. Activity के रुकने के बाद, यह करीब एक सेकंड तक वहीं रहता है. इसके बाद, यह बढ़कर 700 (PREV) हो जाता है. पिछले ऐप्लिकेशन का टाइम आउट होने के बाद, यह कैश मेमोरी की रेंज में चला जाता है. यहां सिस्टम, वर्कर पर असर डाले बिना अपनी मेमोरी को छोटा कर सकता है और उसे वापस पा सकता है.
यूज़र इंटरफ़ेस (यूआई) प्रोसेस और वर्कर प्रोसेस के लिए Perfetto ट्रैक. यूज़र इंटरफ़ेस (यूआई)
       प्रोसेस oom_score_adj, होम के बाद 0 से 200 और फिर 700 तक बढ़ जाता है. वहीं,
       वर्कर प्रोसेस 200 पर बनी रहती है. इसमें करीब 19 एमबी का अज्ञात आरएसएस होता है.
दूसरी इमेज. उपयोगकर्ता के होम बटन दबाने के बाद, प्रोसेस को अलग-अलग करना.

इमेज 2 में, यूज़र इंटरफ़ेस (यूआई) प्रोसेस कुछ समय के लिए 200 पर रहती है. इस दौरान, इसका Activity रुक जाता है और फिर 700 तक बढ़ जाता है. वहीं, वर्कर प्रोसेस 200 पर रहती है. इसमें करीब 19 एमबी का अज्ञात आरएसएस होता है. तुलना करने के लिए, इमेज 3 में को-लोकेटेड कॉन्फ़िगरेशन का 35 सेकंड का ट्रेस दिखाया गया है. इसमें सिंगल प्रोसेस, अपनी यूज़र इंटरफ़ेस (यूआई) मेमोरी के साथ 50 और फिर 200 पर रहती है.

Perfetto, एक ही जगह पर मौजूद प्रोसेस को ट्रैक करता है. होम पेज के बाद, oom_score_adj 0 से बढ़कर 50 हो जाता है. इसके करीब 15 सेकंड बाद, यह 200 हो जाता है. हालांकि, पहचान छिपाने वाला आरएसएस ज़्यादा रहता है.
तीसरी इमेज. उपयोगकर्ता के होम बटन दबाने के बाद, एक ही जगह पर होने वाली प्रोसेस.

इन व्यू को एक्सप्लोर करने के लिए, Perfetto के यूज़र इंटरफ़ेस (यूआई) में स्प्लिट ट्रेस और को-लोकेटेड ट्रेस खोलें.

3. PerfettoSQL की मदद से, ज़्यादा मेमोरी फ़ुटप्रिंट वाली क्वेरी करना

हर oom_score_adj लेवल पर, अपने ऐप्लिकेशन की हर प्रोसेस के लिए, औसत एनॉनिमस और फ़ाइल आरएसएस को मेज़र करने के लिए, क्वेरी (एसक्यूएल) टैब में यह PerfettoSQL क्वेरी चलाएं:

INCLUDE PERFETTO MODULE android.memory.process;

SELECT
  process_name,
  score AS oom_score_adj,
  ROUND(SUM(dur) / 1e9, 2) AS duration_sec,
  ROUND(SUM(anon_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_anon_mb,
  ROUND(SUM(file_rss * dur) / SUM(dur) / 1048576.0, 1) AS avg_file_mb
FROM memory_oom_score_with_rss_and_swap_per_process
WHERE process_name GLOB 'com.example.app*'
GROUP BY process_name, score
ORDER BY process_name, score;

इमेज 2 में स्प्लिट ट्रेस के लिए आउटपुट का सैंपल:

process_name                  oom_score_adj  duration_sec  avg_anon_mb  avg_file_mb
com.android.memorylab                     0          4.11         93.3        110.4
com.android.memorylab                   200          1.06        126.8        110.5
com.android.memorylab                   700          9.72        115.1        110.5
com.android.memorylab:remote              0          0.01         NULL         NULL
com.android.memorylab:remote            200         12.87         18.9         53.7

फ़िगर 3 में, एक ही जगह पर मौजूद ट्रेस के लिए आउटपुट का सैंपल:

process_name           oom_score_adj  duration_sec  avg_anon_mb  avg_file_mb
com.android.memorylab              0          4.10         96.0        110.4
com.android.memorylab             50         16.04        117.6        110.5
com.android.memorylab            200         14.68        116.5        110.6

स्प्लिट कॉन्फ़िगरेशन में, पहले सेकंड के बाद oom_score_adj = 200 पर सिर्फ़ 19 एमबी की एनॉनिमस मेमोरी बचती है. को-लोकेटेड कॉन्फ़िगरेशन में, फ़ोरग्राउंड सेवा के चलने तक, करीब 117 एमबी डेटा 50 और फिर 200 में सेव रहता है.


← मेमोरी पेज को ओएस पर वापस ले जाएं | ↑ ऊपर जाएं