前言
在以前,一直以為Hybrid App開發是一種略顯簡單的事,不會使用太多能發揮移動端原生本身優勢的復雜API,后來在新公司的工作(半混合式開發)過程中,發現混合式開發也是很多坑... 或者說WKWebView好多坑...
以下所說的內容,參考鏈接上基本上都有,本文的敘述方式主要是結合自己的經歷(自己踩過的總結總是那么的深刻...[捂臉])
應該在開始混合開發之前就看下這篇文章的,結果真的是等自己踩坑踩了一遍,總結之后,發現這篇文章上都有....[大哭]
參考鏈接2: http://www.lxweimin.com/p/86d99192df68
目錄
- 加載URL的 encode問題
- loadRequest造成的body數據丟失
- 使用WKUserContentController造成的內存泄漏問題
- WKWebView的白屏問題(拍照引起)
- NSURLProtocol(做網頁緩存)
- WKWebView的截屏問題(做意見反饋)
- window.alert()引起的crash問題(暫時沒遇到)
- WKWebView攔截協議
- User-Agent修改
- UI細節問題
. wkwebview中 h5絕對布局不生效
. iOS 12中WKWebView中表單 鍵盤彈起自動上移,導致的兼容問題
. WKWebview會下移20
. 頁面滾動速率
. 視頻自動播放
. goBack API問題
· didFinishNavigation遲遲不調用 - iOS 11系統上,WKWebView內H5標簽不可點擊,Native不受影響
1. 加載URL的 encode問題
在數據網絡請求或其他情況下,需要把URL中的一些特殊字符轉換成UTF-8編碼,比如:中文。解決無法加載
的問題
編碼:
iOS 9以前
stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding
ios9后對其方法進行了修改
stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLQueryAllowedCharacterSet]
解碼
iOS 9以前
stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding
iOS 9以后
stringByRemovingPercentEncoding
總結:混合開發中,最好將所有的URL的編解碼問題都交給前端或者后端來做
,畢竟移動端發版太笨重了,最起碼保證iOS與Android兩端的處理一致
,否則前端同學做處理就太麻煩了
2. loadRequest造成的body數據丟失
在 WKWebView 上通過 loadRequest 發起的 post 請求 body 數據會丟失:
//同樣是由于進程間通信性能問題,HTTPBody字段被丟棄[request setHTTPMethod:@"POST"];
[request setHTTPBody:[@"bodyData" dataUsingEncoding:NSUTF8StringEncoding]];
[wkwebview loadRequest: request];
解決方案:見參考鏈接,目前暫無使用場景
3. 使用WKUserContentController
造成的內存泄漏問題
self -> webView -> WKWebViewConfiguration -> WKUserContentController -> self (addScriptMessageHandler)
__weak typeof(self) copy_self = self;
addScriptMessageHandler: copy_self //不能解決問題
解決方案:
單獨創建一個類實現`WKScriptMessageHandler`協議,然后在該類中再創建一個協議,由self來實現協議
即: `self -> webView -> WKWebViewConfiguration -> WKUserContentController -> weak delegate obj --delegate--> self `
示例代碼:
1.創建一個新類WeakScriptMessageDelegate
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@interface WeakScriptMessageDelegate : NSObject
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate;
@end
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
self = [super init];
if (self) {
_scriptDelegate = scriptDelegate;
}
return self;
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
[self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
@end
2.在我們使用WKWebView的控制器中引入我們創建的那個類,將注入js對象的代碼改為:
[config.userContentController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:scriptMessage];
3.在delloc方法中通過下面的方式移除注入的js對象
[self.config.userContentController removeScriptMessageHandlerForName:scriptMessage];
上面三步就可以解決控制器不能被釋放的問題了
4. WKWebView的白屏問題(拍照引起)
WKWebView 自詡擁有更快的加載速度,更低的內存占用,但實際上 WKWebView 是一個多進程組件,Network Loading 以及 UI Rendering 在其它進程中執行。
換WKWebView加載網頁后,App 進程內存消耗反而大幅下降,但是仔細觀察會發現,Other Process 的內存占用會增加。在一些用 webGL 渲染的復雜頁面,使用 WKWebView 總體的內存占用(App Process Memory + Other Process Memory)不見得比 UIWebView 少很多。
在 UIWebView 上當內存占用太大的時候,App Process 會 crash;
而在 WKWebView 上當總體的內存占用比較大的時候,WebContent Process 會 crash,從而出現白屏現象
# 解決方案:
- 借助 iOS 9以后
WKNavigtionDelegate
新增了一個回調函數:
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));
當 WKWebView 總體內存占用過大,頁面即將白屏的時候,系統會調用上面的回調函數,我們在該函數里執行[webView reload]
(這個時候 webView.URL 取值尚不為 nil)解決白屏問題。在一些高內存消耗的頁面可能會頻繁刷新當前頁面,H5側也要做相應的適配操作。
- 檢測 webView.title 是否為空
并不是所有H5頁面白屏的時候都會調用上面的回調函數,比如,最近遇到在一個高內存消耗的意見反饋
H5頁面上 present 系統相機,拍照完畢后返回原來頁面的時候出現白屏現象(拍照過程消耗了大量內存,導致內存緊張,WebContent Process 被系統掛起
),但上面的回調函數并沒有被調用。在WKWebView白屏的時候,另一種現象是 webView.titile 會被置空, 因此,可以在 viewWillAppear 的時候檢測webView.title
是否為空來 reload 頁面。
注意:可能有的前端頁面確實沒寫title標簽
,在前端移動端開發中是可能會有這種場景的,會造成頁面反復刷新
綜合以上兩種方法可以解決絕大多數的白屏問題。
5. NSURLProtocol(做網頁緩存)
見WKWebView中NSURLProtocol的使用以及對H5的緩存,這是利用NSURLProtocol做網頁緩存以及帶來的隱患。
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 頁面的截屏問題,Safari 以及 Chrome 這兩個全量切換到 WKWebView 的瀏覽器也存在同樣的問題:對webGL 頁面的截屏結果不是空白就是純黑圖片。
7. window.alert()引起的crash問題(暫時沒遇到)
8. WKWebView攔截協議
WKWebView內默認不允許iTunes、weixin等協議跳轉
UIWebView打開ituns.apple.com、跳轉到appStore,、撥打電話,、喚起郵箱等一系列操作UIWebView 自己處理不了會自動交給UIApplication 來處理。
WKWebView上述事件WKWebView 不會自動交給UIApplication 來處理,除此之外,js端通過window.open() 打開新的網頁的動作也被禁掉了
9. User-Agent修改
不要擅自修改webView的User-Agent,務必要跟前端反復確認,是否有用UA來做一些設備區分,進而做一些系統、機型適配問題。
10. UI細節問題
# 1. wkwebview中 h5絕對布局不生效
_baseWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; (ios 11之后)`
然后:前端需要在meta標簽中增加 **iPhoneX**的適配**---**適配方案**viewport-fit**:**cover**
# 2. iOS 12中WKWebView中表單 鍵盤彈起自動上移,導致的兼容問題
WKWebView會自動監聽鍵盤彈出,并做上下移動處理(效果如同IQKeyboardManage這些庫),但是在iOS12中會有一些問題,鍵盤收起后,控件不恢復原狀,或者部分控件消失等不兼容問題
解決方案:
if(kSystemVersion < 12.0) {
if (@available(iOS 11.0, *)) {
_webview.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
if (@available(iOS 12.0, *)) {
_webview.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentAutomatic;
}
# 3. WKWebview會下移20
解決方案:
VC.automaticallyAdjustsScrollViewInsets = NO; //iOS11以及以后失效
需要使用_webview.scrollView.contentInsetAdjustmentBehavior
# 順帶解釋一下以下兩個屬性
關于extendedLayoutIncludesOpaqueBars
和automaticallyAdjustsScrollViewInsets
- 這兩個屬性屬于UIViewController
- 默認情況下extendedLayoutIncludesOpaqueBars = false 擴展布局不包含導航欄
- 默認情況下automaticallyAdjustsScrollViewInsets = true 自動計算滾動視圖的內容邊距
- 但是,當 導航欄 是 不透明時,而tabBar為透明的時候,為了正確顯示tableView的全部內容,需要重新設置這兩個屬性的值,然后設置contentInset(參考代碼).
在iOS11 中, UIViewController的automaticallyAdjustsScrollViewInsets
屬性已經不再使用,我們需要使用UIScrollView的 contentInsetAdjustmentBehavior
屬性來替代它.
UIScrollViewContentInsetAdjustmentBehavior 是一個枚舉類型,值有以下幾種:
- automatic 和scrollableAxes一樣,scrollView會自動計算和適應頂部和底部的內邊距并且在scrollView 不可滾動時,也會設置內邊距.
- scrollableAxes 自動計算內邊距.
- never不計算內邊距
- always 根據safeAreaInsets 計算內邊距一般我們肯定需要設置為 never,我們自己來控制間距,但是在iOS 12的webView中,就會出現開始所說的問題,需要設置為automatic才能解決
調整WKWebView布局方式,避免調整webView.scrollView.contentInset。實際上,即便在 UIWebView 上也不建議直接調整webView.scrollView.contentInset的值.
# 4. 頁面滾動速率
WKWebView 需要通過 scrollView delegate 調整滾動速率:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
}
# 5. 視頻自動播放
WKWebView 需要通過WKWebViewConfiguration.mediaPlaybackRequiresUserAction設置是否允許自動播放,但一定要在 WKWebView 初始化之前設置,在 WKWebView 初始化之后設置無效。
# 6. goBack API問題
WKWebView 上調用 -[WKWebView goBack], 回退到上一個頁面后不會觸發window.onload()函數、不會執行JS。
# 7. didFinishNavigation遲遲不調用
明明看起來頁面加載完全,卻不調用(一般只發生在第一次進入該頁面)。
解決方法:經過自定義NSURLProtocol,攔截所有的H5加載資源,并在didCompleteWithError中打印資源的加載情況,發現有圖片資源,域名有問題
Error Domain=NSURLErrorDomain Code=-1003 "未能找到使用指定主機名的服務器。
DNS解析失敗導致系統認定H5一直沒加載完成,第二次再進入,系統緩存了DNS解析的映射記錄,所以很快就認定資源錯誤,調用了didFinish方法。
11. iOS 11系統上,WKWebView內H5標簽不可點擊,Native不受影響
在頻繁的切換頁面、刷新WKWebView的情況下,會出現WKWebView卡死,所有的H5標簽不可點擊,Native的UI不受影響,TabBarVC的幾個子控制器最為嚴重,有時候切換、刷新四五次左右,就會出現這種情況。
更新:結論:在viewWillAppear方法中調用了evaluateJavaScript: completionHandler:
方法,將該方法的調用移到viewDidAppear方法中即可。
下面是探索的一些步驟,也走了一些彎路,可繞過
分別從內存、視圖、網絡請求幾個方面入手,按照以下步驟定位問題:
1.對APP進行內存泄漏檢測,優化了幾處代碼。毫無用處
2.WKWebView單獨進程內存問題?因為有一些二級頁面按照問題出現流程復現了N多次,都沒有出現,所以暫時先排除
3.網速問題。發現網速差時,確實很容易復現,網速好的時候,試了好幾次沒復現!做了一些網絡優化,比如及時cancel掉一些不需要的請求,沒有效果。
4.視圖加載、更新問題。**猜測依據:一級頁面更容易復現,且比二級頁面多了一個Tabbar的視圖。
結論:結合第3、4,猜測是網絡過慢時,tabbar出現、隱藏,及WKWebView刷新、加載、渲染HTML,幾種情況結合導致的WKWebView布局混亂。
最后解決方法:包含WKWebView的一級頁面,`viewDidAppear`時重新設置了一下WKWebView的約束。(設置UIScrollViewContentInsetAdjustmentAutomatic = YES,沒有效果)
效果:大大改善了,但卻沒有根治問題。加了個保底方案,下拉刷新時,銷毀舊WKWebView,創建新的,并loadRequest。(因為這些情況下iOS 11上出現的,且沒有更低版本的測試機復現,所以暫時把修改限制在了iOS 11及以下的系統)
最后:有個最省事的方案,針對這些頁面,將WKWebView替換成UIWebView
。 可行,但逃避問題,不太可取,而且UIWebView、WKWebView各有一些特性,另一個不支持,比如WKWebView支持html,滾動時實時回調,而UIWebView只支持滾動停止時回調。且蘋果已經不太支持UIWebView。還是早點擁抱WKWebView吧。