WebView की मेमोरी को मैनेज करना और उसकी परफ़ॉर्मेंस का आकलन करना

WebView आपके Android ऐप्लिकेशन में वेब कॉन्टेंट रेंडर करने के लिए, कई प्रोसेस में नेटिव कोड चलाता है. WebView के इंस्टेंस को मैनेज न करने से, मेमोरी लीक, मेमोरी खत्म होने (ओओएम) की वजह से क्रैश होने, और ऐप्लिकेशन की परफ़ॉर्मेंस खराब होने जैसी समस्याएं हो सकती हैं.

इस दस्तावेज़ में, WebView के मल्टी-प्रोसेस मेमोरी मॉडल के बारे में बताया गया है. साथ ही, मेमोरी लीक होने से रोकने के लिए, इसकी लाइफ़साइकल को सही तरीके से मैनेज करने का तरीका बताया गया है. इसके अलावा, मेमोरी से जुड़ी समस्याओं का पता लगाने के लिए, काम के वर्कफ़्लो भी बताए गए हैं.

वेबव्यू के मेमोरी आर्किटेक्चर को समझना

WebView की मेमोरी को असरदार तरीके से मैनेज करने के लिए, यह समझना ज़रूरी है कि Android, वेब कॉन्टेंट के लिए संसाधन कैसे असाइन करता है:

  • मल्टी-प्रोसेस एक्ज़ीक्यूशन: Android 8.0 (एपीआई लेवल 26) और इसके बाद के वर्शन पर, WebView कई प्रोसेस के ज़रिए, वेब कॉन्टेंट को आपके ऐप्लिकेशन के मुख्य फ़ंक्शन से अलग करता है. कम रैम वाले डिवाइसों पर, यह एक प्रोसेस पर वापस जा सकता है:

    • होस्ट (ब्राउज़र) प्रोसेस: ऐप्लिकेशन की मुख्य प्रोसेस, जहां आपका Activity और Java या Kotlin कोड चलता है.
    • आइसोलेटेड रेंडरर प्रोसेस: यह एक अलग सैंडबॉक्स प्रोसेस (SandboxedProcessService) होती है, जो एचटीएमएल और सीएसएस को पार्स करती है, JavaScript को लागू करती है, और वेब पेजों को रेंडर करती है.
  • नेटिव मेमोरी फ़ुटप्रिंट: ज़्यादातर WebView मेमोरी, नेटिव मेमोरी में असाइन की जाती है, न कि Java हीप में. इसमें रेंडर किए गए ग्राफ़िक, डीओएम ट्री, और JavaScript रनटाइम मेमोरी शामिल है. Java हीप डंप (.hprof) में सिर्फ़ एक लाइटवेट Java रैपर ऑब्जेक्ट दिखता है. इसमें वेब कॉन्टेंट के लिए इस्तेमाल की गई असल मेमोरी कैप्चर नहीं होती.

  • नेटिव मेमोरी का सिस्टम पर असर: Java हीप ऐलोकेशन, ऐप्लिकेशन की maxHeap सीमा से तय होते हैं और OutOfMemoryError के साथ तेज़ी से काम करना बंद कर देते हैं. हालांकि, नेटिव मेमोरी चुपचाप गीगाबाइट तक बढ़ सकती है. जब रिलीज़ न की गई नेटिव मेमोरी, फ़िज़िकल रैम और स्वैप स्पेस (zRAM) को भर देती है, तो Android का लो मेमोरी किलर (एलएमके), बैकग्राउंड में चल रही प्रोसेस को बंद करना शुरू कर देता है, ताकि मेमोरी को वापस पाया जा सके. इससे डिवाइस की मल्टीटास्किंग की क्षमता कम हो जाती है. इसके बाद, फ़ोरग्राउंड ऐप्लिकेशन बंद हो जाता है.

WebView की लाइफ़साइकल को मैनेज करना

मेमोरी लीक होने से रोकने के लिए, लाइफ़साइकल को सही तरीके से मैनेज करना ज़रूरी है. एक आम गलती यह है कि अपने लेआउट से WebView को हटाने या Activity को अपने-आप खत्म होने देने से, उसकी मेमोरी खाली हो जाती है.

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

वेबव्यू इंस्टेंस को क्लीन अप करना

जब आपका Activity या Fragment बंद हो जाए, तो यह पक्का करने के लिए कि वह ठीक से बंद हो और संसाधनों को रिलीज़ करे, यह तरीका अपनाएं:

  1. WebView को उसके पैरंट कंटेनर (ViewGroup) से हटाएं.
  2. लोड हो रहे पेज को रोकें और नेविगेशन इतिहास मिटाएं.
  3. destroy() पर कॉल करें.
  4. null का रेफ़रंस हटाएं.

यहां दिए गए उदाहरण में, WebView को सही तरीके से हटाने का तरीका बताया गया है:

Kotlin

override fun onDestroy() {
    myWebView?.let {
        // Remove the WebView from its parent ViewGroup.
        (it.parent as? ViewGroup)?.removeView(it)
        // Stop active loading and clear history.
        it.stopLoading()
        it.clearHistory()
        // Destroy the instance.
        it.destroy()
    }
    myWebView = null
    super.onDestroy()
}

Java

@Override
protected void onDestroy() {
    if (myWebView != null) {
        // Remove the WebView from its parent ViewGroup.
        if (myWebView.getParent() instanceof ViewGroup) {
            ((ViewGroup) myWebView.getParent()).removeView(myWebView);
        }
        // Stop active loading and clear history.
        myWebView.stopLoading();
        myWebView.clearHistory();
        // Destroy the instance.
        myWebView.destroy();
    }
    myWebView = null;
    super.onDestroy();
}

नष्ट होने के बाद की मेमोरी को समझना

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

ऐसा होना सामान्य है. नेटिव रनटाइम कैश, शेयर की गई लाइब्रेरी, और ऐलोकेट किए गए मेमोरी पेज, प्रोसेस में तब तक बने रहते हैं, जब तक ऑपरेटिंग सिस्टम उन्हें वापस नहीं ले लेता या प्रोसेस खत्म नहीं हो जाती. destroy() का मुख्य मकसद, वेब की मदद से चलने वाली स्क्रीन पर नेविगेट करने के दौरान, उपयोगकर्ताओं को Activity मेमोरी लीक होने से बचाना है.

डीबग करने से जुड़ी मुख्य मेट्रिक

WebView की मेमोरी के इस्तेमाल का विश्लेषण करते समय, इन मेट्रिक पर फ़ोकस करें:

  • रेज़िडेंट सेट साइज़ (आरएसएस): प्रोसेस में मैप की गई कुल फ़िज़िकल रैम. इसमें शेयर किया गया कोड और लाइब्रेरी शामिल हैं. Android Studio प्रोफ़ाइलर में इसे कुल के तौर पर लेबल किया गया है.

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

  • प्राइवेट मेमोरी फ़ुटप्रिंट (पीएमएफ़): बिना सोर्स फ़ाइल वाली आरएसएस मेमोरी और स्वैप मेमोरी (zRAM) का योग. पीएमएफ़ से पता चलता है कि आपके ऐप्लिकेशन ने सिस्टम की कितनी मेमोरी का इस्तेमाल किया है. इस मेमोरी को खाली नहीं किया जा सकता.

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

  • लाइव ऑब्जेक्ट की संख्या (WebViews, Activities, Views): मेमोरी में मौजूद, ऐक्टिव यूज़र इंटरफ़ेस (यूआई), कॉन्टेक्स्ट, और WebView इंस्टेंस की संख्या. इन्हें ट्रैक करने से पता चलता है कि मेमोरी के इस्तेमाल में बढ़ोतरी, Java के रेफ़रंस को बनाए रखने की वजह से हुई है या सिर्फ़ नेटिव ऐलोकेशन की वजह से.

  • प्राइवेट अदर और नेटिव हीप: dumpsys meminfo में, नेटिव C/C++ ऐलोकेशन और कस्टम मेमोरी मैपिंग (जैसे, Chromium PartitionAlloc या एम्बेड किए गए JavaScript रनटाइम हीप), Java हीप के बजाय नेटिव हीप और प्राइवेट अदर में दिखते हैं.

प्रोसेस मेमोरी काउंटर और उनकी कैटगरी के बारे में ज़्यादा जानने के लिए, प्रोसेस मेमोरी की शब्दावली देखें.

गड़बड़ी की जांच करने के लिए काम के वर्कफ़्लो

WebView कई प्रोसेस में काम करता है और नेटिव मेमोरी को ऐलोकेट करता है. इसलिए, इसके फ़ुटप्रिंट की जांच करने के लिए, इन टूल और तकनीकों का इस्तेमाल करें:

प्रोफ़ाइलिंग और डाइग्नोस्टिक टूल

मेमोरी के बंटवारे की जांच करने और लीक का पता लगाने के लिए, इन टूल का इस्तेमाल करें:

  • Android Studio मेमोरी प्रोफ़ाइलर: मेमोरी प्रोफ़ाइलर का इस्तेमाल करके, नेटिव ऐलोकेशन को विज़ुअलाइज़ करें, समय के साथ मेमोरी की कैटगरी ट्रैक करें, और स्क्रीन ट्रांज़िशन के दौरान Activity लीक का पता लगाएं.

  • Perfetto की मदद से मेमोरी को ट्रैक करना: Perfetto का इस्तेमाल करके, सिस्टम-लेवल के मेमोरी काउंटर (जैसे, आरएसएस और बिना सोर्स फ़ाइल वाली आरएसएस मेमोरी) को रिकॉर्ड करें. इससे मेमोरी के इस्तेमाल में होने वाली बढ़ोतरी को मॉनिटर किया जा सकता है. ध्यान दें कि WebView नेटिव इंजन के ऐलोकेशन, Perfetto के हीप प्रोफ़ाइलिंग टूल में कॉलस्टैक जनरेट नहीं करते. वेब कॉन्टेंट में JavaScript हीप स्नैपशॉट और डीओएम ऐलोकेशन की जांच करने के लिए, Chrome DevTools का इस्तेमाल करें.

लाइव ऑब्जेक्ट की संख्या की जांच करना

यह पता लगाने के लिए कि मेमोरी के इस्तेमाल में बढ़ोतरी, सेव किए गए Java फ़्रेमवर्क ऑब्जेक्ट (जैसे, यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट) या नेटिव ऐलोकेशन की वजह से हुई है या नहीं, dumpsys meminfo के Objects सेक्शन की जांच करें:

adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"

आउटपुट में लाइव ऑब्जेक्ट की संख्या दिखती है:

 Objects
               Views:     142         ViewRootImpl:        1
         AppContexts:       3           Activities:        1
              Assets:      12        AssetManagers:        0
       Local Binders:      32        Proxy Binders:       45
       Parcel memory:      15         Parcel count:       30
    Death Recipients:       2             WebViews:        1

इस सेक्शन में, ऐक्टिव फ़्रेमवर्क ऑब्जेक्ट, आईपीसी हैंडल, और पार्सल ऐलोकेशन की संख्या दिखती है. WebView की जांच के लिए, मुख्य रूप से Activities और WebViews पर फ़ोकस करें.

टारगेट किए गए उपयोगकर्ता इंटरैक्शन (जैसे, वेब स्क्रीन को खोलना और बंद करना) को बार-बार करें और संख्या की तुलना करें:

  • इंस्टेंस लीक: अगर हर नेविगेशन पर WebViews या Activities की संख्या बढ़ती है और बेसलाइन पर वापस नहीं आती है, तो इसका मतलब है कि आपका ऐप्लिकेशन, Java WebView इंस्टेंस या होस्ट Activity को लीक कर रहा है. उदाहरण के लिए, ViewGroup.removeView() के मौजूद न होने या लिसनर के रेफ़रंस को बनाए रखने की वजह से ऐसा हो सकता है. लीक हुआ Activity, अपने पूरे व्यू ट्री और डिकोड किए गए इमेज रिसॉर्स को मेमोरी में पिन कर देता है. इस वजह से, बार-बार विज़िट करने पर Java हीप तेज़ी से खत्म हो जाएगा और OutOfMemoryError क्रैश हो जाएगा.

  • नेटिव या डीओएम लीक: अगर WebViews और Activities में कोई बदलाव नहीं होता है, लेकिन प्रोसेस आरएसएस और प्राइवेट अदर की कुल संख्या बढ़ती रहती है, तो लीक की शुरुआत रिलीज़ नहीं किए गए नेटिव रिसॉर्स, डीओएम एलिमेंट या JavaScript इंजन बाइंडिंग से होती है. ये ऐलोकेशन, नेटिव मेमोरी में मौजूद होते हैं और एआरटी गार्बेज कलेक्टर को बायपास करते हैं. इसलिए, ये Java लीक का पता लगाने वाले स्टैंडर्ड टूल को नहीं दिखते. साथ ही, ये तब तक इकट्ठा होते रहते हैं, जब तक ऑपरेटिंग सिस्टम ऐप्लिकेशन को बंद नहीं कर देता.

सीएलआई का इस्तेमाल करके, आइसोलेट की गई रेंडरर प्रोसेस की प्रोफ़ाइल बनाना

अपने ऐप्लिकेशन के पैकेज के नाम के साथ dumpsys meminfo चलाने पर, सिर्फ़ मुख्य होस्ट प्रोसेस के लिए मेमोरी आउटपुट होती है. आइसोलेटेड रेंडरर प्रोसेस की जांच करने के लिए, जहां वेब पेज रेंडर किए जाते हैं:

  1. आइसोलेटेड रेंडरर सर्विस का प्रोसेस आईडी (पीआईडी) ढूंढें:

    adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"

    आउटपुट में, अलग की गई प्रोसेस का रिकॉर्ड और उसका पीआईडी RENDERER_PID (उदाहरण के लिए, 22155) दिखता है:

    Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}
    
  2. पीआईडी का इस्तेमाल करके, रेंडरर प्रोसेस की मेमोरी के ब्रेकडाउन की जांच करें:

    adb shell dumpsys meminfo <var>RENDERER_PID</var>
  3. ब्राउज़र-साइड फ़ुटप्रिंट का आकलन करने के लिए, होस्ट ऐप्लिकेशन की प्रोसेस की जांच करें:

    adb shell dumpsys meminfo <var>PACKAGE_NAME</var>

मेमोरी मैप और ऐलोकेशन की जांच करना

यह देखने के लिए कि कौनसे नेटिव सबसिस्टम या ऐलोकेटर, एनॉनिमस मेमोरी का इस्तेमाल करते हैं, प्रोसेस मेमोरी मैप की जांच करें:

adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"

यहां दी गई टेबल में, आम तौर पर इस्तेमाल होने वाले अज्ञात मेमोरी टैग और मेमोरी के इस्तेमाल में बढ़ोतरी के साथ उनकी प्रासंगिकता की सूची दी गई है:

यादें टैग करना सबसिस्टम ऐप्लिकेशन और वेब कॉन्टेंट के लिए काम का होना मेमोरी बढ़ने की आम वजह क्या है?
[anon:partition_alloc] Chromium PartitionAlloc WebView में डीओएम ट्री, रेंडरिंग बफ़र, V8 JavaScript हीप, और WebAssembly को चलाने के लिए मेमोरी का बंटवारा. हां (ज़्यादा): हैवी वेब पेज, मीडिया-रिच डीओएम लोड करने या खारिज किए गए WebView इंस्टेंस पर destroy() को कॉल न करने से, इस टैग की संख्या सीधे तौर पर बढ़ जाती है.
[anon:scudo...] या [anon:libc_malloc] Android नेटिव हीप ऐलोकेटर (Scudo / jemalloc) NDK लाइब्रेरी, जेएनआई ब्रिज, और नेटिव ग्राफ़िक्स पाइपलाइन के ज़रिए इस्तेमाल किए जाने वाले सामान्य C/C++ नेटिव ऐलोकेशन. हां (सामान्य से ज़्यादा): नेटिव जेएनआई रैपर या तीसरे पक्ष की C++ डिपेंडेंसी, नेविगेशन के दौरान रिलीज़ नहीं किए गए ऐलोकेशन को बनाए रखती हैं. इस वजह से, मेमोरी लीक की समस्या बढ़ जाती है.
[anon:...] (उदाहरण के लिए, [anon:quickjs_heap...]) कस्टम स्क्रिप्टिंग या नेटिव रनटाइम एम्बेड किए गए JavaScript इंजन, कस्टम WebAssembly रनटाइम या कस्टम नेटिव बफ़र पूल. हां (कॉन्टेक्स्ट पर निर्भर करता है): यह हाइब्रिड ऐप्लिकेशन में आम तौर पर होता है. ये ऐप्लिकेशन, नेटिव व्यू के साथ-साथ स्क्रिप्टिंग इंजन को भी एक्ज़ीक्यूट करते हैं और रनटाइम बाइंडिंग को साफ़ नहीं कर पाते.

इन-ऐप्लिकेशन मेमोरी एपीआई की सीमाएं

इन-ऐप्लिकेशन मेमोरी एपीआई (जैसे कि Debug.getMemoryInfo या ActivityManager.getProcessMemoryInfo), सिर्फ़ कॉलिंग प्रोसेस को मेज़र करते हैं. मल्टी-प्रोसेस मोड में, ये एपीआई अलग-अलग रेंडरर प्रोसेस से इस्तेमाल की गई मेमोरी को कैप्चर नहीं कर सकते. कुल मेमोरी का सटीक आकलन करने के लिए, dumpsys meminfo, Perfetto या Android Studio Profiler जैसे सिस्टम टूल का इस्तेमाल करें.

हाइब्रिड ऐप्लिकेशन में ज़्यादा मेमोरी इस्तेमाल होने की समस्या को ठीक करना

बार-बार होने वाले WebView इंटरैक्शन (जैसे, वेब लिंक खोलना या वेब-पावर्ड फ़ीड पर नेविगेट करना) के दौरान, मेमोरी के अचानक बढ़ने की समस्या का पता लगाने के लिए, यहां दिए गए ट्राइएज वर्कफ़्लो का इस्तेमाल करें. इससे यह पता लगाया जा सकता है कि लीक, Java लेयर से हो रहा है या नेटिव इंजन से:

  1. लीक के टाइप (Java बनाम नेटिव) को अलग करें: उपयोगकर्ता के बार-बार ट्रांज़िशन करने (जैसे, वेब लेखों को खोलना और बंद करना या फ़ीड को स्वाइप करना) से पहले और बाद में, dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects" चलाएं.

    • ऑब्ज़र्वेशन: अगर Activities और WebViews की संख्या स्थिर रहती है (उदाहरण के लिए, एक या दो ऐक्टिव इंस्टेंस), तो इसका मतलब है कि ऐप्लिकेशन, Activity कॉन्टेक्स्ट या Java WebView इंस्टेंस लीक नहीं कर रहा है.
  2. अलग-अलग इंटरैक्शन के दौरान मेमोरी डेल्टा को मेज़र करना (टाइम-सीरीज़ ट्रैकिंग): उपयोगकर्ता के अलग-अलग इंटरैक्शन के दौरान dumpsys meminfo स्नैपशॉट कैप्चर करें, ताकि हर ट्रांज़िशन के लिए ऐलोकेशन रेट का हिसाब लगाया जा सके:

    • ऑब्ज़र्वेशन: Java हीप, कैप और हेल्दी रहता है (इस्तेमाल के दौरान बढ़ता है और गार्बेज कलेक्शन के बाद कम होता है). हालांकि, प्राइवेट अदर और नेटिव हीप, हर ट्रांज़िशन के हिसाब से कई मेगाबाइट तक लगातार बढ़ते हैं. इससे पता चलता है कि लीक पूरी तरह से एआरटी रनटाइम के बाहर नेटिव मेमोरी में है. स्टैंडर्ड Java हीप डंप (.hprof) में कोई समस्या नहीं दिखेगी.
  3. अनाम मेमोरी मैप की जांच करना: एडीबी का इस्तेमाल करके, प्रोसेस मेमोरी मैप की जांच करें (मेमोरी मैप और ऐलोकेशन की जांच करना देखें):

    adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"
    • ऑब्ज़र्वेशन: मेमोरी का ज़्यादा इस्तेमाल, [anon:partition_alloc] या एम्बेड किए गए स्क्रिप्टिंग इंजन हीप में होता है. साथ ही, जेएनआई ग्लोबल रेफ़रंस में भी मेमोरी का इस्तेमाल धीरे-धीरे बढ़ता है. इससे पता चलता है कि Java व्यू को बदल दिया गया था, लेकिन नेटिव पेज ऑब्जेक्ट या JavaScript बाइंडिंग को रिलीज़ नहीं किया गया था.
  4. समस्या को ठीक करना:

    • पक्का करें कि रीसाइकल या खारिज की गई हर WebView, ऐक्टिव स्क्रिप्ट (stopLoading()) को साफ़ तौर पर रोकती हो, इतिहास मिटाती हो, और destroy() को कॉल करती हो.
    • खारिज किए गए व्यू से जुड़े कस्टम JavaScript ब्रिज कॉलबैक या जेएनआई ग्लोबल रेफ़रंस को हटाना.
    • पुष्टि करें कि नेविगेशन ट्रांज़िशन के बाद, Private Other और प्रोसेस आरएसएस स्थिर हो जाएं.

अन्य संसाधन

मेमोरी और WebView की परफ़ॉर्मेंस को डीबग और प्रोफ़ाइल करने के बारे में ज़्यादा जानने के लिए, ये संसाधन देखें: