WebView 优化实战
场景
App 有大量 H5 页面通过 WebView 加载,首次打开白屏 2 秒以上。
排查与方案
1. WebView 加载耗时分析
| 阶段 | 典型耗时 | 优化方向 |
|---|---|---|
| WebView 初始化 | 200-500ms | 预创建 |
| 网络请求 | 300-1000ms | 预加载 / 离线包 |
| JS 解析执行 | 200-500ms | 预执行 / 瘦身 |
| 渲染 | 100-300ms | 骨架屏 |
2. 优化方案
// 1. WebView 预创建池:提前初始化,使用时直接取
object WebViewPool {
private val pool = LinkedList<WebView>()
private const val MAX_SIZE = 2
fun preload(context: Context) {
val appContext = context.applicationContext
repeat(MAX_SIZE) {
pool.add(WebView(MutableContextWrapper(appContext)))
}
}
fun obtain(context: Context): WebView {
val webView = if (pool.isNotEmpty()) pool.removeFirst() else WebView(context)
// 替换 Context 为当前 Activity
(webView.context as? MutableContextWrapper)?.baseContext = context
return webView
}
fun recycle(webView: WebView) {
webView.loadUrl("about:blank") // 清空页面
webView.clearHistory()
if (pool.size < MAX_SIZE) pool.add(webView)
}
}
// 2. 离线包方案:将 H5 资源打包到本地,拦截请求
class OfflineInterceptor : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView, request: WebResourceRequest
): WebResourceResponse? {
val url = request.url.toString()
// 匹配到离线资源则直接返回本地文件
val localFile = OfflinePackageManager.match(url)
if (localFile != null) {
return WebResourceResponse(
getMimeType(url),
"UTF-8",
localFile.inputStream()
)
}
return super.shouldInterceptRequest(view, request)
}
}
3. JSBridge 通信
// Native 注册方法供 JS 调用
webView.addJavascriptInterface(object {
@JavascriptInterface
fun getUserInfo(): String {
return Gson().toJson(UserManager.getUser())
}
}, "NativeBridge")
// JS 侧调用
// window.NativeBridge.getUserInfo()
安全
@JavascriptInterface 注解的方法暴露给 JS 调用,务必校验输入,不暴露敏感接口。Android 4.2 以下未加注解也能被调用,有注入风险。
面试答题要点
- WebView 预创建是最有效的优化(省掉初始化 200-500ms)
- 离线包拦截网络请求,消除网络耗时
- JSBridge 的实现方式和安全注意事项
- WebView 内存泄漏的处理(独立进程 / 手动 destroy)