WebView और मेमोरी

वेबव्यू एक बेहतरीन कॉम्पोनेंट है. इसकी मदद से, अपने Android ऐप्लिकेशन में वेब कॉन्टेंट दिखाया जा सकता है. हालांकि, यह एक फ़ुल-फ़ीचर वाला ब्राउज़र इंजन (Chromium) है. इसलिए, यह बहुत ज़्यादा मेमोरी का इस्तेमाल करता है. साथ ही, इसमें मल्टी-प्रोसेस आर्किटेक्चर होता है.

तकनीकी जानकारी: मल्टी-प्रोसेस आर्किटेक्चर

Android के नए वर्शन वाले डिवाइसों पर, WebView मल्टी-प्रोसेस मॉडल का इस्तेमाल करता है. इससे सुरक्षा और स्थिरता को बेहतर बनाया जा सकता है. जब आपका ऐप्लिकेशन किसी वेबव्यू का इस्तेमाल करता है, तो मेमोरी को अलग-अलग प्रोसेस में बांटा जाता है:

  1. ब्राउज़र प्रोसेस (ऐप्लिकेशन प्रोसेस): यह आपके ऐप्लिकेशन की मुख्य प्रोसेस होती है. इसमें Java WebView ऑब्जेक्ट और Chromium इंजन का "browser" हिस्सा शामिल होता है. यह प्रोसेस, यूज़र इंटरफ़ेस (यूआई), नेटवर्क अनुरोधों, और जीपीयू रेंडरिंग को मैनेज करती है. यह सीधे तौर पर Android HWUI रेंडरिंग पाइपलाइन के साथ इंटिग्रेट होती है. Chrome के उलट, WebView में अलग से कोई जीपीयू प्रोसेस नहीं होती.
  2. रेंडरर प्रोसेस: यह प्रोसेस, एचटीएमएल को पार्स करने, JavaScript को एक्ज़ीक्यूट करने, और लेआउट के लिए ज़िम्मेदार होती है. सुरक्षा के लिए, इसे सिस्टम के बाकी हिस्सों से अलग रखा जाता है. फ़िलहाल, ऐप्लिकेशन को सभी WebViews के लिए सिर्फ़ एक रेंडरर प्रोसेस मिलती है. हालांकि, कुछ खास मामलों में ऐसा नहीं होता. Chrome में अक्सर अलग-अलग साइटों के लिए अलग-अलग रेंडरर प्रोसेस का इस्तेमाल किया जाता है.

वेबव्यू आर्किटेक्चर

याददाश्त के लिए यह क्यों ज़रूरी है

dumpsys meminfo <your_package> का इस्तेमाल करने पर, आपको सिर्फ़ ब्राउज़र प्रोसेस (आपके ऐप्लिकेशन की प्रोसेस) के इस्तेमाल की गई मेमोरी दिखती है. रेंडरर प्रोसेस के लिए इस्तेमाल की गई मेमोरी को अलग से गिना जाता है.

ब्राउज़र प्रोसेस में, WebView की मेमोरी को इस तरह डिस्ट्रिब्यूट किया जाता है:

  • Java हीप: इसमें WebView Java रैपर और उससे जुड़े ऑब्जेक्ट शामिल होते हैं.
  • नेटिव हीप: इसमें Chromium ब्राउज़र इंजन के इंटरनल डेटा स्ट्रक्चर, कैश मेमोरी, और स्थिति शामिल होती है. ध्यान दें कि PartitionAlloc का इस्तेमाल करने की वजह से, हो सकता है कि कुछ WebView नेटिव ऐलोकेशन को dumpsys meminfo में "नेटिव हीप" के तौर पर न गिना जाए. इसके बजाय, हो सकता है कि वे "अन्य" या "अज्ञात" के तौर पर दिखें.
  • शेयर की गई मेमोरी: इसका इस्तेमाल, ग्राफ़िकल बफ़र और अन्य डेटा शेयर करने के लिए किया जाता है. ऐसा हो सकता है कि dumpsys meminfo ने इसे साफ़ तौर पर कैटगरी में न रखा हो.

समस्या हल करने के लिए टूल

Chrome DevTools

WebView (रेंडरर प्रोसेस) में मेमोरी का विश्लेषण करने के लिए, Chrome DevTools सबसे बेहतरीन टूल है.

  1. अपने ऐप्लिकेशन में वेबव्यू डीबग करने की सुविधा चालू करें:

    // NOTE: In production, this should be gated behind a developer setting
    // or only enabled for debuggable builds to prevent reverse engineering.
    WebView.setWebContentsDebuggingEnabled(true);
    
  2. अपने डिवाइस को यूएसबी केबल से कनेक्ट करें.

  3. अपनी होस्ट मशीन पर Chrome खोलें और chrome://inspect/#devices पर जाएं.

  4. अपना ऐप्लिकेशन ढूंढें और जांच करें पर क्लिक करें.

  5. DevTools विंडो में, Memory टैब पर जाएं. यहां से, हीप स्नैपशॉट लिए जा सकते हैं या JavaScript हीप के लिए, बंटवारे वाली टाइमलाइन रिकॉर्ड की जा सकती हैं.

dumpsys meminfo

मेमोरी के बारे में ज़्यादा जानकारी देखने के लिए, adb shell dumpsys meminfo --all <package> का इस्तेमाल करें. आउटपुट और ऑब्जेक्ट की संख्या में WebView कैटगरी ढूंढें.

रेंडरर की प्रोफ़ाइलिंग

रेंडरर अलग प्रोसेस में चलता है. इसलिए, सिर्फ़ अपने ऐप्लिकेशन की प्रोफ़ाइल बनाकर, इसके नेटिव हीप की प्रोफ़ाइल नहीं बनाई जा सकती. आपको रेंडरर प्रोसेस के PID की पहचान करनी होगी.

एक से ज़्यादा WebView चालू होने पर, सही रेंडरर PID की पहचान करने के लिए:

  1. dumpsys activity का इस्तेमाल करें:

    adb shell dumpsys activity processes <your_package_name>
    

    mConnections सेक्शन ढूंढें. आपको ConnectionRecord दिखेगा, जिसमें आपके ऐप्लिकेशन को SandboxedProcessService से लिंक करने की जानकारी होगी. उस प्रोसेस का पीआईडी, आपका रेंडरर है. उदाहरण:

    mConnections:
      - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}
    
  2. प्रोसेस के नाम देखें: रेंडरर प्रोसेस के नाम आम तौर पर com.google.android.webview:sandboxed_processX या इसी तरह के होते हैं. अगर सिर्फ़ एक ऐप्लिकेशन, वेबव्यू का इस्तेमाल कर रहा है, तो हो सकता है कि सिर्फ़ एक ही वेबव्यू हो.

पीआईडी मिल जाने के बाद, heapprofd का इस्तेमाल करके इसकी प्रोफ़ाइल बनाई जा सकती है.

WebView की मेमोरी के लिए सबसे सही तरीके

एक्सप्लिसिट डिस्ट्रक्शन

ऐप्लिकेशन से यह उम्मीद की जाती है कि वे WebView.destroy() को कॉल करके यह बताएं कि वे किसी इंस्टेंस का इस्तेमाल कब बंद कर रहे हैं.

WebView यह पक्का करने की कोशिश करता है कि इंस्टेंस को ट्रैश किया जा सके और वे अपने सभी संसाधनों को अपने-आप रिलीज़ कर सकें. हालांकि, 100% मामलों में इसकी गारंटी देना मुश्किल है. ऑटोमैटिक गार्बेज कलेक्शन की सुविधा काम करने के बावजूद, इसमें काफ़ी समय लग सकता है. इससे ऐप्लिकेशन, उम्मीद से ज़्यादा समय तक संसाधनों को अपने पास रखता है.

अगर कोई ऐप्लिकेशन सही समय पर WebView.destroy() को कॉल करता है (जैसे, Activity.onDestroy() में), तो WebView ऑब्जेक्ट के रेफ़रंस को बनाए रखने से, कोई भी अहम नेटिव रिसॉर्स लीक नहीं होगा. WebView ऑब्जेक्ट को डिस्ट्रॉय करने के बाद, ऐक्टिविटी फ़ील्ड में उसके रेफ़रंस को नल करने की कोई ज़रूरत नहीं है. ऐसा इसलिए, क्योंकि जब ऐक्टिविटी को गार्बेज कलेक्शन किया जाएगा, तब इसे साफ़ कर दिया जाएगा.

एक्सरसाइज़: वेबव्यू की मेमोरी का इस्तेमाल करना

पहली कसरत: एक से ज़्यादा प्रोसेस के फ़ुटप्रिंट देखना

  1. MemoryLab लॉन्च करें और अपने ऐप्लिकेशन की मेमोरी का बेसलाइन मेज़रमेंट लें:

    adb shell dumpsys meminfo com.android.memorylab
    

    सैंपल बेसलाइन (रैंगो): TOTAL PSS: 18915 KB

  2. लॉन्च वेबव्यू (सामान्य) पर टैप करें.

  3. WebView में, Allocate JS Memory (1000 DIVs) पर कई बार टैप करें.

  4. ऐप्लिकेशन की मेमोरी की फिर से जांच करें:

    adb shell dumpsys meminfo com.android.memorylab
    
  5. ध्यान दें कि आपके ऐप्लिकेशन की प्रोसेस में मेमोरी का इस्तेमाल, बेसलाइन की तुलना में ज़्यादा न हो! ऐसा इसलिए है, क्योंकि DOM एलिमेंट रेंडरर प्रोसेस में होते हैं.

  6. रेंडरर प्रोसेस ढूंढें:

    adb shell ps -A | grep webview | grep sandboxed
    

    आउटपुट का उदाहरण:

    u0_i9002     14227  1087    1632732 135880 do_epoll_wait       0 S com.google.android.webview:sandboxed_process0
    
  7. रेंडरर प्रोसेस की मेमोरी देखें (इसके पीआईडी का इस्तेमाल करके):

    adb shell dumpsys meminfo 14227
    
  8. रेंडरर प्रोसेस के ज़्यादा TOTAL PSS पर ध्यान दें. हमारे सैंपल रन में, कुछ ऐलोकेशन के बाद यह ~55 एमबी तक पहुंच गया. ध्यान दें कि JavaScript के लिए मेमोरी का बंटवारा (V8 इंजन मैनेज करता है) आम तौर पर dumpsys meminfo के Private Other या Unknown (mmap) सेक्शन में होता है. यह Dalvik Heap में नहीं होता.

दूसरी कसरत: Java-साइड WebView लीक

आम तौर पर, स्टैटिक फ़ील्ड या लंबे समय तक इस्तेमाल होने वाले ऐसे ऑब्जेक्ट में WebView इंस्टेंस को बनाए रखने की वजह से मेमोरी लीक होती है. ऐसा इसलिए, क्योंकि WebView ऑब्जेक्ट एक "ऐंकर" होता है, जो नेटिव संसाधनों और रेंडरर प्रोसेस को बनाए रखता है. इसलिए, इसे लीक करना बहुत महंगा होता है.

WebView लीक होने का असर

  1. MemoryLab में, Launch WebView (Java Leak) पर टैप करें.
  2. पेज लोड होने के बाद, गतिविधि अपने-आप बंद हो जाएगी. इससे बार-बार नेविगेट करने और लीक होने की स्थिति का पता चलता है.
  3. बटन पर चार बार टैप करें.
  4. अपने ऐप्लिकेशन में WebView इंस्टेंस की संख्या देखें:

    adb shell dumpsys meminfo com.android.memorylab
    

    सबसे नीचे मौजूद, ऑब्जेक्ट सेक्शन ढूंढें. आपको दिखेगा कि WebViews की संख्या बढ़कर 4 हो गई है.

    रैंगो पर लीक हुए 4 इंस्टेंस का सैंपल आउटपुट:

     Objects
               Views:       51         ViewRootImpl:        5
         AppContexts:       14           Activities:        5
              Assets:       38        AssetManagers:        0
       Local Binders:       55        Proxy Binders:       77
       Parcel memory:       41         Parcel count:       68
    Death Recipients:        3             WebViews:        4
    
  5. हीप डंप कैप्चर करें और मेमोरी लीक का पता लगाने के लिए, AHAT का इस्तेमाल करें. अगर आपके पाथ में ahat नहीं है, तो इसे Android ट्री से बनाया जा सकता है:

    # Dump heap from device
    adb shell am dumpheap com.android.memorylab /data/local/tmp/heap.hprof
    adb pull /data/local/tmp/heap.hprof
    # Run ahat using the built JAR (found in out/host/linux-x86/framework/)
    java -jar out/host/linux-x86/framework/ahat.jar -p 8888 heap.hprof
    
  6. AHAT के वेब इंटरफ़ेस (localhost:8888) में, सबसे ऊपर मौजूद मेन्यू में allocations लिंक (या sites) पर क्लिक करके, मेमोरी के कुल इस्तेमाल की जानकारी देखें.

    AHAT के लिए मेमोरी का बंटवारा

  7. android.webkit.WebView क्लास खोजें. सभी लाइव इंस्टेंस देखने के लिए, इसके इंस्टेंस की संख्या पर क्लिक करें. आपको सूची में कई इंस्टेंस दिखेंगे.

    AHAT वेबव्यू इंस्टेंस

  8. लीक हुए WebView इंस्टेंस में से किसी एक पर क्लिक करें. नीचे की ओर स्क्रोल करके, GC रूट से सैंपल पाथ सेक्शन पर जाएं. आपको दिखेगा कि इसे com.android.memorylab.WebViewActivity में मौजूद sLeakedWebViews सूची में रखा गया है.

    GC रूट का AHAT पाथ


← नेटिव | ↑ ऊपर जाएं | ऐप्लिकेशन कोड →