wkwebview替换uiwebview_UIwebView->WkwebView精析全套流程

c92f3f77da7c62df234c27c4e4082405.png

【摘要】通常在实现原生端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一起使用来实现离线资源的加载。

9dce11b981228122eace110f83339e7b.png
uiwebview+NSURLProtcol 流程图

wkwebview流程图:

1614a2a6776d784810414b894aa0a163.png
wkwebview流程图

UIwebview特点:

  1. 加载速度慢;
  2. 内存占用多,内存优化困难;
  3. 内存泄漏;
  4. 第一次加载要卡一段时间白屏

wkwebview特点:

  1. 支持更多的HTML5的特性
  2. 高达60fps的滚动刷新率以及内置手势
  3. Safari相同的JavaScript引擎
  4. 将UIWebViewDelegate与UIWebView拆分成了14类与3个协议[2]
  5. 增加加载进度属性:estimatedProgress
  6. 自身不附带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 技术总结)

互联网产品研发管理 (我们公司对产品结构的管理思路)


我们是不一样的技术团队:

2abbfb9b015ec6dd0063c5a507ad5059.png

(我们认为:所有的企业行为,都解读为交易行为,无论是摩拜单车、外卖平台、自动售货机、招聘社区、家政服务,都用交易的语言来表达,我们专栏里面有很多实际案例和开发过程和交付流程)

330f76d0720744a6eed7b2a90c120e36.png

(类似于元素周期表,我们把交易拆解成元素级别,根据业务定制组装,完全复原个性化需求,我们专栏里面有很学术也很实际的介绍)

8b513ded08c3ff1e9587ca110e8b1071.png

(每个项目设置: 导师成长基金、参与人员的奖励,全员股权池,创业氛围浓郁,我们专栏公开分享了我们的一些经验)

6b3f1388176054711a0ac4046c1ed59f.png

(专治各种复杂的业务场景, 我们通过简洁的元素和分层组合,来完成复杂场景的业务定制,我们在这一块有非常多的案例,在互联网创业专栏里面有详细描述)

参考

  1. ^参考文献 https://blog.csdn.net/Double2hao/article/details/94134574
  2. ^参考文献 https://developer.apple.com/documentation/webkit?language=objc
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值