JSBridge 实现原理

本位主要总结下 JSBridge 前端实现原理,来自工做中的总结,安卓/ios代码仅为示意

JavaScript 与 Native之间的互相调用

JavaScript是运行在一个单独的 JS Context中(例如: webview的webkit引擎,JSCore)
1. 注入api的形式
  • 安卓操做方法
// 安卓4.4版本以前,没法获取返回值
    // mWebView = new WebView(this); // 即当前webview对象
    mWebView.loadUrl("javascript: 方法名('参数,须要转为字符串')")

    // 安卓4.4及之后
    mWebView.evaluateJavascript("javascript: 方法名,参数须要转换为字符串", new ValueCallback() {
     @Override
     public void onReceiveValue(String value) {
       // 这里的value即为对应JS方法的返回值
     }
    })

  // 总结:
    1. 4.4 以前Native经过loadUrl来调用js方法,只能让某个js方法执行,可是没法获取该方法的返回值
    2. 4.4 以后,经过evaluateJavaScript异步调用js方法,而且能在onReceive中拿到返回值
    3. 不适合传输大量数据
    4. mWebView.loadUrl("javascript: 方法名") 函数需在UI线程运行,由于mWebView为UI控件,会阻塞UI线程
    
    // JS调用Native
    // 安卓环境配置
    WebSettings webSettings = mWebView.getSettings();
    // Android容器容许js脚本,必需要
    webSettings.setJavaScriptEnabled(true);
    // Android 容器设置侨连对象
    mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");
  
    // Android中JSBridge的业务代码
    private Object getJSBridge() {
      Object insterObj = new Object() {
        @JavascriptInterface
        public String foo() {
          // 此处执行 foo  bridge的业务代码
          return "foo" // 返回值
        }
  
        @JavascriptInterface
        public String foo2(final String param) {
          // 此处执行 foo2 方法  bridge的业务代码
          return "foo2" + param;
        }
      }
      return inserObj;
  
    }
  
  
    // html 中 js调用原生的代码
    // JSBridge 经过addJavascriptInterface已被注入到 window 对象上了
    window.JSBridge.foo(); // 返回 'foo'
  
    window.JSBridge.foo2(); // 返回 'foo2:test'
  
    注意:在安卓4.2以前 addJavascriptInterface有风险,hacker能够经过反编译获取Native注册的Js对象,而后在页面经过反射Java的内置 静态类,获取一些敏感的信息和破坏
  • ios 操做方法
// native 调用 js

  // UIWebview
  [webView stringByEvaluatingJavaScriptFromString:@"方法名(参数);"];
    
    // WKWebview
    [_customWebView evaluateJavaScript:[@"方法名(参数)"] completionHandler:nil];
    

    --------------------
      
  // js 调用 native
    
    // 引用官方库文件 UIWebview(ios8 之前的版本,建议弃用)
    #import <JavaScriptCore/JavaScriptCore.h>
    // webview 加载完毕后设置一些js接口
    -(void)webViewDidFinishLoad:(UIWebView *)webView{
      [self hideProgress];
      [self setJSInterface];
  }
    
    -(void)setJSInterface{ 
      JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
        // 注册名为foo的api方法
        context[@"foo"] = ^() {
          //获取参数
            NSArray *args = [JSContext currentArguments];
            NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
            //作一些本身的逻辑
            //返回一个值  'foo:'+title
            return [NSString stringWithFormat:@"foo:%@", title];
        };
  }
    
    // js 调用原生代码
  window.foo('test'); // 返回 'foo:test'
    
  // 注意:ios7 之前 js没法调用native方法,ios7以后能够引入第三方提供的 JavaScriptCore 库
    总结:
    1. ios7 才出现这种方式,在这以前js没法直接调用Native,只能经过JSBridge方式调用
    2. JS 能调用到已经暴露的api,而且能获得相应返回值
    3. ios原生自己是没法被js调用的,可是经过引入官方提供的第三方“JavaScriptCore”,便可开发api给JS调用
      
    
    // WKWebview  ios8以后才出现,js调用native方法
    // ios 代码配置 https://zhuanlan.zhihu.com/p/32899522
    // js代码
      window.webkit.messageHandlers.JSBridge.postMessage(msgObj);
    
    
    ios开发自带两种webview控件
      UIWebview(ios8 之前的版本,建议弃用)
      版本较老
      可以使用JavaScriptCore来注入全局自定义对象
      占用内存大,加载速度慢
    WKWebview
      版本较新
      加载速度快,占用内存小
      js使用全局对象window.webkit.messageHandlers.{NAME}.postMessage 来调用native的方法

原生和h5 的另外一种通信方式:最广为流行的方法 JSBridge-桥协议

JSBridge 是广为流行的Hybrid 开发中JS和Native一种通讯方式,简单的说,JSBridge就是定义Native和JS的通讯,Native只经过一个固定的桥对象调用JS,JS也只经过固定的桥对象调用native,

基本原理是:javascript

h5 --> 经过某种方式触发一个url --> native捕获到url,进行分析 -->原生作处理 --> native 调用h5的JSBridge对象传递回调html

为何要用JSBridge

上面咱们看到native已经和js实现通讯,为何还要经过url scheme 的这种jsBridge方法呢前端

  1. Android4.2 一下,addJavaScriptInterface方式有安全漏洞
  2. ios7如下,js没法调用native
  3. url scheme交互方式是一套现有的成熟方案,能够兼容各类版本
  • 注意:jsBridge是一种交互理念一种协议,而上述url scheme则是其中的一种实现方式,因此也就是说,就算后面实现变为了 addJavaScriptInterface、JavaScriptCore,也是同样和JSBridge交互
url scheme 介绍
  • url scheme是一种相似于url的连接,是为了方便app直接互相调用设计的:具体为:能够用系统的 OpenURI 打开相似与url的连接(可拼入参数),而后系统会进行判断,若是是系统的 url scheme,则打开系统应用,不然找看是否有app注册中scheme,打开对应app,须要注意的是,这种scheme必须原生app注册后才会生效,如微信的scheme为 weixin://
  • 本文JSBridge中的url scheme则是仿照上述的形式的一种

    具体位置app不会注册对应的scheme,而是由前端页面经过某种方式触发scheme(如用 iframe.src),而后native用某种方法捕获对应的url触发事件,而后拿到当前触发url,根据定好的协议(scheme://method...),分析当前触发了哪一种方法,而后根据定义来实现java

实现一个JSBridge
1. 设计出一个native与js交互的`全局桥对象`
 2. js如何调用native
 3. native如何得知api被调用
 4. 分析 url 参数和回调的格式
 5. native如何调用js
 6. h5中api方法的注册以及格式
  • 设计一个native与js交互的全局对象 ==> 规定js和native之间的通讯必须经过一个h5全局对象JSBridge来实现
// 名称: JSBridge 挂在 window上的一个属性
  var JSBridge = window.JSBridge || (window.JSBridge = {});
  /**
    该对象有以下方法:
    registerHandler(String, Function) 注册本地 js 方法,注册后 native可经过 JSBridge调用,注册后会将方法注册到本地变量 messageHandles中
    
    sendHandler(String, JSON, Function) h5 调用原生开放的api,调用后实际上仍是本地经过 url scheme触发,调用时会将回调 id 存放到本地变量responseCallbacks 中
    
    _handleMessageFromNative h5 调用native以后的回调通知
    参数为 {reposeId: 回调id, responseData: 回调数据}
    
  */
  
  var JSBridge = {
    // 注册本地方法供原生调用
    registerHandler: function(method, cb) {
      // 会将cb 放入 messageHandlers里面,待原生调用
    },
    messageHandles: {}, // h5注册方法集合,供native通知后回调调用
    
    // h5 主动调用native,需生成惟一的callbackId
    sendHandler: function(mathod, data, succCb, errCb) {
      // 内部经过iframe src url scheme 向native发送请求
      // 并将对应的回调注册进 responseCallbacks
      // native 处理结束后将结果信息通知到h5 经过 _handleMessageFromNative
      // h5 拿到返回信息处理 responseCallbacks 里对应的回调
    },
    responseCallbacks: {}, // 回调集合
    
    // native 通知 h5
    _handleMessageFromNative: function(message) {
       // 解析 message,而后根据通知类型执行 messageHandles 或 responseCallbacks里的回调
    }
  }
  
  /**
      注意:
      1. native 调用_handleMessageFromNative通知h5,参数为 json 字符串
      
      2. native 主动调用h5方法时 {methodName: api名, data, callbackId}
          methodName: 开放api的名称
          data: 原生处理后传递给 h5 参数
        须要把回调函数的值 return 出去,供native拿到,
        
        或者再发一个 bridge 回去,方法名是 methodNameSuccess,或者严禁掉,方法名为native生产的callbackId
  */
  如:
  bridge.register("hupu.ui.datatabupdate", (name) => {
    if(name) {
      // 再发一个bridge通知原生tab更新成功,,,method 能够为native生成的 callbackId
      bridge.send('hupu.ui.datatabsuccess', {}) 
    }
  });
  • js 如何调用native ==> 经过 sendHandler 方法调用原生
// sendHandler 执行步骤
1. 判断是否有回调函数,若是有,生成一个回调函数id,并将id,和对应的回调添加放入回调函数集合 responseCallbacks 中

2. 经过特定的参数转换方法,将传入的数据,方法名一块儿拼接成一个 url scheme,以下:
 var param = {
   method: 'methodName',
   data: {xx: 'xx'},
   success: 'successId',
   error: 'errorId'
 }
 // 变成字符串并编码
 var url = scheme://ecape(JSON.stringify(param))

3. 使用内部建立好的iframe来触发scheme(location.href = 可能会形成跳转问题)
 ...建立iframe
 var iframe = document.createElment('iframe');
 iframe.src = url;
 document.head.appendChild(iframe);
 setTimeout(() => document.head.removeChild('iframe'), 200)
  • native 如何得知 api 被调用ios

    • 安卓捕获 url scheme:shouldoverrideurlloading 捕获到url进行分析
    • 安卓端也可经过h5的 window.prompt(url, '') 来触发scheme,而后native经过重写webviewClientonJsPrompt 来获取url,而后解析
    • ios: 在 UIWebView WKWebview 内发起的全部网络请求,均可以经过 delegate函数在native层获得通知,经过 shouldStartLoadWithRequest捕获webview中触发的url scheme
  • 分析url参数和回调的格式git

    • native已经接收到了js调用的方法,接下来原生应该按照定义好的数据格式来解析数据了,从url中提取出 method、data、successId、errorId
    • 根据方法名,再本地寻找对应的方法,接收对应的参数进行执行,执行完毕后,而后通知 h5并携带相应参数
  • native如何调用 js (参照上面的native执行js的方法)github

    • h5调用native后的被动通知 JSBridge._handleMessageFromNative(messageJSON),json格式:{responseId, reponseData}
    • native 主动调用h5 注册的方法,JSBridge._handleMessageFromNative(param),param 格式为 {methodName, data},因为是异步不支持批量调用

参考资料

  1. https://www.cnblogs.com/dailc...
  2. https://zhuanlan.zhihu.com/p/...JSBridgeDemo
  3. https://github.com/chemdemo/c...
相关文章
相关标签/搜索