WebView 是一种强大的组件,可让您在 Android 应用中显示网页内容。不过,由于它本质上是一个功能齐全的浏览器引擎 (Chromium),因此具有很大的内存占用空间和复杂的多进程架构。
技术背景:多进程架构
在现代 Android 设备上,WebView 使用多进程模型来提高安全性和稳定性。当应用使用 WebView 时,内存会分布在不同的进程中:
- 浏览器进程(应用进程):这是应用的主进程。它包含 Java
WebView对象和 Chromium 引擎的“浏览器”部分。此进程管理界面、网络请求和 GPU 渲染(直接与 Android HWUI 渲染流水线集成)。 与 Chrome 不同,WebView 没有单独的 GPU 进程。 - 渲染器进程:此进程负责解析 HTML、执行 JavaScript 和布局。为了安全起见,它与系统的其余部分隔离开来。目前,应用的所有 WebView 都只获得一个渲染器进程(极少数特殊情况除外),这与 Chrome 经常为不同网站使用单独的渲染器进程不同。

这对记忆有何影响
使用 dumpsys meminfo <your_package> 时,您只会看到浏览器进程(您的应用进程)使用的内存。渲染器进程使用的内存单独计算。
在浏览器进程中,WebView 内存的分布情况如下:
- Java 堆:包含
WebViewJava 封装容器和相关对象。 - 原生堆:包含 Chromium 浏览器引擎的内部数据结构、缓存和状态。请注意,由于使用了 PartitionAlloc,某些 WebView 原生分配可能不会在
dumpsys meminfo的“原生堆”下进行统计,而是可能会显示在“其他”或“未知”下。 - 共享内存:用于共享图形缓冲区和其他数据。
dumpsys meminfo可能无法明确归类。
问题排查工具
Chrome DevTools
用于分析 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。找到您的应用,然后点击检查。
在开发者工具窗口中,前往 Memory(内存)标签页,以拍摄堆快照或记录 JavaScript 堆的分配时间轴。
dumpsys meminfo
使用 adb shell dumpsys meminfo --all <package> 可查看内存细分情况。
在输出中查找 WebView 类别和对象数量。
分析渲染器
由于渲染器在单独的进程中运行,因此您无法仅通过剖析应用来剖析其原生堆。您必须专门识别渲染器进程的 PID。
在多个 WebView 处于有效状态时,识别正确的渲染器 PID:
使用
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% 的情况下都很难保证这一点。即使自动垃圾回收机制正常运行,也可能会出现明显的延迟,导致应用保留资源的时间比预期长得多。
如果应用在适当的时间(例如在 Activity.onDestroy() 中)调用 WebView.destroy(),那么保留对 WebView 对象本身的引用不会泄漏任何重要的原生资源。在销毁 WebView 对象后,无需严格将 Activity 字段中对该对象的引用设为 null,因为当 Activity 本身被垃圾回收时,该对象也会被清理。
练习:WebView 内存实操
练习 1:观察多进程占用空间
启动 MemoryLab 并对应用的内存使用情况进行基准测量:
adb shell dumpsys meminfo com.android.memorylab示例基准值(范围):
TOTAL PSS: 18915 KB点按 Launch WebView (Normal)。
在 WebView 中,点按 Allocate JS Memory (1000 DIVs) 几次。
再次检查应用的内存:
adb shell dumpsys meminfo com.android.memorylab观察到应用进程中的内存与基准相比没有明显增加!这是因为 DOM 元素位于 Renderer 进程中。
查找渲染器进程:
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 引擎处理)通常会影响
dumpsys meminfo的 Private Other 或 Unknown (mmap) 部分,而不是 Dalvik 堆。
练习 2:Java 端 WebView 泄漏
一个常见错误是在静态字段或泄漏的长生命周期对象中保留 WebView 实例。由于 WebView 对象是一个“锚点”,它会占用原生资源,甚至整个渲染器进程,因此泄露该对象会造成非常严重的后果。

- 在 MemoryLab 中,点按 Launch WebView (Java Leak)。
- 在网页加载完毕后,该 activity 会自动关闭(模拟重复导航和内存泄漏累积)。
- 点按该按钮 4 次。
检查应用中的
WebView实例数:adb shell dumpsys meminfo com.android.memorylab找到底部的对象部分。您会看到
WebViews的数量已增加到 4。rango 上的示例输出(4 个泄露的实例):
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 Web 界面 (
localhost:8888) 中,点击顶部菜单中的分配链接(或网站),查看总体内存用量。
搜索
android.webkit.WebView类。点击其实例数量即可查看所有实时实例。您应该会在列表中看到多个实例。
点击泄露的某个
WebView实例。向下滚动到 Sample Path from GC Root 部分。您会看到它被com.android.memorylab.WebViewActivity中的sLeakedWebViews列表所持有。