WebView เป็นคอมโพเนนต์ที่มีประสิทธิภาพซึ่งช่วยให้คุณแสดงเนื้อหาเว็บภายในแอปพลิเคชัน Android ได้ อย่างไรก็ตาม เนื่องจากโดยพื้นฐานแล้วเป็นเอนจินเบราว์เซอร์ที่มีฟีเจอร์ครบถ้วน (Chromium) จึงใช้หน่วยความจำที่ใช้จำนวนมากและมีสถาปัตยกรรมแบบหลายกระบวนการที่ซับซ้อน
ข้อมูลเบื้องหลังทางเทคนิค: สถาปัตยกรรมแบบหลายกระบวนการ
ในอุปกรณ์ที่ใช้พลังงานจาก Android รุ่นใหม่ WebView จะใช้โมเดลแบบหลายกระบวนการเพื่อปรับปรุงความปลอดภัยและความเสถียร เมื่อแอปใช้ WebView ระบบจะกระจายหน่วยความจำไปยังกระบวนการต่างๆ ดังนี้
- กระบวนการเบราว์เซอร์ (กระบวนการของแอป): นี่คือกระบวนการหลัก
ของแอปพลิเคชัน ซึ่งมีออบเจ็กต์
WebViewของ Java และส่วน "เบราว์เซอร์" ของเอนจิน Chromium กระบวนการนี้จะจัดการ UI, คำขอเครือข่าย และการแสดงผล GPU (ผสานรวมกับไปป์ไลน์การแสดงผล HWUI ของ Android โดยตรง) WebView ไม่มีกระบวนการ GPU แยกต่างหากเหมือนกับ Chrome - กระบวนการแสดงผล: กระบวนการนี้มีหน้าที่แยกวิเคราะห์ HTML, เรียกใช้ JavaScript และจัดเลย์เอาต์ โดยจะแยกออกจากระบบส่วนที่เหลือเพื่อความปลอดภัย ปัจจุบันแอปจะได้รับกระบวนการแสดงผลเพียงกระบวนการเดียวสำหรับ WebView ทั้งหมด (ยกเว้นกรณีพิเศษที่พบได้น้อย) ซึ่งแตกต่างจาก Chrome ที่มักจะใช้กระบวนการแสดงผลแยกต่างหากสำหรับเว็บไซต์ต่างๆ

ความสำคัญต่อหน่วยความจำ
เมื่อใช้ dumpsys meminfo <your_package> คุณจะเห็นเฉพาะหน่วยความจำที่
**กระบวนการเบราว์เซอร์** (กระบวนการของแอป) ใช้ ระบบจะพิจารณาหน่วยความจำที่กระบวนการแสดงผล ใช้แยกต่างหาก
หน่วยความจำ WebView จะกระจายอยู่ในกระบวนการเบราว์เซอร์ดังนี้
- ฮีปของ Java: มี
WebViewWrapper ของ Java และออบเจ็กต์ที่เกี่ยวข้อง - ฮีปเนทีฟ: มีโครงสร้างข้อมูลภายใน
แคช และสถานะของเอนจินเบราว์เซอร์ 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ค้นหาแอปแล้วคลิกตรวจสอบ
ในหน้าต่างเครื่องมือสำหรับนักพัฒนาเว็บ ให้ไปที่แท็บหน่วยความจำ เพื่อถ่ายภาพฮีปหรือบันทึกไทม์ไลน์การจัดสรรสำหรับฮีป JavaScript
dumpsys meminfo
ใช้ adb shell dumpsys meminfo --all <package> เพื่อดูรายละเอียดหน่วยความจำ
มองหาหมวดหมู่ WebView ในเอาต์พุตและจำนวนออบเจ็กต์
การทำโปรไฟล์ของ Renderer
เนื่องจาก Renderer ทำงานในกระบวนการแยกต่างหาก คุณจึงทำโปรไฟล์ฮีปเนทีฟไม่ได้เพียงแค่ทำโปรไฟล์แอป คุณต้องระบุ PID ของกระบวนการแสดงผลโดยเฉพาะ
วิธีระบุ PID ของ Renderer ที่ถูกต้องเมื่อ WebView หลายรายการทำงานอยู่
ใช้
dumpsys activity:adb shell dumpsys activity processes <your_package_name>มองหาส่วน
mConnectionsคุณจะเห็นConnectionRecordที่ลิงก์แอปกับSandboxedProcessServicePID ของกระบวนการนั้นคือ Renderer ตัวอย่าง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 ให้แตะจัดสรรหน่วยความจำ JS (1000 DIV) หลายครั้ง
ตรวจสอบหน่วยความจำของแอปอีกครั้ง
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สังเกต PSS ทั้งหมด ที่สูงของกระบวนการแสดงผล ในการเรียกใช้ตัวอย่างของเรา ค่านี้เพิ่มขึ้นเป็น ~55MB หลังจากมีการจัดสรร 2-3 ครั้ง โปรดทราบว่าการจัดสรร JavaScript (จัดการโดยเอนจิน V8) มักจะมีส่วนร่วมในส่วน Private Other หรือ Unknown (mmap) ของ
dumpsys meminfoแทนที่จะเป็นฮีป Dalvik
แบบฝึกหัดที่ 2: การรั่วไหลของ WebView ฝั่ง Java
ข้อผิดพลาดที่พบบ่อยคือการเก็บอินสแตนซ์ WebView ไว้ในฟิลด์แบบคงที่หรือในออบเจ็กต์ที่มีอายุการใช้งานยาวนานซึ่งทำให้เกิดการรั่วไหล เนื่องจากออบเจ็กต์ WebView เป็น "จุดยึด" ขนาดใหญ่ที่เก็บทรัพยากรเนทีฟและอาจเก็บกระบวนการแสดงผลทั้งหมดไว้ การรั่วไหลจึงมีค่าใช้จ่ายสูงมาก

- ใน MemoryLab ให้แตะ เปิด 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) ให้คลิกลิงก์การจัดสรร (หรือเว็บไซต์) ในเมนูด้านบนเพื่อดูการใช้งานหน่วยความจำโดยรวม
ค้นหาคลาส
android.webkit.WebViewคลิกจำนวนอินสแตนซ์ เพื่อดูอินสแตนซ์ที่ใช้งานอยู่ทั้งหมด คุณควรเห็นอินสแตนซ์หลายรายการในรายการ
คลิกอินสแตนซ์
WebViewที่รั่วไหลรายการใดรายการหนึ่ง เลื่อนลงไปที่ส่วนเส้นทางตัวอย่างจากราก GC คุณจะเห็นว่ารายการsLeakedWebViewsในcom.android.memorylab.WebViewActivityเก็บอินสแตนซ์นี้ไว้