WebView הוא רכיב רב עוצמה שמאפשר להציג תוכן מהאינטרנט באפליקציה ל-Android. עם זאת, מכיוון שמדובר למעשה במנוע דפדפן (Chromium) עם תכונות מלאות, הוא תופס הרבה הזיכרון שבשימוש וכולל ארכיטקטורה מורכבת של ריבוי תהליכים.
רקע טכני: ארכיטקטורה מרובת תהליכים
במכשירים מודרניים עם Android, רכיב WebView משתמש במודל מרובה תהליכים כדי לשפר את האבטחה והיציבות. כשהאפליקציה משתמשת ב-WebView, הזיכרון מתחלק בין תהליכים שונים:
- תהליך הדפדפן (תהליך האפליקציה): זהו התהליך הראשי של האפליקציה. הוא מכיל את אובייקט Java
WebViewואת החלק 'browser' במנוע Chromium. התהליך הזה מנהל את ממשק המשתמש, את בקשות הרשת ואת עיבוד ה-GPU (משולב ישירות עם צינור העיבוד של Android HWUI). בניגוד ל-Chrome, ל-WebView אין תהליך GPU נפרד. - תהליך הרינדור: התהליך הזה אחראי לניתוח של HTML, להרצה של JavaScript ולפריסה. הוא מבודד משאר המערכת מטעמי אבטחה. בשלב הזה, האפליקציות מקבלות רק תהליך עיבוד אחד לכל רכיבי ה-WebView (חוץ מכמה מקרים מיוחדים נדירים), בניגוד ל-Chrome, שלרוב משתמש בתהליכי עיבוד נפרדים לאתרים שונים.

למה זה חשוב לזיכרון
כשמשתמשים ב-dumpsys meminfo <your_package>, רואים רק את הזיכרון שבו נעשה שימוש על ידי תהליך הדפדפן (תהליך האפליקציה). הזיכרון שבו נעשה שימוש בתהליך הרינדור נספר בנפרד.
בתוך תהליך הדפדפן, הזיכרון של WebView מחולק באופן הבא:
- ערימת Java: מכילה את
WebViewJava wrapper ואובייקטים קשורים. - Native heap: מכיל את המבנים הפנימיים של נתוני מנוע הדפדפן Chromium, מטמונים ומצב. שימו לב: בגלל השימוש ב-PartitionAlloc, יכול להיות שחלק מהקצאות הזיכרון המקומיות של WebView לא ייספרו בקטע Native Heap ב-
dumpsys meminfo, ויכול להיות שהן יופיעו במקום זאת בקטע Other או Unknown. - זיכרון משותף: משמש לשיתוף מאגרי נתונים גרפיים ונתונים אחרים. יכול להיות שהסיווג של התוכן הזה לא יהיה ברור ב-
dumpsys meminfo.
כלים לפתרון בעיות
כלי פיתוח ל-Chrome
הכלי הכי יעיל לניתוח הזיכרון בתוך ה-WebView (תהליך הרינדור) הוא כלי הפיתוח ל-Chrome.
הפעלת ניפוי באגים ב-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);מחברים את המכשיר באמצעות USB.
פותחים את Chrome במחשב המארח ועוברים אל
chrome://inspect/#devices.מאתרים את האפליקציה ולוחצים על בדיקה.
בחלון DevTools, עוברים לכרטיסייה Memory כדי לצלם תמונות מצב של הערימה או כדי להקליט צירי זמן של הקצאה עבור ערימת ה-JavaScript.
dumpsys meminfo
אפשר להשתמש ב-adb shell dumpsys meminfo --all <package> כדי לראות פירוט של הזיכרון.
מחפשים את הקטגוריה WebView בפלט ואת ספירת האובייקטים.
יצירת פרופיל של מנוע הרינדור
מכיוון שרכיב ה-Renderer פועל בתהליך נפרד, אי אפשר ליצור פרופיל של ה-Heap המקורי שלו רק על ידי יצירת פרופיל של האפליקציה. צריך לזהות את ה-PID של תהליך ה-Renderer באופן ספציפי.
כדי לזהות את מזהה התהליך הנכון של רכיב הרינדור כשכמה רכיבי WebView פעילים:
שימוש ב-
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 ...}בדיקת שמות התהליכים: בדרך כלל תהליכי הרינדור נקראים
com.google.android.webview:sandboxed_processXאו משהו דומה. אם רק אפליקציה אחת משתמשת ב-WebView, סביר להניח שיהיה רק מופע אחד.
אחרי שמקבלים את ה-PID, אפשר ליצור פרופיל באמצעות heapprofd.
שיטות מומלצות לשימוש בזיכרון ב-WebView
השמדה מפורשת
האפליקציות צריכות להתקשר אל WebView.destroy() כדי לציין מתי הן מסיימות עם מופע.
רכיב WebView מנסה לוודא שאפשר לבצע איסוף אשפה במופעים ולשחרר את כל המשאבים שלהם באופן אוטומטי, אבל קשה להבטיח את זה ב-100% מהמקרים. גם כשהאיסוף האוטומטי של נתונים לא רלוונטיים פועל, יכול להיות שהוא יתעכב משמעותית, ולכן האפליקציה תמשיך להשתמש במשאבים הרבה יותר זמן מהצפוי.
אם אפליקציה קוראת ל-WebView.destroy() בזמן המתאים (למשל, ב-Activity.onDestroy()), שמירת הפניה לאובייקט WebView עצמו לא תגרום לדליפת משאבים מקוריים משמעותית. אין צורך להגדיר הפניות לאובייקט WebView בשדות של Activity כ-null אחרי השמדה שלו, כי המערכת תנקה אותו כשהיא תבצע איסוף אשפה של Activity עצמו.
תרגילים: התנסות מעשית עם זיכרון WebView
תרגיל 1: התבוננות בטביעת הרגל של ריבוי תהליכים
מפעילים את MemoryLab ומבצעים מדידת בסיס של הזיכרון של האפליקציה:
adb shell dumpsys meminfo com.android.memorylabערך בסיסי לדוגמה (rango):
TOTAL PSS: 18915 KBמקישים על הפעלת WebView (רגיל).
בתצוגת WebView, מקישים על Allocate JS Memory (1000 DIVs) כמה פעמים.
בודקים שוב את הזיכרון של האפליקציה:
adb shell dumpsys meminfo com.android.memorylabשימו לב שהזיכרון בתהליך של האפליקציה לא גדל באופן משמעותי בהשוואה לנתוני הבסיס. הסיבה לכך היא שרכיבי ה-DOM נמצאים בתהליך העיבוד.
מאתרים את תהליך העיבוד:
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בודקים את הזיכרון של תהליך הרינדור (באמצעות ה-PID שלו):
adb shell dumpsys meminfo 14227שימו לב לערך הגבוה של TOTAL PSS בתהליך הרינדור. בדוגמה שלנו, גודל הזיכרון גדל ל-~55MB אחרי כמה הקצאות. שימו לב שהקצאות של JavaScript (שמטופלות על ידי מנוע V8) בדרך כלל תורמות לקטעים Private Other או Unknown (mmap) של
dumpsys meminfo, ולא ל-Dalvik Heap.
תרגיל 2: דליפת WebView בצד Java
טעות נפוצה היא שמירת מופע של WebView בשדה סטטי או באובייקט לטווח ארוך שגורם לדליפה. האובייקט WebView הוא 'עוגן' כבד שמחזיק משאבים מקומיים וגם תהליכי עיבוד שלמים, ולכן דליפה שלו היא בעלת השלכות חמורות.

- ב-MemoryLab, מקישים על Launch WebView (Java Leak) (הפעלת WebView (דליפת Java)).
- הפעילות תיסגר אוטומטית אחרי שהדף ייטען (סימולציה של ניווט חוזר ושל הצטברות דליפות).
- מקישים על הכפתור 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מבצעים תמונת מצב של הזיכרון ומשתמשים ב-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בממשק האינטרנט של AHAT (
localhost:8888), לוחצים על הקישור allocations (או sites) בתפריט העליון כדי לראות את השימוש הכולל בזיכרון.
מחפשים את הכיתה
android.webkit.WebView. לוחצים על מספר המופעים כדי לראות את כל המופעים הפעילים. ברשימה אמורות להופיע כמה מכונות.
לוחצים על אחד מהמופעים של
WebViewשנחשפו. גוללים למטה לקטע Sample Path from GC Root (נתיב לדוגמה משורש GC). הוא יופיע ברשימהsLeakedWebViewsב-com.android.memorylab.WebViewActivity.
← מותאמת | ↑ למעלה | קוד האפליקציה →