WebView والذاكرة

‫WebView هو مكوّن فعّال يتيح لك عرض محتوى الويب داخل تطبيق Android. ومع ذلك، بما أنّه محرّك متصفّح كامل الميزات (Chromium)، فإنّه يستهلك مساحة كبيرة من الذاكرة ويستخدم بنية معقّدة متعددة العمليات.

الخلفية الفنية: بنية العمليات المتعددة

على أجهزة Android الحديثة، يستخدم WebView نموذجًا متعدد العمليات لتحسين الأمان والثبات. عندما يستخدم تطبيقك WebView، يتم توزيع الذاكرة على عمليات مختلفة:

  1. عملية المتصفح (عملية التطبيق): هذه هي العملية الرئيسية لتطبيقك. يحتوي هذا الملف على عنصر Java WebView وجزء "المتصفح" من محرك Chromium. تتولّى هذه العملية إدارة واجهة المستخدم وطلبات الشبكة وعرض الرسومات على وحدة معالجة الرسومات (GPU) (يتم دمجها مباشرةً مع مسار عرض HWUI في Android). على عكس Chrome، لا تتضمّن WebView عملية منفصلة لوحدة معالجة الرسومات.
  2. عملية العرض: هذه العملية مسؤولة عن تحليل HTML وتنفيذ JavaScript وتحديد التنسيق. ويتم عزله عن بقية النظام للحفاظ على الأمان. في الوقت الحالي، تحصل التطبيقات على عملية عرض واحدة فقط لجميع عناصر WebView (باستثناء بعض الحالات الخاصة النادرة)، على عكس Chrome الذي يستخدم غالبًا عمليات عرض منفصلة للمواقع الإلكترونية المختلفة.

بنية WebView

أهمية ذلك بالنسبة إلى الذاكرة

عند استخدام dumpsys meminfo <your_package>، لا يظهر لك سوى مقدار الذاكرة التي يستخدمها عملية المتصفح (عملية تطبيقك). يتم احتساب الذاكرة التي تستخدمها عملية العارض بشكل منفصل.

داخل عملية المتصفّح، يتم توزيع ذاكرة WebView على النحو التالي:

  • ذاكرة التجميع المؤقت في Java: تحتوي على WebView برنامج تضمين Java والكائنات ذات الصلة.
  • الذاكرة المجمّعة الأصلية: تحتوي على بنى البيانات الداخلية وذاكرات التخزين المؤقت والحالة الخاصة بمحرّك متصفّح Chromium. يُرجى العِلم أنّه بسبب استخدام PartitionAlloc، قد لا يتم احتساب بعض عمليات التخصيص الأصلية في WebView ضمن "الذاكرة المؤقتة الأصلية" في dumpsys meminfo، وقد تظهر بدلاً من ذلك ضمن "غير ذلك" أو "غير معروف".
  • الذاكرة المشترَكة: تُستخدَم لمشاركة المخازن المؤقتة الرسومية والبيانات الأخرى. وقد لا يتم تصنيفها بوضوح ضمن dumpsys meminfo.

أدوات تحديد المشاكل وحلّها

أدوات مطوّري البرامج في Chrome

إنّ أدوات مطوري البرامج في Chrome هي الأداة الأقوى لتحليل الذاكرة داخل WebView (عملية العرض).

  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. وصِّل جهازك عبر منفذ USB.

  3. افتح Chrome على جهازك المضيف وانتقِل إلى chrome://inspect/#devices.

  4. ابحث عن تطبيقك وانقر على فحص.

  5. في نافذة DevTools، انتقِل إلى علامة التبويب الذاكرة لأخذ لقطات من الذاكرة المؤقتة أو تسجيل المخططات الزمنية للتخصيص في الذاكرة المؤقتة لـ JavaScript.

dumpsys meminfo

استخدِم adb shell dumpsys meminfo --all <package> للاطّلاع على تفاصيل الذاكرة. ابحث عن فئة WebView في الناتج وعدد العناصر.

تحديد مواصفات العارض

بما أنّ عملية العرض تعمل في عملية منفصلة، لا يمكنك إنشاء ملف تعريف لذاكرة التخزين المؤقت الأصلية الخاصة بها من خلال إنشاء ملف تعريف لتطبيقك فقط، بل عليك تحديد معرّف العملية (PID) لعملية العرض على وجه التحديد.

لتحديد رقم تعريف العملية الصحيح لبرنامج العرض عندما تكون عدّة عناصر WebView نشطة، اتّبِع الخطوات التالية:

  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 أو اسم مشابه. إذا كان تطبيق واحد فقط يستخدم WebView، من المحتمل أن يكون هناك تطبيق واحد فقط.

بعد الحصول على معرّف المنتج، يمكنك إنشاء ملف شخصي له باستخدام heapprofd.

أفضل الممارسات المتعلّقة بذاكرة WebView

التدمير الصريح

من المتوقّع أن تستدعي التطبيقات WebView.destroy() للإشارة إلى وقت انتهاء استخدامها لنسخة معيّنة.

على الرغم من أنّ WebView تحاول ضمان إمكانية جمع البيانات غير الضرورية من المثيلات وإتاحة جميع مواردها تلقائيًا، يصعب ضمان ذلك في% 100 من الحالات. وحتى عندما تعمل عملية جمع البيانات المُهمَلة تلقائيًا، قد تتأخر بشكل كبير، ما يؤدي إلى احتفاظ التطبيق بالموارد لفترة أطول بكثير من المتوقع.

إذا استدعى تطبيق WebView.destroy() في الوقت المناسب (مثلاً، في Activity.onDestroy())، لن يؤدي الاحتفاظ بمرجع لكائن WebView نفسه إلى تسرُّب أي موارد أصلية مهمة. ليس من الضروري إزالة مراجع العنصر WebView في حقول النشاط بعد إتلافه، لأنّه سيتم تنظيفه عند جمع البيانات غير الضرورية في النشاط نفسه.

تمارين: تجربة عملية مع ذاكرة WebView

التمرين 1: مراقبة حجم الذاكرة المستخدَمة في العمليات المتعددة

  1. شغِّل MemoryLab وأجرِ قياسًا أساسيًا لذاكرة تطبيقك:

    adb shell dumpsys meminfo com.android.memorylab
    

    نموذج خط الأساس (rango): TOTAL PSS: 18915 KB

  2. انقر على تشغيل WebView (عادي).

  3. في WebView، انقر على تخصيص ذاكرة JavaScript (1000 DIV) عدة مرات.

  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. لاحظ ارتفاع إجمالي PSS لعملية العرض. في عملية التشغيل النموذجية، ارتفع حجم الذاكرة إلى 55 ميغابايت تقريبًا بعد بضع عمليات تخصيص. يُرجى العِلم أنّ عمليات تخصيص JavaScript (التي يتم التعامل معها من خلال محرّك V8) تساهم عادةً في الأقسام خاصة/أخرى أو غير معروفة (mmap) في dumpsys meminfo، بدلاً من Dalvik Heap.

التمرين 2: تسريب WebView من جهة Java

من الأخطاء الشائعة الاحتفاظ بنسخة من WebView في حقل ثابت أو في عنصر طويل الأمد يؤدي إلى تسرُّب الذاكرة. لأنّ العنصر WebView هو "عنصر ربط" كبير الحجم يحتوي على موارد أصلية وربما عمليات عرض كاملة، لذا فإنّ تسريبه مكلف للغاية.

تأثير تسريب WebView

  1. في MemoryLab، انقر على Launch WebView (Java Leak).
  2. سيتم إغلاق النشاط تلقائيًا بعد تحميل الصفحة (محاكاة التنقّل المتكرّر وتراكم التسريب).
  3. انقر على الزر 4 مرات.
  4. تحقَّق من عدد مثيلات WebView في تطبيقك:

    adb shell dumpsys meminfo com.android.memorylab
    

    ابحث عن قسم العناصر في أسفل الصفحة. سيظهر لك أنّ عدد WebViews قد زاد إلى 4.

    نموذج الناتج (4 مثيلات تم تسريبها) على جهاز 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. التقط لقطة لأجزاء من الذاكرة واستخدِم أداة تحليل تخصيص الذاكرة في Android للعثور على تسرُّب الذاكرة. إذا لم يكن لديك 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)، انقر على رابط عمليات التخصيص (أو المواقع الإلكترونية) في القائمة العلوية للاطّلاع على إجمالي استخدام الذاكرة.

    تخصيصات AHAT

  7. ابحث عن الصف android.webkit.WebView. انقر على عدد مرات الظهور للاطّلاع على جميع مرات الظهور النشطة. من المفترض أن تظهر لك مثيلات متعدّدة في القائمة.

    مثيلات WebView في &quot;أداة اختبار إعلانات التطبيقات&quot;

  8. انقر على أحد WebView المثيلات التي تم تسريبها. انتقِل للأسفل إلى قسم نموذج المسار من جذر GC. ستظهر لك في القائمة sLeakedWebViews ضمن com.android.memorylab.WebViewActivity.

    مسار AHAT إلى جذر GC


← مدمج مع المحتوى | ↑ للأعلى | رمز التطبيق →