一、iOS 平臺中 UIWebView 與 WKWebView 有什么區(qū)別?
UIWebView 是蘋果繼承于 UIView 封裝的一個加載 web 內(nèi)容的類,它可以加載任何遠(yuǎn)端的web數(shù)據(jù)展示在你的頁面上,你可以像瀏覽器一樣前進(jìn)后退刷新等操作。不過蘋果在 iOS8 以后推出了 WKWebView 來加載 Web,并應(yīng)用于 iOS 和 OSX 中,它取代了 UIWebView 和 WebView ,在兩個平臺上支持同一套 API。
它脫離于 UIWebView 的設(shè)計,將原本的設(shè)計拆分成14個類,和3個代理協(xié)議,雖然是這樣但是了解之后其實用法比較簡單,依照職責(zé)單一的原則,每個協(xié)議做的事情根據(jù)功能分類。
WKWebView 與 UIWebView 的區(qū)別:
WKWebView 的內(nèi)存遠(yuǎn)遠(yuǎn)沒有 UIWebView 的開銷大,而且沒有緩存;
WKWebView 擁有高達(dá) 60FPS 滾動刷新率及內(nèi)置手勢;
WKWebView 支持了更多的 HTML5 特性;
WKWebView 高效的 app 和 web 信息交換通道;
WKWebView 允許 JavaScript 的 Nitro 庫加載并使用, UIWebView 中限制了;
WKWebView 目前缺少關(guān)于頁碼相關(guān)的 API;
WKWebView 提供加載網(wǎng)頁進(jìn)度的屬性;
WKWebView 使用 Safari 相同的 JavaScript 引擎;
WKWebView 增加加載進(jìn)度屬性: estimatedProgress ;
WKWebView 不支持頁面緩存,需要自己注入 cookie , 而 UIWebView 是自動注入 cookie ;WKWebView 無法發(fā)送 POST 參數(shù)問題;
WKWebView 可以和js直接互調(diào)函數(shù),不像 UIWebView 需要第三方庫 WebViewJavascriptBridge 來協(xié)助處理和 js 的交互;注意:
大多數(shù)App需要支持 iOS7 以上的版本,而 WKWebView 只在 iOS8 后才能用,所以需要一個兼容性方案,既 iOS7 下用 UIWebView , iOS8 后用 WKWebView 。但是目前 IOS10 以下的系統(tǒng)以及很少了,小結(jié):
WKWebView 相較于 UIWebView 在整體上有較大的提升,滿足 iOS 上面使用同一套控件的功能,同時對整個內(nèi)存的開銷以及滾動刷新率和 JS 交互做了優(yōu)化的處理。
依據(jù)職責(zé)單一原則,拆分成了三個協(xié)議去實現(xiàn) WebView 的響應(yīng),解耦了 JS 交互和加載進(jìn)度的響應(yīng)處理。
WKWebView 沒有做緩存處理,所以對網(wǎng)頁需要緩存的加載性能要求沒那么高的還是可以考慮 UIWebView 。
二、WKWebView 有哪一些坑?
- WKWebView 白屏問題
WKWebView 實際上是個多進(jìn)程組件,這也是它加載速度更快,內(nèi)存暫用更低的原因。
在 UIWebView 上當(dāng)內(nèi)存占用太大的時候,App Process 會 crash;而在 WKWebView 上當(dāng)總體的內(nèi)存占用比較大的時候,WebContent Process 會 crash,從而出現(xiàn)白屏現(xiàn)象。
- 解決辦法:
a.借助 WKNavigtionDelegate
系統(tǒng)會調(diào)用回調(diào)函數(shù):
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView;
我們在該函數(shù)里執(zhí)行 [webView reload](這個時候 webView.URL 取值尚不為 nil)解決白屏問題。
在一些高內(nèi)存消耗的頁面可能會頻繁刷新當(dāng)前頁面,H5側(cè)也要做相應(yīng)的適配操作。
b. 檢測 webView.title 是否為空
并不是所有 H5 頁面白屏的時候都會調(diào)用上面的回調(diào)函數(shù),比如,最近遇到在一個高內(nèi)存消耗的 H5 頁面上 present 系統(tǒng)相機(jī),拍照完畢后返回原來頁面的時候出現(xiàn)白屏現(xiàn)象(拍照過程消耗了大量內(nèi)存,導(dǎo)致內(nèi)存緊張,WebContent Process 被系統(tǒng)掛起),但上面的回調(diào)函數(shù)并沒有被調(diào)用。在 WKWebView 白屏的時候,另一種現(xiàn)象是 webView.titile 會被置空, 因此,可以在 viewWillAppear的時候檢測 webView.title 是否為空來 reload 頁面。
- WKWebView Cookie 問題
WKWebView Cookie 問題在于 WKWebView 發(fā)起的請求不會自動帶上存儲于 NSHTTPCookieStorage 容器中的 Cookie,而在 UIWebView 會自動帶上 Cookie。
原因是:
WKWebView 擁有自己的私有存儲,不會將 Cookie 存入到標(biāo)準(zhǔn)的 Cookie 容器 NSHTTPCookieStorage 中。
實踐發(fā)現(xiàn) WKWebView 實例其實也會將 Cookie 存儲于 NSHTTPCookieStorage 中,但存儲時機(jī)有延遲,在 iOS8上,當(dāng)頁面跳轉(zhuǎn)的時候,當(dāng)前頁面的 Cookie 會寫入 NSHTTPCookieStorage 中,而在 iOS10 上,JS 執(zhí)行 document.cookie 或服務(wù)器 set-cookie 注入的 Cookie 會很快同步到 NSHTTPCookieStorage 中,F(xiàn)ireFox 工程師曾建議通過 resetWKProcessPool 來觸發(fā) Cookie 同步到 NSHTTPCookieStorage 中,實踐發(fā)現(xiàn)不起作用,并可能會引發(fā)當(dāng)前頁面 session cookie丟失等問題。
- 解決辦法1:
WKWebViewloadRequest 前,在 request header 中設(shè)置 Cookie, 解決首個請求 Cookie 帶不上的問題; - 解決辦法2:
通過 document.cookie 設(shè)置 Cookie 解決后續(xù)頁面(同域) Ajax``、iframe 請求的 Cookie 問題;(注意: document.cookie() 無法跨域設(shè)置 cookie)。
- WKWebView loadRequest 問題
在 WKWebView 上通過 loadRequest 發(fā)起的 post 請求 body 數(shù)據(jù)會丟失,同樣是由于進(jìn)程間通信性能問題, HTTPBody 字段被丟棄。 - WKWebView NSURLProtocol問題
WKWebView 在獨立于 app 進(jìn)程之外的進(jìn)程中執(zhí)行網(wǎng)絡(luò)請求,請求數(shù)據(jù)不經(jīng)過主進(jìn)程,因此,在 WKWebView 上直接使用 NSURLProtocol 無法攔截請求。
- 解決辦法:
由于 WKWebView 在獨立進(jìn)程里執(zhí)行網(wǎng)絡(luò)請求。一旦注冊 http(s)scheme 后,網(wǎng)絡(luò)請求將從 NetworkProcess 發(fā)送到 AppProcess,這樣 NSURLProtocol 才能攔截網(wǎng)絡(luò)請求。在 webkit2 的設(shè)計里使用 MessageQueue 進(jìn)行進(jìn)程之間的通信,Network Process 會將請求 encode 成一個 Message,然后通過 IPC 發(fā)送給 AppProcess。出于性能的原因, encode 的時候 HTTPBody 和 HTTPBodyStream 這兩個字段會被丟棄掉了。
- WKWebView 頁面樣式問題
在 WKWebView 適配過程中,我們發(fā)現(xiàn)部分 H5 頁面元素位置向下偏移或被拉伸變形,追蹤后發(fā)現(xiàn)主要是 H5 頁面高度值異常導(dǎo)致。
- 解決辦法:
調(diào)整 WKWebView 布局方式,避免調(diào)整 webView.scrollView.contentInset 。實際上,即便在UIWebView 上也不建議直接調(diào)整 webView.scrollView.contentInset 的值,這確實會帶來一些奇怪的問題。如果某些特殊情況下非得調(diào)整 contentInset 不可的話,可以通過下面方式讓H5頁面恢復(fù)正常顯示。
- WKWebView 截屏問題
WKWebView 下通過 -[CALayer renderInContext:]實現(xiàn)截屏的方式失效,需要通過以下方式實現(xiàn)截屏功能:
@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 頁面的截屏問題,截屏結(jié)果不是空白就是純黑圖片。
- 解決辦法:
無奈之下,我們只能約定一個JS接口,讓游戲開發(fā)商實現(xiàn)該接口,具體是通過 canvas getImageData()方法取得圖片數(shù)據(jù)后返回 base64 格式的數(shù)據(jù),客戶端在需要截圖的時候,調(diào)用這個JS接口獲取 base64String并轉(zhuǎn)換成 UIImage。
- WKWebView crash問題
如果 WKWebView 退出的時候,JS剛好執(zhí)行了 window.alert(), alert 框可能彈不出來, completionHandler 最后沒有被執(zhí)行,導(dǎo)致 crash;
另一種情況是在 WKWebView 一打開,JS就執(zhí)行 window.alert(),這個時候由于 WKWebView 所在的 UIViewController 出現(xiàn)( push 或 present )的動畫尚未結(jié)束,alert 框可能彈不出來, completionHandler最后沒有被執(zhí)行,導(dǎo)致 crash。 - 視頻自動播放
WKWebView 需要通過 WKWebViewConfiguration.mediaPlaybackRequiresUserAction 設(shè)置是否允許自動播放,但一定要在 WKWebView 初始化之前設(shè)置,在 WKWebView 初始化之后設(shè)置無效。 - goBack API問題
WKWebView 上調(diào)用 -[WKWebViewgoBack], 回退到上一個頁面后不會觸發(fā) window.onload() 函數(shù)、不會執(zhí)行JS。 - 頁面滾動速率
WKWebView 需要通過 scrollViewdelegate 調(diào)整滾動速率:
- (void )scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}
四、常見的 WebView 性能優(yōu)化方案有哪一些?
- 問題分析
首先需要了解,對于一個普通用戶來講,打開一個 WebView 通常會經(jīng)歷哪幾個階段,一般有這些:
a.交互無反饋;
b.到達(dá)新的頁面,頁面白屏;
c.頁面基本框架出現(xiàn),但是沒有數(shù)據(jù);頁面處于loading狀態(tài);
出現(xiàn)所需的數(shù)據(jù);
當(dāng) App 首次打開時,默認(rèn)是并不初始化瀏覽器內(nèi)核的;只有當(dāng)創(chuàng)建 WebView 實例的時候,才會創(chuàng)建 WebView 的基礎(chǔ)框架。
所以與瀏覽器不同,App 中打開 WebView 的第一步并不是建立連接,而是啟動瀏覽器內(nèi)核。
于是我們找到了“為什么WebView總是很慢”的原因之一:
而在客戶端中,客戶端需要先花費時間初始化 WebView 完成后,才開始加載。
而這段時間,由于WebView還不存在,所有后續(xù)的過程是完全阻塞的。
因此由于這段過程發(fā)生在 native 的代碼中,單純靠前端代碼是無法優(yōu)化的;
大部分的方案都是前端和客戶端協(xié)作完成,以下是幾個業(yè)界采用過的方案:
- 全局 WebView池
- WebView 預(yù)加載
- 獨立的web進(jìn)程,與主進(jìn)程隔開
- WebView 釋放防止內(nèi)存泄露
參考文章:[《UIWebView與WKWebView》] (http://www.cocoachina.com/articles/17337)
參考文章:[《WKWebView 那些坑》] (https://kknews.cc/tech/x2rzg3g.html)