浅谈iOS和HTML5的js交互问题

版权所有,禁止匿名转载;禁止商业使用。

纵观所有iOS与H5交互的方案,有以下几种:

  • 第一种:有很多的app直接使用在webview的代理中通过拦截的方式与native进行交互,通常是通过拦截url scheme判断是否是我们需要拦截处理的url及其所对应的要处理的功能是什么。任意版本都支持。
  • 第二种:iOS7之后出了JavaScriptCore.framework用于与JS交互,但是不支持iOS6,对于还需要支持iOS6的app,就不能考虑这个了。若需要了解,看最后的推荐阅读。
  • 第三种:WebViewJavascriptBridge开源库使用,本质上,它也是通过webview的代理拦截scheme,然后注入相应的JS。
  • 第四种:react-native,这个没玩过(与前三种不同)。
  • React Native 不是黑科技,我们写的代码总是以一种非常合理,可以解释的方式的运行着,只是绝大多数人没有理解而已。接下来我以 iOS 平台为例,简单的解释一下 React Native 的原理。

    首先要明白的一点是,即使使用了 React Native,我们依然需要 UIKit 等框架,调用的是 Objective-C 代码。总之,JavaScript 只是辅助,它只是提供了配置信息和逻辑的处理结果。React Native 与 Hybrid 完全没有关系,它只不过是以 JavaScript 的形式告诉 Objective-C 该执行什么代码。

    其次,React Native 能够运行起来,全靠 Objective-C 和 JavaScript 的交互。对于没有接触过 JavaScript 的人来说,非常有必要理解 JavaScript 代码如何被执行。

    我们知道 C 系列的语言,经过编译,链接等操作后,会得到一个二进制格式的可执行文,所谓的运行程序,其实是运行这个二进制程序。

    而 JavaScript 是一种脚本语言,它不会经过编译、链接等操作,而是在运行时才动态的进行词法、语法分析,生成抽象语法树(AST)和字节码,然后由解释器负责执行或者使用 JIT 将字节码转化为机器码再执行。整个流程由 JavaScript 引擎负责完成。

本篇文章专讲讲WebViewJavascriptBridge。


WebViewJavascriptBridge的基本原理

我们看看WebViewJavascriptBridge.m中Webview代理拦截的代码:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

    if (webView != _webView) { return YES; }

    NSURL *url = [request URL];

    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;

    if ([_base isCorrectProcotocolScheme:url]) {

        if ([_base isBridgeLoadedURL:url]) {

            [_base injectJavascriptFile];

        } else if ([_base isQueueMessageURL:url]) {

            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];

            [_base flushMessageQueue:messageQueueString];

        } else {

            [_base logUnkownMessage:url];

        }

        return NO;

    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {

        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];

    } else {

        return YES;

    }

}

在拦截后,通过先通过-isBridgeLoadedURL:方法判断URL是否是需要bridge的URL,若是,则通过injectJavascriptFile方法注入JS;否则判断URL是否是队列消息,若是,则执行查询命令JS并刷新消息队列;最后,URL被识别为未知的消息。


js在前端的使用


<!doctype html>

<html>

  <head>

  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">

    <style type='text/css'>

      html { font-family:Helvetica; color:#222; }

      h1 { color:steelblue; font-size:24px; margin-top:24px; }

      button { margin:0 3px 10px; font-size:12px; }

      .logLine { border-bottom:1px solid #ccc; padding:4px 2px; font-family:courier; font-size:11px; }

    </style>

  </head>

  

  <body>

    <h1>WebViewJavascriptBridge Demo</h1>

    

    <script>

      window.onerror = function(err) {

        log('window.onerror: ' + err)

      }

    

      /*这段代码是固定的,必须要放到js中*/

      function setupWebViewJavascriptBridge(callback) {

        if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }

        if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }

        window.WVJBCallbacks = [callback];

        var WVJBIframe = document.createElement('iframe');

        WVJBIframe.style.display = 'none';

        WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';

        document.documentElement.appendChild(WVJBIframe);

        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)

      }

    

      /*与OC交互的所有JS方法都要放在此处注册,才能调用通过JS调用OC或者让OC调用这里的JS*/

      setupWebViewJavascriptBridge(function(bridge) {

       var uniqueId = 1

       function log(message, data) {

         var log = document.getElementById('log')

         var el = document.createElement('div')

         el.className = 'logLine'

         el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)

         if (log.children.length) {

            log.insertBefore(el, log.children[0])

         } else {

           log.appendChild(el)

         }

       }

       /* Initialize your app here */

      

       /*我们在这注册一个js调用OC的方法,不带参数,且不用ObjC端反馈结果给JS:打开本demo对应的博文*/

       bridge.registerHandler('openWebviewBridgeArticle', function() {

          log("openWebviewBridgeArticle was called with by ObjC")

       })

       /*JS给ObjC提供公开的API,在ObjC端可以手动调用JS的这个API。接收ObjC传过来的参数,且可以回调ObjC*/

       bridge.registerHandler('getUserInfos', function(data, responseCallback) {

         log("Get user information from ObjC: ", data)

         responseCallback({'userId': '123456', 'blog': '博客'})

       })

                                  

       /*JS给ObjC提供公开的API,ObjC端通过注册,就可以在JS端调用此API时,得到回调。ObjC端可以在处理完成后,反馈给JS,这样写就是在载入页面完成时就先调用*/

       bridge.callHandler('getUserIdFromObjC', function(responseData) {

         log("JS call ObjC's getUserIdFromObjC function, and js received response:", responseData)

       })

 

       document.getElementById('blogId').onclick = function (e) {

         log('js call objc: getBlogNameFromObjC')

         bridge.callHandler('getBlogNameFromObjC', {'blogURL': 'http://www.baidu.com'}, function(response) {

                          log('JS got response', response)

                          })

       }

     })

      

    </script>

    

    <div id='buttons'></div> <div id='log'></div>

    

    <div>

       <input type="button" value="getBlogNameFromObjC" id="blogId"/>

    </div>

  </body>

</html>



在JS端,嵌入步骤是:

  • 第一步:将下面的代码放在JS中:
  • /*这段代码是固定的,必须要放到js中*/

    function setupWebViewJavascriptBridge(callback) {

        if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }

        if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }

        window.WVJBCallbacks = [callback];

        var WVJBIframe = document.createElement('iframe');

        WVJBIframe.style.display = 'none';

        WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';

        document.documentElement.appendChild(WVJBIframe);

        setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)

    }

  • 第二步:在下面的方法体里写相关JS代码:

  • setupWebViewJavascriptBridge(function(bridge) {

        /* Initialize your app here */

        所有与iOS交互的JS代码放这里!

    })

     JS如何调用iOS代码

  • 通过bridge.callHandler来调用:

  • bridge.callHandler('getBlogNameFromObjC',

                     {'blogURL': 'http://www.baidu.com'},

                     function callback(response) {

                       log('JS got response', response)

                     }

    })

  • 其中,各参数说明如下:

  • JS端加入WebViewJavascriptBridge代码注意事项

    如果在下面的函数体内有任何错误,都不会有打印日志,也不会有任何回调

  • setupWebViewJavascriptBridge(function(bridge) {

        /* Initialize your app here */

        所有与iOS交互的JS代码放这里!

    })

     

  • 因此,如果遇到什么也没有输出,说明你写错了。另外,上面有demo中,log函数是自定义的,不是系统的,因此如果没有加入这个函数的定义,调用它也会导致不能交互。

  • iOS端如何使用

  • 第一步:开启日志

  • // 开启日志,方便调试

    if (_bridge) {

            return;

        }

      

        [WebViewJavascriptBridge enableLogging];

  • 第二步:给ObjC与JS建立桥梁

  • // 给哪个webview建立JS与OjbC的沟通桥梁

    _bridge = [WebViewJavascriptBridge bridgeForWebView:_webView];

        

    [self loadExamplePage:_webView];

     

    // 设置代理,如果不需要实现,可以不设置

    [self.bridge setWebViewDelegate:self];

  • 第三步:注册HandleName,用于给JS端调用iOS端

  • //此处的data可以是任何的数据形式,一般最好以字符串和字典为好

  • id data = token;

        

    //----------------传状态---iOS端给html5传值-getToken是方法名必须和html5的方法名相对应------

        [_bridge callHandler:@"getToken" data:data responseCallback:^(id response) {

            

        NSLog(@"testJavascriptHandler responded: %@", response);

            

        }];



   //----------------登陆-这是接受html5要执行的方法-  同理getLoad也必须和html5的方法名相对应--   data是html5返回的登陆需要的信息一般笔者让html5以字典的方式传------

    [_bridge registerHandler:@"getLoad" handler:^(id data, WVJBResponseCallback responseCallback) {

        NSLog(@"testObjcCallback called: %@", data);

        responseCallback(@"Response from testObjcCallback");

    

        [self loading];//调用oc代码调转登陆控制器去登陆

     

    }];



想了解更多的伙伴,可以去https://github.com/CoderJackyHuang/WebViewJavascriptBridgeDemo去查看

0 0