4
头图

JavaScript是运行在一个单独的 JS Context中(例如: webview的webkit引擎,JSCore)
本位主要总结下 JSBridge 前端实现原理,来自工作中的总结,安卓/ios代码仅为示意

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

  • jsBridge 两种交互方式 注入apiurl schema重写h5全局方法

注入api的形式

安卓操作方法
  • native调用js

    // 安卓4.4版本之前,无法获取返回值
    // mWebView = new WebView(this); // 即当前webview对象
    mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')")
    
    // 安卓4.4及以后
    //  webView.evaluateJavascript("javascript:if(window.callJS){window.callJS('" + str + "');}", new ValueCallback<String>() {
    mWebView.evaluateJavascript("javascript: 方法名,参数需要转换为字符串", new ValueCallback() {
        @Override
        public void onReceiveValue(String value) {
        // 这里的value即为对应JS方法的返回值
        }
    })
    
    // js 在全局window上声明一个函数供安卓调用
    window.callAndroid = function() {
        console.log('来自中h5的方法,供native调用')
        return "来自h5的返回值"
    }
    
    /** 总结:
      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;
    }
    // js调用原生的代码
    // JSBridge 通过addJavascriptInterface已被注入到 window 对象上了
    window.JSBridge.foo(); // 返回 'foo'
    window.JSBridge.foo2(); // 返回 'foo2:test'
    // 注意:在安卓4.2之前 addJavascriptInterface有风险,hacker可以通过反编译获取Native注册的Js对象,然后在页面通过反射Java的内置 静态类,获取一些敏感的信息和破坏
ios 操作方法
  • js 调用native

      // 注意: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.{name}.postMessage(msgObj);
    
      /*
          * 优缺点
          ios开发自带两种webview控件 UIWebview(ios8 以前的版本,建议弃用)版本较老,
          可使用JavaScriptCore来注入全局自定义对象
          占用内存大,加载速度慢
          WKWebview 版本较新 加载速度快,占用内存小
      */
  • 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];
            };
        }
        window.foo('test'); // 返回 'foo:test'

    url scheme 介绍

  • url scheme是一种类似于url的链接,是为了方便app直接互相调用设计的:具体为:可以用系统的 OpenURI 打开类似与url的链接(可拼入参数),然后系统会进行判断,如果是系统的 url scheme,则打开系统应用,否则找看是否有app注册中scheme,打开对应app,需要注意的是,这种scheme必须原生app注册后才会生效,如微信的scheme为 weixin://
  • 调用过程(如用 iframe.src),然后native用某种方法捕获对应的url触发事件,然后拿到当前触发url,根据定好的协议(scheme://method/?params=xxx),然后native拦截该请求分析当前触发了哪种方法,然后根据定义来实现
  • 客户端捕获url

    • 安卓捕获 url scheme:shouldoverrideurlloading 捕获到url进行分析
    • ios: 在 UIWebView WKWebview 内发起的所有网络请求,都可以通过 delegate函数在native层得到通知,通过 shouldStartLoadWithRequest捕获webview中触发的url scheme
  • 大致流程:h5 --> 通过某种方式触发一个url --> native捕获到url,进行分析 -->原生做处理 --> 如果需要回调 native再调用h5的JSBridge对象传递回调
  • 缺点:速度可能稍慢一点,url长度会有限制,需要定义url结构解析较为复杂
  • 相较于注入api形式有以下有优点:

    1. Android4.2 一下,addJavaScriptInterface方式有安全漏洞
    2. ios7以下,js无法调用native
    3. url scheme交互方式是一套现有的成熟方案,可以兼容各种版本

重写prompt/alert等原生方法

  • native会劫持webviewonJsAlert、onJsConfirm、onConsoleMessage、onJsPrompt并进行重写,好像ios高版本对此方式做了限制

设计实现一个JSBridge

  1. 设计出一个native与js交互的全局桥对象
  2. js如何调用native
  3. native如何调用js
  4. h5中api方法 send/register

// 名称: JSBridge 挂在 window上的一个属性
window.JSBridge = {
    // ...其他属性,比如:版本、app基础信息
    // 回调函数集合
    _cbMap: {},
    // js注册方法,native主动发起调用
    register(method, callback) {
        const callbackId = method
        this._cbMap[callbackId] = callback
    },
    // h5 调用native方法,调用时会将回调 id 存放到本地变量cbList中
    send(method, data, callback) {
        let callbackId
        if (callback) {
            callbackId = `${method}_${Date.now()}`
            this._cbMap[callbackId] = callback
        }
        const params = {
            method, // 和客户端约定好定为method字段,即bridge名称
            callback: callbackId,
            data: {} // 业务参数
        }
        // 调用native将参数传递进去 ==> 通信方式以上三种可任意选择
        callNative(JSON.Stringify(params))
    },
    // native回调js的方法 obj: { 回调id, 回调数据 }
    // 相当于native只调用此方法,参数为json字符串
    handler(obj) {
        const { callbackId, data } = JSON.parse(obj)
        // 执行对应的回调函数即send传进来的callback,如果要返回值,可再发一个send
        this._cbMap[callbackId] && this._cbMap[callbackId].(data)
    }
}

// 调用示例
// 主动发消息
JSBridge.send('ui.callNative', {}, (data) => data) 
// 注册在本地,被动接受客户端调用
JSBridge.register("ui.datatabupdate", (data) => data);
JSBridge.send内部的callNative的具体实现
  • url schema方式

      // url schema 实现
      // 变成字符串并编码
      var url = scheme://ecape(JSON.stringify(param))
    
      // 使用内部创建好的iframe来触发scheme(location.href = 可能会造成跳转问题)
      var iframe = document.createElment('iframe');
      iframe.src = url;
      document.head.appendChild(iframe);
      setTimeout(() => document.head.removeChild('iframe'), 200)
  • api注入方式

      // ios
      window.webkit.messageHandlers.{name}.postMessage(JSON.stringify(params))
    
      // 安卓
      window.{name}.{name}(JSON.stringify(params))

参考资料

  1. https://www.cnblogs.com/dailc...
  2. https://zhuanlan.zhihu.com/p/...JSBridgeDemo
  3. https://github.com/chemdemo/c...
  4. 安卓与js交互
  5. ZOO TTEAM JSBridge

大桔子
588 声望51 粉丝

下一步该干什么呢...