WebView là một thành phần mạnh mẽ cho phép bạn hiển thị nội dung web trong ứng dụng Android. Tuy nhiên, vì về cơ bản, đây là một công cụ trình duyệt đầy đủ tính năng (Chromium), nên nó có mức sử dụng bộ nhớ đáng kể và một cấu trúc đa quy trình phức tạp.
Thông tin kỹ thuật: Cấu trúc đa quy trình
Trên các thiết bị chạy Android hiện đại, WebView sử dụng mô hình đa tiến trình để cải thiện tính bảo mật và độ ổn định. Khi ứng dụng của bạn sử dụng WebView, bộ nhớ sẽ được phân phối trên nhiều quy trình:
- Quy trình trình duyệt (quy trình ứng dụng): Đây là quy trình chính của ứng dụng. Nó chứa đối tượng
WebViewJava và phần "trình duyệt" của công cụ Chromium. Quy trình này quản lý giao diện người dùng, các yêu cầu mạng và hoạt động kết xuất GPU (tích hợp trực tiếp với quy trình kết xuất HWUI của Android). Không giống như Chrome, WebView không có quy trình GPU riêng. - Quy trình kết xuất: Quy trình này chịu trách nhiệm phân tích cú pháp HTML, thực thi JavaScript và bố cục. Nó tách biệt với phần còn lại của hệ thống để đảm bảo an ninh. Hiện tại, các ứng dụng chỉ nhận được một quy trình kết xuất cho tất cả WebView (ngoại trừ một số ít trường hợp đặc biệt), không giống như Chrome thường sử dụng các quy trình kết xuất riêng biệt cho các trang web khác nhau.

Tại sao điều này lại quan trọng đối với bộ nhớ
Khi sử dụng dumpsys meminfo <your_package>, bạn chỉ thấy bộ nhớ mà quy trình trình duyệt (quy trình ứng dụng của bạn) sử dụng. Bộ nhớ mà quy trình trình kết xuất sử dụng được tính riêng.
Trong quy trình trình duyệt, bộ nhớ WebView được phân phối như sau:
- Vùng nhớ khối xếp Java: Chứa trình bao bọc
WebViewJava và các đối tượng có liên quan. - Heap gốc: Chứa các cấu trúc dữ liệu, bộ nhớ đệm và trạng thái nội bộ của công cụ trình duyệt Chromium. Xin lưu ý rằng do sử dụng PartitionAlloc, một số hoạt động phân bổ gốc của WebView có thể không được tính vào "Native Heap" (Heap gốc) trong
dumpsys meminfomà có thể xuất hiện trong mục "Other" (Khác) hoặc "Unknown" (Không xác định). - Bộ nhớ dùng chung: Dùng để chia sẻ các vùng đệm đồ hoạ và dữ liệu khác. Có thể
dumpsys meminfokhông phân loại rõ ràng nội dung này.
Công cụ khắc phục sự cố
Chrome DevTools
Công cụ mạnh mẽ nhất để phân tích bộ nhớ bên trong WebView (quy trình kết xuất) là Chrome DevTools.
Bật tính năng gỡ lỗi WebView trong ứng dụng của bạn:
// NOTE: In production, this should be gated behind a developer setting // or only enabled for debuggable builds to prevent reverse engineering. WebView.setWebContentsDebuggingEnabled(true);Kết nối thiết bị qua USB.
Mở Chrome trên máy chủ lưu trữ rồi chuyển đến
chrome://inspect/#devices.Tìm ứng dụng của bạn rồi nhấp vào inspect (kiểm tra).
Trong cửa sổ Công cụ cho nhà phát triển, hãy chuyển đến thẻ Bộ nhớ để chụp nhanh heap hoặc ghi lại dòng thời gian phân bổ cho heap JavaScript.
dumpsys meminfo
Dùng adb shell dumpsys meminfo --all <package> để xem thông tin chi tiết về bộ nhớ.
Tìm danh mục WebView trong đầu ra và số lượng đối tượng.
Lập hồ sơ trình kết xuất
Vì Trình kết xuất chạy trong một quy trình riêng biệt, nên bạn không thể phân tích tài nguyên cho vùng nhớ gốc của Trình kết xuất chỉ bằng cách phân tích tài nguyên cho ứng dụng của mình. Bạn phải xác định cụ thể PID của quy trình trình kết xuất.
Cách xác định PID trình kết xuất chính xác khi có nhiều WebView đang hoạt động:
Sử dụng
dumpsys activity:adb shell dumpsys activity processes <your_package_name>Tìm phần
mConnections. Bạn sẽ thấy mộtConnectionRecordliên kết ứng dụng của bạn với mộtSandboxedProcessService. PID của quy trình đó là trình kết xuất của bạn. Ví dụ:mConnections: - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}Kiểm tra tên quy trình: Các quy trình kết xuất thường có tên là
com.google.android.webview:sandboxed_processXhoặc tương tự. Nếu chỉ có một ứng dụng đang dùng WebView, thì có thể sẽ chỉ có một WebView.
Sau khi có PID, bạn có thể lập hồ sơ bằng heapprofd.
Các phương pháp hay nhất về bộ nhớ WebView
Huỷ rõ ràng
Các ứng dụng dự kiến sẽ gọi WebView.destroy() để cho biết thời điểm chúng hoàn tất một phiên bản.
Mặc dù WebView cố gắng đảm bảo rằng các phiên bản có thể được thu gom rác và tự động giải phóng tất cả tài nguyên, nhưng khó có thể đảm bảo điều này trong 100% trường hợp. Ngay cả khi tính năng tự động thu gom rác hoạt động, tính năng này có thể bị trì hoãn đáng kể, khiến ứng dụng giữ lại tài nguyên lâu hơn nhiều so với dự kiến.
Nếu ứng dụng gọi WebView.destroy() vào thời điểm thích hợp (ví dụ: trong Activity.onDestroy()), việc giữ lại một giá trị tham chiếu đến chính đối tượng WebView sẽ không làm rò rỉ bất kỳ tài nguyên gốc đáng kể nào. Không nhất thiết phải đặt giá trị rỗng cho các thông tin tham chiếu đến đối tượng WebView trong các trường Hoạt động sau khi huỷ đối tượng này, vì đối tượng sẽ được dọn dẹp khi chính Hoạt động đó được thu gom rác.
Bài tập: thực hành với bộ nhớ WebView
Bài tập 1: quan sát dấu vết của nhiều quy trình
Khởi chạy MemoryLab và đo lường bộ nhớ cơ sở của ứng dụng:
adb shell dumpsys meminfo com.android.memorylabĐường cơ sở mẫu (rango):
TOTAL PSS: 18915 KBNhấn vào Launch WebView (Normal) (Khởi chạy WebView (Thông thường)).
Trong WebView, hãy nhấn vào Allocate JS Memory (1000 DIVs) (Phân bổ bộ nhớ JS (1000 DIV)) vài lần.
Kiểm tra lại bộ nhớ của ứng dụng:
adb shell dumpsys meminfo com.android.memorylabQuan sát thấy bộ nhớ trong quy trình ứng dụng của bạn không tăng đáng kể so với đường cơ sở! Điều này là do các phần tử DOM nằm trong Quy trình kết xuất.
Tìm quy trình kết xuất:
adb shell ps -A | grep webview | grep sandboxedVí dụ về kết quả đầu ra:
u0_i9002 14227 1087 1632732 135880 do_epoll_wait 0 S com.google.android.webview:sandboxed_process0Kiểm tra bộ nhớ của quy trình kết xuất (bằng PID):
adb shell dumpsys meminfo 14227Quan sát PSS TỔNG cao của quy trình kết xuất. Trong lần chạy mẫu của chúng tôi, kích thước này đã tăng lên ~55 MB sau một vài lần phân bổ. Xin lưu ý rằng các hoạt động phân bổ JavaScript (do công cụ V8 xử lý) thường đóng góp vào các phần Private Other (Riêng tư khác) hoặc Unknown (Không xác định) (mmap) của
dumpsys meminfo, thay vì Dalvik Heap.
Bài tập 2: Rò rỉ WebView phía Java
Một sai lầm thường gặp là giữ một thực thể WebView trong một trường tĩnh hoặc trong một đối tượng tồn tại lâu dài bị rò rỉ. Vì đối tượng WebView là một "neo" nặng giữ lại các tài nguyên gốc và có khả năng giữ lại toàn bộ quy trình kết xuất, nên việc rò rỉ đối tượng này sẽ rất tốn kém.

- Trong MemoryLab, hãy nhấn vào Launch WebView (Java Leak) (Khởi chạy WebView (Lỗi rò rỉ Java)).
- Hoạt động này sẽ tự động đóng sau khi trang được tải (mô phỏng thao tác điều hướng lặp lại và tích luỹ rò rỉ).
- Nhấn nút này 4 lần.
Kiểm tra số lượng phiên bản
WebViewtrong ứng dụng của bạn:adb shell dumpsys meminfo com.android.memorylabTìm mục Đối tượng ở dưới cùng. Bạn sẽ thấy số lượng
WebViewsđã tăng lên 4.Ví dụ về kết quả đầu ra (4 phiên bản bị rò rỉ) trên 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: 4Ghi tệp báo lỗi và sử dụng AHAT để tìm chỗ rò rỉ. Nếu không có
ahattrong đường dẫn, bạn có thể tạo đường dẫn này từ cây 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.hprofTrong giao diện web AHAT (
localhost:8888), hãy nhấp vào đường liên kết phân bổ (hoặc trang web) trong trình đơn trên cùng để xem mức sử dụng bộ nhớ tổng thể.
Tìm lớp
android.webkit.WebView. Nhấp vào số lượng phiên bản của phiên bản đó để xem tất cả các phiên bản đang hoạt động. Bạn sẽ thấy nhiều phiên bản trong danh sách.
Nhấp vào một trong các phiên bản
WebViewbị rò rỉ. Di chuyển xuống phần Sample Path from GC Root (Đường dẫn mẫu từ gốc GC). Bạn sẽ thấy danh sách này được giữ lại bởi danh sáchsLeakedWebViewstrongcom.android.memorylab.WebViewActivity.
← Gốc | ↑ Lên | Mã ứng dụng →