WebView เรียกใช้โค้ดแบบเนทีฟในหลายกระบวนการเพื่อแสดงผลเนื้อหาเว็บในแอป Android ของคุณ การปล่อยให้อินสแตนซ์ WebView ไม่ได้รับการจัดการอาจทำให้หน่วยความจำรั่วไหล ข้อขัดข้องเนื่องจากหน่วยความจำไม่เพียงพอ (OOM) และประสิทธิภาพของแอปลดลง
เอกสารนี้อธิบายโมเดลหน่วยความจำแบบหลายกระบวนการของ WebView, อธิบายวิธีจัดการวงจรอย่างเหมาะสมเพื่อป้องกันการรั่วไหล และให้เวิร์กโฟลว์ที่ใช้งานได้จริงสำหรับการวินิจฉัยปัญหาเกี่ยวกับหน่วยความจำ
ทำความเข้าใจสถาปัตยกรรมหน่วยความจำของ WebView
หากต้องการจัดการหน่วยความจำ WebView อย่างมีประสิทธิภาพ โปรดทำความเข้าใจวิธีที่ Android จัดสรรทรัพยากรสำหรับเนื้อหาเว็บ ดังนี้
การดำเนินการแบบหลายกระบวนการ: ใน Android 8.0 (ระดับ API 26) ขึ้นไป
WebViewจะแยกเนื้อหาเว็บออกจากฟังก์ชันหลักของแอปในหลายกระบวนการ (ในอุปกรณ์ที่มี RAM ต่ำ อาจกลับไปใช้กระบวนการเดียว)- กระบวนการโฮสต์ (เบราว์เซอร์): กระบวนการหลักของแอปที่โค้ด
Activityและ Java หรือ Kotlin ทำงาน - กระบวนการแสดงผลแบบแยก: กระบวนการแซนด์บ็อกซ์แยกต่างหาก (
SandboxedProcessService) ที่แยกวิเคราะห์ HTML และ CSS, เรียกใช้ JavaScript และแสดงผลหน้าเว็บ
- กระบวนการโฮสต์ (เบราว์เซอร์): กระบวนการหลักของแอปที่โค้ด
หน่วยความจำที่ใช้แบบเนทีฟ: หน่วยความจำ
WebViewส่วนใหญ่ ซึ่งรวมถึงกราฟิกที่แสดงผล แผนผัง DOM และหน่วยความจำรันไทม์ของ JavaScript จะได้รับการจัดสรรในหน่วยความจำแบบเนทีฟ ไม่ใช่ในฮีป Java ฮีปดัมป์ของ Java (.hprof) จะแสดงเฉพาะออบเจ็กต์ Wrapper ของ Java ที่มีน้ำหนักเบา และไม่บันทึกหน่วยความจำจริงที่เนื้อหาเว็บใช้ผลกระทบของหน่วยความจำแบบเนทีฟต่อระบบ: การจัดสรรหน่วยความจำแบบเนทีฟต่างจากการจัดสรรฮีป Java ซึ่งถูกจำกัดด้วยขีดจำกัด
maxHeapของแอปและจะล้มเหลวอย่างรวดเร็วพร้อมข้อผิดพลาดOutOfMemoryErrorเนื่องจากหน่วยความจำแบบเนทีฟสามารถเพิ่มขึ้นเป็นกิกะไบต์ได้โดยไม่มีการแจ้งเตือน เมื่อหน่วยความจำเนทีฟที่ไม่ได้ปล่อยใช้ RAM จริงและพื้นที่ Swap (zRAM) จนเต็ม Low Memory Killer (LMK) ของ Android จะเริ่มสิ้นสุดกระบวนการเบื้องหลังเพื่อเรียกคืนหน่วยความจำ ซึ่งจะลดประสิทธิภาพการทำงานหลายอย่างพร้อมกันโดยรวมของอุปกรณ์ก่อนที่จะปิดแอปที่ทำงานอยู่เบื้องหน้าในที่สุด
จัดการวงจรของ WebView
การจัดการวงจรที่เหมาะสมเป็นสิ่งสำคัญในการป้องกันหน่วยความจำรั่วไหล ข้อผิดพลาดที่พบบ่อยคือการคิดว่าการนำ WebView ออกจากเลย์เอาต์หรือการปล่อยให้ Activity ทำงานจนเสร็จจะทำให้หน่วยความจำว่างโดยอัตโนมัติ
เพื่อให้แน่ใจว่ามีการล้างทั้งการอ้างอิงบริบท Java และทรัพยากรการแสดงผลแบบเนทีฟอย่างสมบูรณ์ คุณต้องจัดลำดับการแยกส่วนอย่างชัดเจนในวงจรของคอมโพเนนต์โฮสต์ (เช่น onDestroy()) โดยหยุดการดำเนินการของหน้าเว็บที่ใช้งานอยู่ แยกมุมมองออกจากคอนเทนเนอร์ และปล่อยการเชื่อมโยงแบบเนทีฟ
ล้างอินสแตนซ์ WebView
หากต้องการปิดระบบอย่างสมบูรณ์และปล่อยทรัพยากรเมื่อทำลาย Activity หรือ Fragment ให้ทำดังนี้
- นำ
WebViewออกจากคอนเทนเนอร์หลัก (ViewGroup) - หยุดการโหลดที่ทำงานอยู่และล้างประวัติการนำทาง
- โทร
destroy() - ล้างการอ้างอิงถึง
null
ตัวอย่างต่อไปนี้แสดงวิธีล้าง WebView อย่างถูกต้อง
Kotlin
override fun onDestroy() { myWebView?.let { // Remove the WebView from its parent ViewGroup. (it.parent as? ViewGroup)?.removeView(it) // Stop active loading and clear history. it.stopLoading() it.clearHistory() // Destroy the instance. it.destroy() } myWebView = null super.onDestroy() }
Java
@Override
protected void onDestroy() {
if (myWebView != null) {
// Remove the WebView from its parent ViewGroup.
if (myWebView.getParent() instanceof ViewGroup) {
((ViewGroup) myWebView.getParent()).removeView(myWebView);
}
// Stop active loading and clear history.
myWebView.stopLoading();
myWebView.clearHistory();
// Destroy the instance.
myWebView.destroy();
}
myWebView = null;
super.onDestroy();
}
ทำความเข้าใจความทรงจำหลังการทำลายล้าง
เมื่อคุณเรียกใช้ destroy() ระบบจะปล่อยบริบท Activity ล้างลำดับชั้นการแสดงผล และหยุดการทำงานของเว็บในเบื้องหลัง อย่างไรก็ตาม คุณอาจสังเกตเห็นว่าหน่วยความจำจริงของกระบวนการ (Resident Set Size) ไม่ได้ลดลงทันทีจนถึงเกณฑ์พื้นฐานก่อน WebView
โดยลักษณะการทำงานนี้เป็นสิ่งที่ปกติ แคชรันไทม์แบบเนทีฟ คลังที่ใช้ร่วมกัน และหน้าหน่วยความจำที่จัดสรรจะยังคงอยู่ในกระบวนการจนกว่าระบบปฏิบัติการจะเรียกคืนหรือกระบวนการสิ้นสุดลง เป้าหมายหลักของ destroy() คือการป้องกันไม่ให้เกิดหน่วยความจำรั่วไหลแบบสะสมของ Activity เมื่อผู้ใช้ไปยังหน้าจอที่ทำงานบนเว็บและออกจากหน้าจอดังกล่าว
เมตริกการแก้ไขข้อบกพร่องที่สำคัญ
เมื่อวิเคราะห์การใช้หน่วยความจำของ WebView ให้มุ่งเน้นที่เมตริกต่อไปนี้
Resident Set Size (RSS): RAM จริงทั้งหมดที่แมปเข้ากับกระบวนการ ซึ่งรวมถึงโค้ดและไลบรารีที่ใช้ร่วมกัน (ติดป้ายกำกับเป็นทั้งหมดใน Android Studio Profiler)
RSS แบบไม่ระบุตัวตน (RssAnon): การจัดสรรหน่วยความจำที่จัดสรรโดยกระบวนการโดยตรงซึ่งไม่มีไฟล์ในดิสก์รองรับ (เช่น ฮีปแบบเนทีฟและการจัดสรรรันไทม์ JavaScript) ซึ่งแสดงถึงต้นทุนหน่วยความจำหลักของเนื้อหาเว็บ (ติดป้ายกำกับว่าจัดสรรแล้วใน Android Studio Profiler)
หน่วยความจำที่ใช้แบบส่วนตัว (PMF): ผลรวมของ RSS แบบไม่ระบุตัวตนและ Swap (zRAM) PMF แสดงภาระหน่วยความจำที่ขับออกไม่ได้จริงซึ่งแอปของคุณกำหนดให้กับระบบ
PMF ของเบราว์เซอร์เทียบกับ PMF ของโปรแกรมแสดงผล: หน่วยความจำที่ใช้โดยกระบวนการหลักของแอปเทียบกับหน่วยความจำที่ใช้โดยกระบวนการแสดงผลที่แยกต่างหาก เนื้อหาเว็บที่หนักหน่วงทำให้เกิดการพุ่งสูงขึ้นในกระบวนการแสดงผลเป็นหลัก
จำนวนออบเจ็กต์ที่ทำงานอยู่ (
WebViews,Activities,Views): จำนวนอินสแตนซ์ UI, บริบท และWebViewที่ทำงานอยู่ซึ่งเก็บไว้ในหน่วยความจำ การติดตามข้อมูลเหล่านี้จะช่วยระบุว่าการเพิ่มขึ้นของหน่วยความจำเกิดจากการอ้างอิง Java ที่เก็บไว้หรือการจัดสรรเฉพาะแบบเนทีฟPrivate Other และ Native Heap: ใน
dumpsys meminfoการจัดสรร C/C++ แบบเนทีฟและการแมปหน่วยความจำที่กำหนดเอง (เช่นPartitionAllocของ Chromium หรือฮีปรันไทม์ JavaScript แบบฝัง) จะปรากฏในส่วน Native Heap และ Private Other แทนที่จะเป็น Java Heap
ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวนับหน่วยความจำของกระบวนการและหมวดหมู่ของตัวนับได้ที่อภิธานศัพท์เกี่ยวกับหน่วยความจำของกระบวนการ
เวิร์กโฟลว์การวินิจฉัยที่ใช้งานได้จริง
เนื่องจาก WebView ทำงานในหลายกระบวนการและจัดสรรหน่วยความจำแบบเนทีฟ ให้ใช้เครื่องมือและเทคนิคต่อไปนี้เพื่อตรวจสอบการใช้ทรัพยากร
เครื่องมือวิเคราะห์และวินิจฉัย
หากต้องการตรวจสอบการจัดสรรหน่วยความจำและวิเคราะห์การรั่วไหล ให้ใช้เครื่องมือต่อไปนี้
เครื่องมือสร้างโปรไฟล์หน่วยความจำของ Android Studio: ใช้เครื่องมือสร้างโปรไฟล์หน่วยความจำเพื่อแสดงภาพการจัดสรรแบบเนทีฟ ติดตามหมวดหมู่หน่วยความจำเมื่อเวลาผ่านไป และตรวจหาการรั่วไหลของ
Activityในการเปลี่ยนหน้าจอการติดตามหน่วยความจำด้วย Perfetto: ใช้ Perfetto เพื่อบันทึกตัวนับหน่วยความจำระดับระบบ (เช่น RSS และ RSS แบบไม่ระบุตัวตน) เพื่อสังเกตการเติบโตของหน่วยความจำโดยรวม โปรดทราบว่าการจัดสรรเครื่องมือแบบเนทีฟของ
WebViewจะไม่สร้าง Call Stack ในเครื่องมือสร้างโปรไฟล์ฮีปของ Perfetto ใช้เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เพื่อตรวจสอบสแนปชอตฮีป JavaScript และการจัดสรร DOM ภายในเนื้อหาเว็บ
ตรวจสอบจำนวนออบเจ็กต์ที่ใช้งานอยู่
หากต้องการตรวจสอบว่าการเพิ่มขึ้นของหน่วยความจำเกิดจากออบเจ็กต์เฟรมเวิร์ก Java ที่เก็บไว้ (เช่น คอมโพเนนต์ UI) หรือการจัดสรรแบบเนทีฟ ให้ตรวจสอบส่วน Objects ของ dumpsys meminfo ดังนี้
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"เอาต์พุตจะแสดงจำนวนออบเจ็กต์ที่ใช้งานอยู่ ดังนี้
Objects
Views: 142 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 12 AssetManagers: 0
Local Binders: 32 Proxy Binders: 45
Parcel memory: 15 Parcel count: 30
Death Recipients: 2 WebViews: 1
ส่วนนี้จะแสดงจำนวนออบเจ็กต์เฟรมเวิร์ก, แฮนเดิล IPC และการจัดสรรพัสดุที่ใช้งานอยู่ สำหรับการวินิจฉัย WebView ให้มุ่งเน้นที่ Activities และ WebViews เป็นหลัก
ทำการโต้ตอบของผู้ใช้เป้าหมาย (เช่น การเปิดและปิดหน้าจอเว็บ) ซ้ำๆ และเปรียบเทียบจำนวนดังนี้
อินสแตนซ์รั่วไหล: หาก
WebViewsหรือActivitiesเพิ่มขึ้นในการไปยังส่วนต่างๆ แต่ละครั้งและไม่กลับไปที่ค่าพื้นฐาน แสดงว่าแอปของคุณทำให้อินสแตนซ์ JavaWebViewหรือโฮสต์Activityรั่วไหล (เช่น เนื่องจากไม่มีViewGroup.removeView()หรือการอ้างอิง Listener ที่เก็บไว้) เนื่องจากActivityที่รั่วไหลจะปักหมุดทั้งแผนผังมุมมองและทรัพยากรรูปภาพที่ถอดรหัสแล้วไว้ในหน่วยความจำ การเข้าชมซ้ำๆ จะทำให้ฮีป Java หมดลงอย่างรวดเร็วและทำให้เกิดข้อขัดข้องOutOfMemoryErrorการรั่วไหลของเนทีฟหรือ DOM: หาก
WebViewsและActivitiesยังคงที่ขณะที่ RSS ของกระบวนการทั้งหมดและอื่นๆ ที่เป็นส่วนตัวเพิ่มขึ้นอย่างต่อเนื่อง การรั่วไหลจะเกิดขึ้นในทรัพยากรเนทีฟที่ยังไม่ได้เผยแพร่ องค์ประกอบ DOM หรือการเชื่อมโยงเครื่องมือ JavaScript เนื่องจากการจัดสรรเหล่านี้อยู่ในหน่วยความจำแบบเนทีฟและข้ามผ่านตัวเก็บขยะ ART จึงยังคงมองไม่เห็นเครื่องมือตรวจจับการรั่วไหลของ Java มาตรฐาน และยังคงสะสมต่อไปจนกว่าระบบปฏิบัติการจะสิ้นสุดการทำงานของแอป
สร้างโปรไฟล์กระบวนการแสดงผลที่แยกต่างหากโดยใช้ CLI
การเรียกใช้ dumpsys meminfo ด้วยชื่อแพ็กเกจของแอปจะแสดงผลหน่วยความจำสำหรับกระบวนการโฮสต์หลักเท่านั้น วิธีตรวจสอบกระบวนการแสดงผลที่แยกต่างหากซึ่งแสดงผลหน้าเว็บ
ค้นหารหัสกระบวนการ (PID) ของบริการโปรแกรมแสดงผลที่แยกต่างหาก โดยทำดังนี้
adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"เอาต์พุตจะแสดงระเบียนกระบวนการที่แยกออกมาและ PID ของกระบวนการดังกล่าว RENDERER_PID (เช่น
22155)Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}ตรวจสอบรายละเอียดหน่วยความจำของกระบวนการแสดงผลโดยใช้ PID ดังนี้
adb shell dumpsys meminfo <var>RENDERER_PID</var>ตรวจสอบกระบวนการของแอปโฮสต์เพื่อประเมินรอยเท้าฝั่งเบราว์เซอร์ โดยทำดังนี้
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
ตรวจสอบแผนผังและการจัดสรรหน่วยความจำ
หากต้องการดูว่าระบบย่อยหรือตัวจัดสรรแบบเนทีฟใดใช้หน่วยความจำแบบไม่ระบุชื่อ ให้ตรวจสอบแผนผังหน่วยความจำของกระบวนการ โดยทำดังนี้
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"ตารางต่อไปนี้แสดงแท็กหน่วยความจำแบบไม่ระบุตัวตนที่พบบ่อยและความเกี่ยวข้องกับการเพิ่มขึ้นของหน่วยความจำ
| แท็กความทรงจำ | OOB | ความเกี่ยวข้องกับเนื้อหาแอปและเว็บ | สาเหตุที่พบบ่อยของการเพิ่มขึ้นของหน่วยความจำ |
|---|---|---|---|
[anon:partition_alloc] |
Chromium PartitionAlloc | การจัดสรรสำหรับแผนผัง DOM, บัฟเฟอร์การแสดงผล, ฮีป JavaScript ของ V8 และการดำเนินการ WebAssembly ใน WebView |
ใช่ (สูง): การโหลดหน้าเว็บที่มีขนาดใหญ่, DOM ที่มีสื่อจำนวนมาก หรือการไม่เรียก destroy() ในอินสแตนซ์ WebView ที่ถูกทิ้งจะทำให้แท็กนี้เพิ่มขึ้นโดยตรง |
[anon:scudo...] หรือ [anon:libc_malloc] |
ตัวจัดสรรฮีปแบบเนทีฟของ Android (Scudo / jemalloc) | การจัดสรรแบบเนทีฟ C/C++ ทั่วไปที่ใช้โดยไลบรารี NDK, JNI Bridge และไปป์ไลน์กราฟิกแบบเนทีฟ | ใช่ (ปานกลางถึงสูง): การเติบโตจะเกิดขึ้นเมื่อ Wrapper JNI แบบเนทีฟหรือทรัพยากร Dependency ของ C++ ของบุคคลที่สามเก็บการจัดสรรที่ยังไม่ได้เผยแพร่ไว้ในการไปยังส่วนต่างๆ |
[anon:...] (เช่น [anon:quickjs_heap...]) |
สคริปต์ที่กำหนดเองหรือรันไทม์แบบเนทีฟ | เครื่องมือ JavaScript แบบฝัง, รันไทม์ WebAssembly ที่กำหนดเอง หรือพูลบัฟเฟอร์เนทีฟที่กำหนดเอง | ใช่ (ขึ้นอยู่กับบริบท): พบได้ทั่วไปในแอปแบบผสมที่เรียกใช้เครื่องมือการเขียนสคริปต์ควบคู่ไปกับมุมมองแบบเนทีฟ และไม่สามารถล้างการเชื่อมโยงรันไทม์ได้ |
ข้อจำกัดของ API หน่วยความจำในแอป
API หน่วยความจำในแอป (เช่น Debug.getMemoryInfo หรือ ActivityManager.getProcessMemoryInfo) จะวัดเฉพาะกระบวนการเรียกใช้เท่านั้น
ในโหมดหลายกระบวนการ API เหล่านี้จะไม่สามารถบันทึกหน่วยความจำที่ใช้โดยกระบวนการแสดงผลที่แยกต่างหาก หากต้องการประเมินหน่วยความจำทั้งหมดอย่างแม่นยำ ให้ใช้เครื่องมือระบบ เช่น dumpsys meminfo, Perfetto หรือ Android Studio Profiler
การคัดแยกหน่วยความจำสูงในแอปแบบไฮบริด
เมื่อวิเคราะห์การเพิ่มขึ้นของหน่วยความจำที่อธิบายไม่ได้ระหว่างการโต้ตอบ WebView ที่เกิดซ้ำ (เช่น การเปิดเว็บลิงก์หรือการไปยังฟีดที่ทำงานบนเว็บ) ให้ใช้เวิร์กโฟลว์การคัดแยกต่อไปนี้เพื่อแยกแยะว่าการรั่วไหลนั้นเกิดขึ้นในเลเยอร์ Java หรือเครื่องมือเนทีฟ
แยกประเภทการรั่วไหล (Java เทียบกับเนทีฟ): เรียกใช้
dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"ก่อนและหลังการเปลี่ยนผ่านของผู้ใช้ซ้ำๆ (เช่น การเปิดและปิดบทความบนเว็บหรือการปัดผ่านฟีด)- การสังเกต: หากจำนวน
ActivitiesและWebViewsยังคงคงที่ (เช่น อินสแตนซ์ที่ใช้งานอยู่ 1-2 รายการ) แสดงว่าแอปไม่ได้ทำให้บริบทActivityหรืออินสแตนซ์ JavaWebViewรั่วไหล
- การสังเกต: หากจำนวน
วัดความแตกต่างของหน่วยความจำในการโต้ตอบต่างๆ (การติดตามอนุกรมเวลา): จับภาพสแนปชอต
dumpsys meminfoในการโต้ตอบของผู้ใช้หลายครั้งเพื่อคำนวณอัตราการจัดสรรต่อการเปลี่ยนผ่าน:- การสังเกต: Java Heap ยังคงถูกจำกัดและทำงานได้ดี (พุ่งสูงขึ้นระหว่างการใช้งานและลดลงหลังจากการรวบรวมขยะ) แต่ Private Other และ Native Heap เพิ่มขึ้นอย่างต่อเนื่องหลายเมกะไบต์ต่อการเปลี่ยนผ่าน ซึ่งพิสูจน์ได้ว่าการรั่วไหลทั้งหมดอยู่ในหน่วยความจำแบบเนทีฟนอกรันไทม์ ART
ฮีปดัมป์ของ Java มาตรฐาน (
.hprof) จะไม่แสดงปัญหาใดๆ
- การสังเกต: Java Heap ยังคงถูกจำกัดและทำงานได้ดี (พุ่งสูงขึ้นระหว่างการใช้งานและลดลงหลังจากการรวบรวมขยะ) แต่ Private Other และ Native Heap เพิ่มขึ้นอย่างต่อเนื่องหลายเมกะไบต์ต่อการเปลี่ยนผ่าน ซึ่งพิสูจน์ได้ว่าการรั่วไหลทั้งหมดอยู่ในหน่วยความจำแบบเนทีฟนอกรันไทม์ ART
ฮีปดัมป์ของ Java มาตรฐาน (
ตรวจสอบแผนผังหน่วยความจำที่ไม่ระบุชื่อ: ตรวจสอบแผนผังหน่วยความจำของกระบวนการโดยใช้ ADB (ดูตรวจสอบแผนผังหน่วยความจำและการจัดสรร)
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- การสังเกต: การเติบโตของหน่วยความจำกระจุกตัวอยู่ในฮีปของ
[anon:partition_alloc]หรือเครื่องมือการเขียนสคริปต์แบบฝัง พร้อมกับการเพิ่มขึ้นอย่างช้าๆ ของการอ้างอิงส่วนกลางของ JNI ซึ่งหมายความว่าแม้จะมีการแทนที่มุมมอง Java แล้ว แต่ระบบไม่ได้ปล่อยออบเจ็กต์หน้าเว็บแบบเนทีฟหรือการเชื่อมโยง JavaScript ที่เกี่ยวข้อง
- การสังเกต: การเติบโตของหน่วยความจำกระจุกตัวอยู่ในฮีปของ
การแก้ไข:
- ตรวจสอบว่า
WebViewที่รีไซเคิลหรือทิ้งทุกรายการหยุดสคริปต์ที่ทำงานอยู่ (stopLoading()) อย่างชัดเจน ล้างประวัติ และเรียกใช้destroy() - ยกเลิก Callback ของบริดจ์ JavaScript ที่กำหนดเองหรือการอ้างอิง JNI ทั่วโลกที่เชื่อมโยงกับมุมมองที่ปิดไปแล้ว
- ตรวจสอบว่า
Private Otherและ RSS ของกระบวนการเสถียรหลังจากการเปลี่ยนการนำทาง
- ตรวจสอบว่า
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการแก้ไขข้อบกพร่องและการทำโปรไฟล์หน่วยความจำและประสิทธิภาพของ WebView ได้ที่แหล่งข้อมูลต่อไปนี้