一、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)