前言
我们大前端团队内部 ?每周一练 的知识复习计划继续加油,本篇文章是 《Hybrid APP 混合应用专题》 主题的第二期和第三期的合集。
这一期共整理了 10 个问题,和相应的参考答案,文字和图片较多,建议大家可以收藏,根据文章目录来阅读。
之前分享的每周内容,我都整理到掘金收藏集 ?《EFT每周一练》 上啦,欢迎点赞收藏咯??。
内容回顾:
文章收录:
本系列所有文章,都将收录在 Github 上,欢迎点击查阅。
注:本文整理部分资料来源网络,有些图片/段落找不到原文出处,如有侵权,联系删除。
一、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 有哪一些坑?
参考文章:《WKWebView 那些坑》
1. WKWebView 白屏问题
WKWebView
实际上是个多进程组件,这也是它加载速度更快,内存暂用更低的原因。
在 UIWebView
上当内存占用太大的时候,App Process 会 crash;而在 WKWebView
上当总体的内存占用比较大的时候,WebContent Process 会 crash,从而出现白屏现象。
解决办法:
- 借助 WKNavigtionDelegate
当 WKWebView
总体内存占用过大,页面即将白屏的时候,系统会调用上面的回调函数,我们在该函数里执行[webView reload]
(这个时候 webView.URL
取值尚不为nil
)解决白屏问题。在一些高内存消耗的页面可能会频繁刷新当前页面,H5侧也要做相应的适配操作。
- 检测 webView.title 是否为空
并不是所有 H5 页面白屏的时候都会调用上面的回调函数,比如,最近遇到在一个高内存消耗的 H5 页面上 present 系统相机,拍照完毕后返回原来页面的时候出现白屏现象(拍照过程消耗了大量内存,导致内存紧张,WebContent Process 被系统挂起),但上面的回调函数并没有被调用。在 WKWebView
白屏的时候,另一种现象是 webView.titile
会被置空, 因此,可以在 viewWillAppear
的时候检测 webView.title
是否为空来 reload
页面。
2. WKWebView Cookie 问题
WKWebView
Cookie
问题在于 WKWebView
发起的请求不会自动带上存储于 NSHTTPCookieStorage
容器中的 Cookie
,而在 UIWebView
会自动带上 Cookie
。
原因是:
WKWebView
拥有自己的私有存储,不会将 Cookie
存入到标准的 Cookie
容器NSHTTPCookieStorage
中。
实践发现 WKWebView
实例其实也会将 Cookie
存储于 NSHTTPCookieStorage
中,但存储时机有延迟,在 iOS 8
上,当页面跳转的时候,当前页面的 Cookie
会写入 NSHTTPCookieStorage
中,而在 iOS 10
上,JS 执行 document.cookie
或服务器 set-cookie
注入的 Cookie
会很快同步到 NSHTTPCookieStorage
中,FireFox 工程师曾建议通过 reset WKProcessPool
来触发 Cookie
同步到 NSHTTPCookieStorage
中,实践发现不起作用,并可能会引发当前页面 session cookie
丢失等问题。
解决办法1:
WKWebView loadRequest
前,在 request header
中设置 Cookie
, 解决首个请求 Cookie
带不上的问题;
解决办法2:
通过 document.cookie
设置 Cookie
解决后续页面(同域)Ajax``、iframe
请求的 Cookie
问题;(注意:document.cookie()
无法跨域设置 cookie
)。
3. WKWebView loadRequest 问题
在 WKWebView
上通过 loadRequest
发起的 post
请求 body
数据会丢失,同样是由于进程间通信性能问题, HTTPBody
字段被丢弃。
4. WKWebView NSURLProtocol问题
WKWebView
在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此,在 WKWebView
上直接使用 NSURLProtocol
无法拦截请求。
解决办法:
由于 WKWebView
在独立进程里执行网络请求。一旦注册 http(s) scheme
后,网络请求将从 Network Process
发送到 App Process
,这样 NSURLProtocol
才能拦截网络请求。在 webkit2
的设计里使用 MessageQueue
进行进程之间的通信,Network Process 会将请求 encode
成一个 Message
,然后通过 IPC 发送给App Process
。出于性能的原因,encode
的时候 HTTPBody
和 HTTPBodyStream
这两个字段会被丢弃掉了。
5. WKWebView 页面样式问题
在 WKWebView
适配过程中,我们发现部分 H5 页面元素位置向下偏移或被拉伸变形,追踪后发现主要是 H5 页面高度值异常导致。
解决办法:
调整 WKWebView
布局方式,避免调整webView.scrollView.contentInset
。实际上,即便在 UIWebView
上也不建议直接调整 webView.scrollView.contentInset
的值,这确实会带来一些奇怪的问题。如果某些特殊情况下非得调整 contentInset
不可的话,可以通过下面方式让H5页面恢复正常显示。
6. 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接口获取base64 String
并转换成 UIImage
。<