WebView 优化实战
问题
App 内 WebView 加载慢、白屏时间长,如何优化?
答案
优化手段
| 手段 | 说明 | 收益 |
|---|---|---|
| 预创建 WebView 池 | App 启动时创建 WKWebView 实例 | 减少 500ms+ |
| 离线包 | 关键 H5 页面资源打包到 App 内 | 减少网络耗时 |
| 预加载 | 提前 loadRequest | 用户点击时秒开 |
| DNS 预解析 | 预连接 H5 域名 | 减少 DNS 耗时 |
| JSBridge 优化 | 减少通信次数,批量传输 | 减少交互延迟 |
WebView 池
class WebViewPool {
static let shared = WebViewPool()
private var pool: [WKWebView] = []
func warmUp(count: Int = 2) {
for _ in 0..<count {
let config = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: config)
webView.loadHTMLString("", baseURL: nil) // 触发内核初始化
pool.append(webView)
}
}
func dequeue() -> WKWebView {
if let webView = pool.first {
pool.removeFirst()
return webView
}
return WKWebView()
}
func recycle(_ webView: WKWebView) {
webView.loadHTMLString("", baseURL: nil)
pool.append(webView)
}
}
离线包方案
// 拦截 WKWebView 请求,返回本地资源
class OfflineHandler: WKURLSchemeHandler {
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url,
let localData = loadLocalResource(for: url) else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let response = URLResponse(url: url, mimeType: mimeType(for: url),
expectedContentLength: localData.count, textEncodingName: "utf-8")
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(localData)
urlSchemeTask.didFinish()
}
}
常见面试问题
Q1: WKWebView 和 UIWebView 的区别?
答案:WKWebView 运行在独立进程,内存占用不算在 App 进程内,不易 OOM。UIWebView 已在 iOS 12 废弃。WKWebView 支持 WKURLSchemeHandler 自定义协议拦截,但不支持 NSURLProtocol。