Hybrid App中WKWebView采坑浅谈

一、iOS 平台中 UIWebView 与 WKWebView 有什么区别?

UIWebView 是苹果继承于 UIView 封装的一个加载 web 内容的类,它可以加载任何远端的web数据展示在你的页面上,你可以像浏览器一样前进后退刷新等操作。不过苹果在 iOS8 以后推出了 WKWebView 来加载 Web,并应用于 iOS 和 OSX 中,它取代了 UIWebView 和 WebView ,在两个平台上支持同一套 API。
它脱离于 UIWebView 的设计,将原本的设计拆分成14个类,和3个代理协议,虽然是这样但是了解之后其实用法比较简单,依照职责单一的原则,每个协议做的事情根据功能分类。


  • WKWebView 与 UIWebView 的区别:
    WKWebView 的内存远远没有 UIWebView 的开销大,而且没有缓存;
    WKWebView 拥有高达 60FPS 滚动刷新率及内置手势;
    WKWebView 支持了更多的 HTML5 特性;
    WKWebView 高效的 app 和 web 信息交换通道;
    WKWebView 允许 JavaScript 的 Nitro 库加载并使用, UIWebView 中限制了;
    WKWebView 目前缺少关于页码相关的 API;
    WKWebView 提供加载网页进度的属性;
    WKWebView 使用 Safari 相同的 JavaScript 引擎;
    WKWebView 增加加载进度属性: estimatedProgress ;
    WKWebView 不支持页面缓存,需要自己注入 cookie , 而 UIWebView 是自动注入 cookie ;

  • WKWebView 无法发送 POST 参数问题;
    WKWebView 可以和js直接互调函数,不像 UIWebView 需要第三方库 WebViewJavascriptBridge 来协助处理和 js 的交互;

  • 注意:
    大多数App需要支持 iOS7 以上的版本,而 WKWebView 只在 iOS8 后才能用,所以需要一个兼容性方案,既 iOS7 下用 UIWebView , iOS8 后用 WKWebView 。但是目前 IOS10 以下的系统以及很少了,

  • 小结:
    WKWebView 相较于 UIWebView 在整体上有较大的提升,满足 iOS 上面使用同一套控件的功能,同时对整个内存的开销以及滚动刷新率和 JS 交互做了优化的处理。
    依据职责单一原则,拆分成了三个协议去实现 WebView 的响应,解耦了 JS 交互和加载进度的响应处理。
    WKWebView 没有做缓存处理,所以对网页需要缓存的加载性能要求没那么高的还是可以考虑 UIWebView 。

二、WKWebView 有哪一些坑?

  1. WKWebView 白屏问题
    WKWebView 实际上是个多进程组件,这也是它加载速度更快,内存暂用更低的原因。
    在 UIWebView 上当内存占用太大的时候,App Process 会 crash;而在 WKWebView 上当总体的内存占用比较大的时候,WebContent Process 会 crash,从而出现白屏现象。
  • 解决办法:
    a.借助 WKNavigtionDelegate
系统会调用回调函数:
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView;
我们在该函数里执行 [webView reload](这个时候 webView.URL 取值尚不为 nil)解决白屏问题。
在一些高内存消耗的页面可能会频繁刷新当前页面,H5侧也要做相应的适配操作。

b. 检测 webView.title 是否为空
并不是所有 H5 页面白屏的时候都会调用上面的回调函数,比如,最近遇到在一个高内存消耗的 H5 页面上 present 系统相机,拍照完毕后返回原来页面的时候出现白屏现象(拍照过程消耗了大量内存,导致内存紧张,WebContent Process 被系统挂起),但上面的回调函数并没有被调用。在 WKWebView 白屏的时候,另一种现象是 webView.titile 会被置空, 因此,可以在 viewWillAppear的时候检测 webView.title 是否为空来 reload 页面。

  1. WKWebView Cookie 问题
    WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie,而在 UIWebView 会自动带上 Cookie。
    原因是:
    WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中。
    实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在 iOS8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中,FireFox 工程师曾建议通过 resetWKProcessPool 来触发 Cookie 同步到 NSHTTPCookieStorage 中,实践发现不起作用,并可能会引发当前页面 session cookie丢失等问题。
  • 解决办法1:
    WKWebViewloadRequest 前,在 request header 中设置 Cookie, 解决首个请求 Cookie 带不上的问题;
  • 解决办法2:
    通过 document.cookie 设置 Cookie 解决后续页面(同域) Ajax``、iframe 请求的 Cookie 问题;(注意: document.cookie() 无法跨域设置 cookie)。
  1. WKWebView loadRequest 问题
    在 WKWebView 上通过 loadRequest 发起的 post 请求 body 数据会丢失,同样是由于进程间通信性能问题, HTTPBody 字段被丢弃。
  2. WKWebView NSURLProtocol问题
    WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在 WKWebView 上直接使用 NSURLProtocol 无法拦截请求。
  • 解决办法:
    由于 WKWebView 在独立进程里执行网络请求。一旦注册 http(s)scheme 后,网络请求将从 NetworkProcess 发送到 AppProcess,这样 NSURLProtocol 才能拦截网络请求。在 webkit2 的设计里使用 MessageQueue 进行进程之间的通信,Network Process 会将请求 encode 成一个 Message,然后通过 IPC 发送给 AppProcess。出于性能的原因, encode 的时候 HTTPBody 和 HTTPBodyStream 这两个字段会被丢弃掉了。
  1. WKWebView 页面样式问题
    在 WKWebView 适配过程中,我们发现部分 H5 页面元素位置向下偏移或被拉伸变形,追踪后发现主要是 H5 页面高度值异常导致。
  • 解决办法:
    调整 WKWebView 布局方式,避免调整 webView.scrollView.contentInset 。实际上,即便在UIWebView 上也不建议直接调整 webView.scrollView.contentInset 的值,这确实会带来一些奇怪的问题。如果某些特殊情况下非得调整 contentInset 不可的话,可以通过下面方式让H5页面恢复正常显示。
  1. WKWebView 截屏问题
    WKWebView 下通过 -[CALayer renderInContext:]实现截屏的方式失效,需要通过以下方式实现截屏功能:
@implementation UIView (ImageSnapshot)
- (UIImage*)imageSnapshot {    
    UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES,self.contentScaleFactor);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
@end

然而这种方式依然解决不了 webGL 页面的截屏问题,截屏结果不是空白就是纯黑图片。

  • 解决办法:
    无奈之下,我们只能约定一个JS接口,让游戏开发商实现该接口,具体是通过 canvas getImageData()方法取得图片数据后返回 base64 格式的数据,客户端在需要截图的时候,调用这个JS接口获取 base64String并转换成 UIImage。
  1. WKWebView crash问题
    如果 WKWebView 退出的时候,JS刚好执行了 window.alert(), alert 框可能弹不出来, completionHandler 最后没有被执行,导致 crash;
    另一种情况是在 WKWebView 一打开,JS就执行 window.alert(),这个时候由于 WKWebView 所在的 UIViewController 出现( push 或 present )的动画尚未结束,alert 框可能弹不出来, completionHandler最后没有被执行,导致 crash。
  2. 视频自动播放
    WKWebView 需要通过 WKWebViewConfiguration.mediaPlaybackRequiresUserAction 设置是否允许自动播放,但一定要在 WKWebView 初始化之前设置,在 WKWebView 初始化之后设置无效。
  3. goBack API问题
    WKWebView 上调用 -[WKWebViewgoBack], 回退到上一个页面后不会触发 window.onload() 函数、不会执行JS。
  4. 页面滚动速率
    WKWebView 需要通过 scrollViewdelegate 调整滚动速率:
- (void )scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}

四、常见的 WebView 性能优化方案有哪一些?

  1. 问题分析
    首先需要了解,对于一个普通用户来讲,打开一个 WebView 通常会经历哪几个阶段,一般有这些:
    a.交互无反馈;
    b.到达新的页面,页面白屏;
    c.页面基本框架出现,但是没有数据;页面处于loading状态;
    出现所需的数据;

当 App 首次打开时,默认是并不初始化浏览器内核的;只有当创建 WebView 实例的时候,才会创建 WebView 的基础框架。
所以与浏览器不同,App 中打开 WebView 的第一步并不是建立连接,而是启动浏览器内核。
于是我们找到了“为什么WebView总是很慢”的原因之一:

而在客户端中,客户端需要先花费时间初始化 WebView 完成后,才开始加载。
而这段时间,由于WebView还不存在,所有后续的过程是完全阻塞的。
因此由于这段过程发生在 native 的代码中,单纯靠前端代码是无法优化的;

大部分的方案都是前端和客户端协作完成,以下是几个业界采用过的方案:

  1. 全局 WebView池
  2. WebView 预加载
  3. 独立的web进程,与主进程隔开
  4. WebView 释放防止内存泄露

参考文章:[《UIWebView与WKWebView》] (http://www.cocoachina.com/articles/17337)
参考文章:[《WKWebView 那些坑》] (https://kknews.cc/tech/x2rzg3g.html)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容