前言
在以前,一直以为Hybrid App开发是一种略显简单的事,不会使用太多能发挥移动端原生本身优势的复杂API,后来在新公司的工作(半混合式开发)过程中,发现混合式开发也是很多坑... 或者说WKWebView好多坑...
以下所说的内容,参考链接上基本上都有,本文的叙述方式主要是结合自己的经历(自己踩过的总结总是那么的深刻...(\捂脸))
应该在开始混合开发之前就看下这篇文章的,结果真的是等自己踩坑踩了一遍,总结之后,发现这篇文章上都有....(\大哭)
参考链接2: https://www.jianshu.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迟迟不调用
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
#import
@interface WeakScriptMessageDelegate : NSObject
@property (nonatomic, weak) id scriptDelegate;
- (instancetype)initWithDelegate:(id)scriptDelegate;
@end
@implementation WeakScriptMessageDelegate
- (instancetype)initWithDelegate:(id)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方法。