WKWebView问题优化指南了
2021-05-20 本文已影响0人
YY110
一、Cookie不能同步和丢失
- 首次加载页面的时候带不上 Cookie;
- Ajax 类型的请求带不上 Cookie;
- 跳转页面时(status code 302) 带不上 Cookie;
- 前端设置 document.cookie 不能及时被同步。
解决方案
- 首次加载的时候手动设置 request header field 'Cookie';
- 通过 hook js document.cookie setter and getter 方法,注入 名字为 cookie 的 bridge,当 js 端有cookie变更的时候会及时通知 native,从而实现和 js 端同步 cookie;
- Ajax 的问题采用注入 document.cookie = name=value; 脚本解决;
二、不支持 NSURLProtocol 拦截
- iOS 11 之前的处理 (call private API) ;
- iOS 11 之后的处理(hook public API)。
解决方案
- iOS 11 之前采用 [WKBrowsingContextController registerSchemeForCustomProtocol:@"https"] 私有 API 实现;
- iOS 11 之后采用 hook +handlesURLScheme 使之 -setURLSchemeHandler:forURLScheme 支持 http 和 https 注册;
三、POST 类型请求 Request Body 丢失
当使用网络拦截后,WebKit的IPC进程的请求会转到主进程执行,由于进程切换回导致性能下降,所以WebKit会主动丢弃request的body。这对我们来说,是不可接受的,有想要网络拦截,还想要body。有什么办法能解决这个问题呢?这样根据不同的iOS版本分开来看。
- iOS 11 之后采用 hook public API 的方式;
- iOS 11 之前采用 hook public API 和 hook js XHR。
解决方案
- 在 NSURLProtocol 子类中的 - startLoading 通过获取 request.HTTPBodyStream 来填充 request.HTTPBody 实现。但是此方法在有些时候(如Ajax)会失败;
- 通过 hook js XMLHTTPRequest 相关方法实现。具体的来说,注入 XHR requestType 为 post 的 send 方法,把 requstBody 和对应的 URL 通过桥的方式提前传给客户端,客户端接收端数据后,保存起来,在 NSURLProtocol 子类中的 -initWithRequest:cachedResponse:client: 方法拦截中填充保存在客户端的 requstBody 到 request 中,即可保证 requestBody 不会丢失;
四、白屏问题
- 内存占用过大导致 WebContent process 进程崩溃;
- 内存占用过大导致 WebContent process 被挂起。
解决方案
- 当 WKWebView 占用内存过大的时候,会导致 WebContent process crash,会回调 -webViewWebContentProcessDidTerminate:,可在此方法中添加 [webView reload],重新载入页面解决;
- 当 WKWebView 占用内存过大的时候(多见于选择相册) ,会导致 WebContent process 被挂起,此情况不会调用 -webViewWebContentProcessDidTerminate:,可通过在 WebViewController 的 -viewWillAppear: 方法中检测 webView.title 是否存在,如果不存在可认为进程被挂起,可在此方法中添加 [webView reload],重新载入页面解决;
五、无法通过 URL Scheme 方式打开三方 App
WKWebView 限制了 WEB 页面打开三方 APP 的能力,必须在代理方法 - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 中拦截,判断 URL 中的 Scheme 或 host,然后通过 [[UIApplication sharedApplication] openURL:] 方法打开。
需要注意的是,对于三方APP,通过 Scheme 来判断即可,如高德地图的 scheme 是 iosamaps。但对于 iOS 部分自带 App,则需要通过 URL host 判断,如 AppStore 的 host 是 http://itunes.apple.com。
剩余问题
据网上文章描述,有的情况用以上的两种解决方案不可行。可通过遍历 webView.subviews,是否包含 WKCompositingView 来判断白屏情况,如果是则收回旧 webView,重新创建新的 webView。但是此方法是否可行,暂未验证,目前也未采用。
- (BOOL)isBlankView:(UIView *)view { // YES:blank
Class wkCompositingView =NSClassFromString(@"WKCompositingView");
if ([view isKindOfClass:[wkCompositingView class]]) {
return NO;
}
for(UIView *subView in view.subviews) {
if (![self isBlankView:subView]) {
return NO;
}
}
return YES;
}