前言
在以前,一直以为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迟迟不调用 - 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吧。