ওয়েবভিউ এবং মেমরি

WebView একটি শক্তিশালী কম্পোনেন্ট যা আপনাকে আপনার অ্যান্ড্রয়েড অ্যাপ্লিকেশনের মধ্যে ওয়েব কন্টেন্ট প্রদর্শন করতে দেয়। তবে, যেহেতু এটি মূলত একটি পূর্ণাঙ্গ ব্রাউজার ইঞ্জিন (ক্রোমিয়াম), তাই এটি অনেক বেশি মেমরি ব্যবহার করে এবং এর একটি জটিল মাল্টি-প্রসেস আর্কিটেকচার রয়েছে।

প্রযুক্তিগত পটভূমি: মাল্টি-প্রসেস আর্কিটেকচার

আধুনিক অ্যান্ড্রয়েড-চালিত ডিভাইসগুলিতে, নিরাপত্তা ও স্থিতিশীলতা উন্নত করার জন্য ওয়েবভিউ একটি মাল্টি-প্রসেস মডেল ব্যবহার করে। যখন আপনার অ্যাপ একটি ওয়েবভিউ ব্যবহার করে, তখন মেমরি বিভিন্ন প্রসেসের মধ্যে ভাগ হয়ে যায়:

  1. ব্রাউজার প্রসেস (অ্যাপ প্রসেস) : এটি আপনার অ্যাপ্লিকেশনের প্রধান প্রসেস। এতে জাভা WebView অবজেক্ট এবং ক্রোমিয়াম ইঞ্জিনের "ব্রাউজার" অংশটি থাকে। এই প্রসেসটি UI, নেটওয়ার্ক রিকোয়েস্ট এবং GPU রেন্ডারিং পরিচালনা করে (যা সরাসরি অ্যান্ড্রয়েড HWUI রেন্ডারিং পাইপলাইনের সাথে সমন্বিত)। ক্রোমের মতো নয়, WebView-এর কোনো আলাদা GPU প্রসেস নেই।
  2. রেন্ডারার প্রসেস : এই প্রসেসটি এইচটিএমএল (HTML) পার্সিং, জাভাস্ক্রিপ্ট (JavaScript) এক্সিকিউশন এবং লেআউটের জন্য দায়ী। নিরাপত্তার কারণে এটিকে সিস্টেমের বাকি অংশ থেকে আলাদা রাখা হয়। বর্তমানে, অ্যাপগুলো সমস্ত ওয়েবভিউ (WebViews)-এর জন্য একটিমাত্র রেন্ডারার প্রসেস ব্যবহার করে (কিছু বিরল বিশেষ ক্ষেত্র ছাড়া), যা ক্রোমের থেকে ভিন্ন, কারণ ক্রোম প্রায়শই বিভিন্ন সাইটের জন্য আলাদা রেন্ডারার প্রসেস ব্যবহার করে।

ওয়েবভিউ আর্কিটেকচার

স্মৃতির জন্য এটি কেন গুরুত্বপূর্ণ

যখন আপনি dumpsys meminfo <your_package> ব্যবহার করেন, তখন আপনি কেবল ব্রাউজার প্রসেস (আপনার অ্যাপ প্রসেস) দ্বারা ব্যবহৃত মেমরি দেখতে পান। রেন্ডারার প্রসেস দ্বারা ব্যবহৃত মেমরি আলাদাভাবে হিসাব করা হয়।

ব্রাউজার প্রসেসের ভিতরে, WebView-এর মেমরি নিম্নরূপে বণ্টিত হয়:

  • জাভা হিপ : এতে WebView জাভা র‍্যাপার এবং সম্পর্কিত অবজেক্টগুলো থাকে।
  • নেটিভ হিপ : এতে ক্রোমিয়াম ব্রাউজার ইঞ্জিনের অভ্যন্তরীণ ডেটা স্ট্রাকচার, ক্যাশ এবং স্টেট থাকে। উল্লেখ্য যে, PartitionAlloc ব্যবহারের কারণে, কিছু WebView নেটিভ অ্যালোকেশন dumpsys meminfo তে "নেটিভ হিপ"-এর অধীনে গণনা নাও হতে পারে এবং এর পরিবর্তে "অন্যান্য" বা "অজানা"-এর অধীনে প্রদর্শিত হতে পারে।
  • শেয়ার্ড মেমরি : গ্রাফিক্যাল বাফার এবং অন্যান্য ডেটা শেয়ার করার জন্য ব্যবহৃত হয়। dumpsys meminfo দ্বারা এটি স্পষ্টভাবে শ্রেণীবদ্ধ নাও হতে পারে।

সমস্যা সমাধানের সরঞ্জাম

ক্রোম ডেভটুলস

WebView-এর ভেতরের মেমরি (রেন্ডারার প্রসেস) বিশ্লেষণ করার জন্য সবচেয়ে শক্তিশালী টুল হলো Chrome DevTools।

  1. আপনার অ্যাপে WebView ডিবাগিং সক্ষম করুন:

    // 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 ট্যাবে যান।

ডাম্পসিস মেমইনফো

মেমোরির বিস্তারিত বিবরণ দেখতে adb shell dumpsys meminfo --all <package> ব্যবহার করুন। আউটপুটে WebView ক্যাটাগরি এবং অবজেক্টের সংখ্যাগুলো দেখুন।

রেন্ডারারের প্রোফাইলিং

যেহেতু রেন্ডারার একটি আলাদা প্রসেসে চলে, তাই শুধু আপনার অ্যাপ প্রোফাইলিং করে আপনি এর নেটিভ হিপ প্রোফাইল করতে পারবেন না। আপনাকে অবশ্যই নির্দিষ্টভাবে রেন্ডারার প্রসেসটির পিআইডি (PID) শনাক্ত করতে হবে।

একাধিক ওয়েবভিউ সক্রিয় থাকলে সঠিক রেন্ডারার পিআইডি শনাক্ত করতে:

  1. dumpsys activity ব্যবহার করুন :

    adb shell dumpsys activity processes <your_package_name>
    

    mConnections সেকশনটি খুঁজুন। সেখানে আপনি একটি ConnectionRecord দেখতে পাবেন, যা আপনার অ্যাপকে একটি SandboxedProcessService এর সাথে যুক্ত করে। সেই প্রসেসটির PID-ই হলো আপনার রেন্ডারার। উদাহরণ:

    mConnections:
      - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}
    
  2. প্রসেসের নাম যাচাই করুন : রেন্ডারার প্রসেসগুলোর নাম সাধারণত com.google.android.webview:sandboxed_processX বা এই ধরনের হয়ে থাকে। যদি কেবল একটি অ্যাপ WebView ব্যবহার করে, তবে সম্ভবত একটিই প্রসেস থাকবে।

একবার PID পেয়ে গেলে, আপনি heapprofd ব্যবহার করে সেটির প্রোফাইল তৈরি করতে পারবেন।

ওয়েবভিউ মেমরির জন্য সর্বোত্তম অনুশীলন

সুস্পষ্ট ধ্বংস

কোনো একটি ইনস্ট্যান্সের কাজ শেষ হয়ে গেলে, অ্যাপগুলোকে WebView.destroy() কল করে তা জানাতে হয়।

যদিও WebView নিশ্চিত করার চেষ্টা করে যে ইনস্ট্যান্সগুলো স্বয়ংক্রিয়ভাবে গার্বেজ কালেকশনের মাধ্যমে তাদের সমস্ত রিসোর্স মুক্ত করে দেবে, তবুও শতভাগ ক্ষেত্রে এর নিশ্চয়তা দেওয়া কঠিন। এমনকি যখন স্বয়ংক্রিয় গার্বেজ কালেকশন কাজ করে, তখনও এতে উল্লেখযোগ্য বিলম্ব হতে পারে, যার ফলে অ্যাপটি প্রত্যাশার চেয়ে অনেক বেশি সময় ধরে রিসোর্স ধরে রাখে।

যদি কোনো অ্যাপ সঠিক সময়ে WebView.destroy() কল করে (যেমন, Activity.onDestroy() ফাংশনে), তাহলে WebView অবজেক্টটির রেফারেন্স ধরে রাখলে উল্লেখযোগ্য কোনো নেটিভ রিসোর্স লিক হবে না। Activity অবজেক্টটি ডেস্ট্রয় করার পর এর ফিল্ডগুলোতে থাকা WebView অবজেক্টের রেফারেন্সগুলো null করে দেওয়ার কোনো কঠোর প্রয়োজন নেই, কারণ Activity-টি যখন গার্বেজ কালেক্টেড হয়, তখন এই রেফারেন্সগুলোও পরিষ্কার হয়ে যায়।

অনুশীলন: ওয়েবভিউ মেমরি নিয়ে হাতে-কলমে কাজ

অনুশীলন ১: একাধিক প্রক্রিয়ার পদচিহ্ন পর্যবেক্ষণ

  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. রেন্ডারার প্রসেসটির মেমরি পরীক্ষা করুন (এর PID ব্যবহার করে):

    adb shell dumpsys meminfo 14227
    
  8. রেন্ডারার প্রসেসের উচ্চ TOTAL PSS লক্ষ্য করুন। আমাদের নমুনা রানে, কয়েকটি অ্যালোকেশনের পর এটি প্রায় ৫৫ মেগাবাইটে পৌঁছে যায়। উল্লেখ্য যে, জাভাস্ক্রিপ্ট অ্যালোকেশন (যা V8 ইঞ্জিন দ্বারা পরিচালিত হয়) সাধারণত ডালভিক হিপে না গিয়ে, dumpsys meminfo এর Private Other বা Unknown (mmap) সেকশনে যুক্ত হয়।

অনুশীলন ২: জাভা-সাইডের ওয়েবভিউ লিক

একটি সাধারণ ভুল হলো একটি WebView ইনস্ট্যান্সকে স্ট্যাটিক ফিল্ডে বা এমন কোনো দীর্ঘস্থায়ী অবজেক্টে ধরে রাখা যা মেমরি লিক করে। যেহেতু WebView অবজেক্টটি একটি ভারী 'অ্যাঙ্কর' যা নেটিভ রিসোর্স এবং সম্ভাব্য সম্পূর্ণ রেন্ডারার প্রসেসকে ধরে রাখে, তাই এটি লিক হওয়া অত্যন্ত ব্যয়বহুল।

ওয়েবভিউ লিকের প্রভাব

  1. মেমোরিল্যাব- এ, লঞ্চ ওয়েবভিউ (জাভা লিক)-এ ট্যাপ করুন।
  2. পৃষ্ঠাটি লোড হওয়ার পরে কার্যকলাপটি স্বয়ংক্রিয়ভাবে বন্ধ হয়ে যাবে (যা বারবার নেভিগেশন এবং লিকেজ জমা হওয়াকে অনুকরণ করে)।
  3. বাটনটিতে ৪ বার ট্যাপ করুন।
  4. আপনার অ্যাপে WebView ইনস্ট্যান্সের সংখ্যা পরীক্ষা করুন:

    adb shell dumpsys meminfo com.android.memorylab
    

    একদম নিচে থাকা অবজেক্টস সেকশনটি দেখুন। আপনি দেখতে পাবেন যে WebViews এর সংখ্যা বেড়ে ৪ হয়েছে।

    rango-তে নমুনা আউটপুট (৪টি লিক হওয়া ইনস্ট্যান্স):

     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 না থাকে, তবে আপনি অ্যান্ড্রয়েড ট্রি থেকে এটি বিল্ড করতে পারেন:

    # 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 ), সামগ্রিক মেমরি ব্যবহার দেখতে উপরের মেনুতে থাকা অ্যালোকেশনস লিঙ্কে (বা সাইটস ) ক্লিক করুন।

    AHAT বরাদ্দ

  7. android.webkit.WebView ক্লাসটি খুঁজুন। এর সমস্ত সক্রিয় ইনস্ট্যান্স দেখতে এর ইনস্ট্যান্স সংখ্যার উপর ক্লিক করুন। আপনি তালিকায় একাধিক ইনস্ট্যান্স দেখতে পাবেন।

    AHAT ওয়েবভিউ ইনস্ট্যান্স

  8. লিক হওয়া WebView ইনস্ট্যান্সগুলোর মধ্যে একটিতে ক্লিক করুন। নিচে স্ক্রল করে 'Sample Path from GC Root' সেকশন পর্যন্ত যান। আপনি দেখতে পাবেন যে এটি com.android.memorylab.WebViewActivity এর sLeakedWebViews লিস্ট দ্বারা ধারণ করা আছে।

    GC রুটে AHAT পাথ


← নেটিভ | ↑ উপরে | অ্যাপ কোড →