【摘要】通常在实现原生端app开发的时候,有的时候需要浏览H5网页,可能还需要在相应的网页进行业务交互。特别是采用混合开发型的app,app中很多业务交互场景几乎都是通过原生与h5进行数据交互实现的。所以对于交互的方式,使用哪个内核,成为了我们需要进行抉择和研究的一部分。我们以前用的UIwebview来交互,本来这段时间一直在研究如何两套同时兼容,因为我们公司前端js封装了一个Android和iOS通用原生交互的js,为了尽量减少变动,所以最终决定不使用流行的第三方框架WebViewJavascriptBridge并且完全舍弃UIwebView。
本篇主要是讲述从UIwebveiw直接过渡到WkwebView.
作者:x-teamer成员:清泓【最后更新日期2020年3月21日】
其实说白了就是要在原生app中显示web内容。实现一个浏览器的功能。苹果公司实现这个功能的内核有两个。其一是UIwebview,其一为WKWebView。
UIwebview在iOS2就出来了,wkWebview的诞生是在iOS8系统上才发布。毫无疑问WKWebView
将逐步取代UIWebView
。
wkwebview uiwebview流程图[1]
uiwebview一般会和NSURLProtcol一起使用来实现离线资源的加载。
wkwebview流程图:
UIwebview特点:
- 加载速度慢;
- 内存占用多,内存优化困难;
- 内存泄漏;
- 第一次加载要卡一段时间白屏
wkwebview特点:
- 支持更多的HTML5的特性
- 高达60fps的滚动刷新率以及内置手势
- Safari相同的JavaScript引擎
- 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议[2]
- 增加加载进度属性:
estimatedProgress
- 自身不附带cookie
2月11日更新前言:说说题外话,写完这些基础属性之后,我便放下这篇文章有一段时日了,现在记起来还真是。来说一说这段时间的成果,因为当初一直用的uiwebview,所以说没有在意WkwebView的开发难度。一试试,确实低估了替换难度。以前的很多基础回调包括接收数据的方法都没有数据显示了,我本身在原来的老项目里改了一下内核,结果发现很多东西都用不了,所以直接新建了一个项目。希望在这里跟大家一起从零开始走完这个从UIwebView升级到WkwebView的项目历程。
第一部分:js端信息传输类
H5端,我们采用的是一个js类,对所属移动平台进行判断,交互内容依据平台的不同而采用不同的方式发送信息。
由于我们之前用的是UIwebview内核,全部都用的JavaScriptCore,利用jsContext实现信息接收处理的机制,但在WkwebView中已经不适用,所以,前端那边必须进行方法调整。在iOS对wkWebView的 介绍中,有说到,前端js那边的方法调用为 例: window.webkit.messageHandlers.jsBrige.postMessage(action, token);
;(function() {
//约定好的消息名称
var apiNames = {
share: 'appShare',
scan: 'appScan',
getGPS: 'appGetGPS',
appBack: 'appBack'
}
var userAgent = window.navigator.userAgent.toUpperCase()
//判断移动端平台
var isAnroid = (function() {
return userAgent.indexOf('ANDROID') != -1
})()
var isIos = (function() {
return userAgent.indexOf('MAC OS') != -1
})()
var nativeBridge = function(name, param, cbName) {
console.log('name:' + name)
console.log('param:' + JSON.stringify(param))
console.log('cbName:' + cbName)
// param为空的时候,转换成JSON格式,param有值的时候转换成对象
// var testParam=M.extend(cbName, JSON.stringify(param));
// 新加解决扫码和分享冲突的代码2020年3月19 23点 只能调用分享
// var wkParam;
// if(isEmpty(param)){
// wkParam=JSON.stringify(param);
// }
// else{
// wkParam=param;
// }
// 3月20日9点30 结果,只能调用扫码
// var wkParam;
// if(isEmpty(JSON.stringify(param))){
// wkParam=JSON.stringify(param);
// // wkParam="";
// }
// else{
// wkParam=param;
// }
// 3月20日9点40 结果:两边调用不起来。(原因为param在这个类里面肯定为空,因为具体加数据处理
//其实是在调用这个方法之后进行数据添加的,所以判断param是否非空这个方案根本不可行)
//为什么不在iOS端直接进行数据解析,而要如此大费周折在前端折腾?因为如果发送给iOS的消息里面不
//包含任何内容,哪怕是一个null,消息根本接收不到,不要说之后的解析js回传的数据,就连双方约定
//的消息都触发不了,连WKScriptMessageHandler代理方法都触发不了。
// var wkParam=JSON.stringify(param);
// if (isNotEmpty(wkParam)){
// wkParam=param;
// }
//最终只能是:(这边大概思路是,在这里新建一个wkParam的对象,判断,如果是扫码事件,就把param
//转换成JSON格式,如果param里面存在数据,就直接把param赋给wkParam)
var wkParam;
if (name=="appScan") {
wkParam=JSON.stringify(param);
}else{
wkParam=param;
}
if (isAnroid) {
window.JSInterface[name](cbName, JSON.stringify(param));
} else if (isIos) {
// UIwebview专用,这边是以前UIwebView专用的方法
// window[name](cbName, JSON.stringify(param))
// 原始方法2020年3月18(这样的方法导致的后果是,无法对前后两条数据进行分步解析
//如果iOS端需要获取到其中的某个数据的时候,无法对数据进行特征性获取
// try{
// window.webkit.messageHandlers[name].postMessage(cbName,JSON.stringify(param));
// }catch(e){
// }
// 最新写法2020年3月20 10点
window.webkit.messageHandlers[name].postMessage({
//这边主要是把两个数据,撮合起来,弄成一个数组,否则的话,无法对发送的数据进行区分。
fun: cbName,
arg: {
// callback:JSON.stringify(param)
// callback:param
callback:wkParam
// param
}
})
//历史版本1 2020年3月17(如果有两个对象,同时发送两个消息,虽然两个消息都能收到,但是,
//当消息同时发送两次的时候,iOS端收到消息触发业务处理会处理两次,这样写是肯定不行的)
// try{
// window.webkit.messageHandlers[name].postMessage(param);
window.webkit.messageHandlers[name].postMessage(cbName);
// }catch(e){
//
// }
// 历史版本22020年3月16
//如果原生端只接受一个参数:对象
// if (!param){
// var param = {}
// alert(param);
// }
// param.callbackName = cbName
// window.webkit.messageHandlers[name].postMessage(param)
}
}
// 2020年3月20日9点,解决扫码,分享原生冲突问题(本来打算用非空判断对param进行判断,因为param
//为空值的时候,如果不转换成json格式,js发给iOS的数据会是:“ window.webkit.messageHandlers
//appScan.postMessage(),如果给iOS发送的消息什么都没有,iOS根本接收不到消息,完全没有响应。
//如果param为空,转换成json格式之后,js发送给iOS的数据格式为“ window.webkit.messageHandlers
//appScan.postMessage(null),这样的话就避免了iOS端无法获取到消息。
// 判断字符是否为空
// function isEmpty(obj){
// return (typeof obj === 'undefined' || obj === null || obj === "");
// }
// function isEmpty(str){
// if(str != null && trim(str).length > 0){
// return false;
// }
// return true;
// }
// //判断字符是否非空
// function isNotEmpty(){
// if(str != null && trim(str).length > 0){
// return ture;
// }
// return false;
// }
var callNative = function(name, param, callback) {
if (typeof callback != 'function') {
callback = param || function() {}
}
var cbName = '_NATIVE_CB_' + name
window[cbName] = function(data) {
var jsonData = typeof data == 'string' ? JSON.parse(data) : data
callback(jsonData)
}
try {
nativeBridge(name, param, cbName)
} catch (e) {
window[cbName]()
}
}
var native = {
isAnroid: isAnroid,
isIos: isIos
}
for (let key in apiNames) {
native[key] = function(param, callback) {
callNative(apiNames[key], param, callback)
}
}
window.Native = native
})()
第二部分:初始化(主要是对wkwebview的属性进行一系列的配置)
iOS这边再通过addScriptMessageHandler方法把前端的信息代入进去
#pragma mark 初始化webView
- (void)initWebView {
WKWebViewConfiguration *config =[[WKWebViewConfiguration alloc]init];
config.preferences.javaScriptEnabled=YES;
config.preferences.javaScriptCanOpenWindowsAutomatically= NO;
// 创建设置对象
WKPreferences *preference = [[WKPreferences alloc]init];
// 在iOS上默认为NO,表示是否允许不经过用户交互由javaScript自动打开窗口
preference.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preference;
WKWebView *webview =[[WKWebView alloc]initWithFrame:self.view.bounds configuration:config];
webview.navigationDelegate = self;
webview.UIDelegate = self;
//禁止滑动
webview.scrollView.scrollEnabled = false;
webview.scrollView.bounces = false;
//与前端约定好事件名,此处绑定事件。
[[webview configuration].userContentController addScriptMessageHandler:self name:@"appScan"];
[[webview configuration].userContentController addScriptMessageHandler:self name:@"appShare"];
[self.view addSubview:webview ];
self.wkWebView=webview;
}
这相当于配置一个装东西的“器皿“吧,以前的UIwebview基本是不支持这么配置的,大部分配置属性都需要通过自己写方法把属性配进”器皿“,而这个wkwebview在这一方面确实有一个特别大的进步,直接通过几行代码从内部配置进去了一些属性。重点在添加与前端约定好的消息,比如,扫码,分享,基础的属性配置已经完成。
第三部分:iOS端接收js端发送过来的数据
现在来说一下里面比较重要的一个方法,就是注册js方法用到的- addScriptMessageHandler。这个方法里面有两个参数,第一个参数是:userContentController的代理对象,第二个参数是JS里发送postMessage的对象。 所以要使用MessageHandler功能,就必须要实现WKScriptMessageHandler协议。 收到回调之后,iOS这边会对业务进行相关处理,扫码收到信息可调用扫码,分享收到js发送的信息可以在WKScriptMessageHandler里面接收到js端设定好的分享内容,然后解析完成调用微信,把分享数据分享到微信好友或者朋友圈。
#pragma mark 设置wkwebview的WKScriptMessageHandler代理方法-------------------------------------------------------------------------------------------------------------------------------------
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
NSLog(@"%@",message.name);// 方法名
NSLog(@"%@",message.body);// 传递的数据
if ([message.name isEqualToString:@"appScan"]) {
id body = message.body;
NSLog(@"appScan内容为:%@", body);
self.wkScanMethod=nil;
NSArray *scanArg = body;
NSDictionary *appScanDic =(NSDictionary *)scanArg;
NSLog(@"扫码传递过来的数据:%@",appScanDic);
// 两个分支,第一个分支fun,第二个分支arg。
self.wkScanMethod = [appScanDic objectForKey:@"fun"];
// self.wkScanMethod =fun;
// appScanDic
// self.wkScanMethod = appScanDic;
NSLog(@"原生扫码方法js->ios:%@",self.wkScanMethod);
//在主线程执行
WeakSelf
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf scanningQRCode];
});
}
else if([message.name isEqualToString:@"appShare"]){
id body = message.body;
// NSLog(@"appShare内容为:%@", body);
NSArray *shareAry=body;
// 整个数据组
NSDictionary *appShareDic = (NSDictionary *)shareAry;
NSLog(@"分享传递过来的数据:%@",appShareDic);
if(appShareDic){
NSDictionary *fun =[appShareDic objectForKey:@"fun"];
NSLog(@"fun:%@",fun);
// @try {
//
// NSLog(@"shareTitle:%@",self.wkShareMethod);
//
// }@catch (NSException* e) {
// NSLog(@"Exception: %@", e);
// }
// @finally {NSLog(@"finally");}
// 在主线程执行
WeakSelf
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf shareWeixinNoMenuView:appShareDic];
});
}else{
NSLog(@"分享失败请重试");
}
}
}
第四部分:iOS通过调用js端的方法,进行相关业务处理。
【未完待续】
欢迎关注 技术团队的知乎账号 我们凭团队实例运作以下专栏, 必须干货!
互联网创业专栏 (我们小伙伴的创业历程)
与您一起聊技术 (APP、微信公众号、小程序、H5 技术总结)
互联网产品研发管理 (我们公司对产品结构的管理思路)
我们是不一样的技术团队:
(我们认为:所有的企业行为,都解读为交易行为,无论是摩拜单车、外卖平台、自动售货机、招聘社区、家政服务,都用交易的语言来表达,我们专栏里面有很多实际案例和开发过程和交付流程)
(类似于元素周期表,我们把交易拆解成元素级别,根据业务定制组装,完全复原个性化需求,我们专栏里面有很学术也很实际的介绍)
(每个项目设置: 导师成长基金、参与人员的奖励,全员股权池,创业氛围浓郁,我们专栏公开分享了我们的一些经验)
(专治各种复杂的业务场景, 我们通过简洁的元素和分层组合,来完成复杂场景的业务定制,我们在这一块有非常多的案例,在互联网创业专栏里面有详细描述)
参考
- ^参考文献 https://blog.csdn.net/Double2hao/article/details/94134574
- ^参考文献 https://developer.apple.com/documentation/webkit?language=objc