天天看点

native和js怎么交互?JsBridge了解吗?

【问】native和js怎么交互?

【答】

  1. 概念说明:HybridAPP开发中常遇到native与webview中h5(即js)需要交互的问题。如js想要调用native拍照、图片上传、支付、页面分享等;native想要发送相册图片给js等。为了解决这两者间的通信问题,就有了JsBridge(需要注意的是,jsBridge更多的是指一种形式,一种思想,而不是一种具体的技术方案),我们需要实现它。
  2. jsBridge实现:

    2.1 js调用native:主要有两种方式,注入API和拦截URL Scheme

    (1)注入API:该方式的主要原理是,通过Webview提供的接口,向js的context(window)中注入对象或者方法,让js调用时,直接执行相应native代码,达到js调用native的目的。

    ios端实例:

    //IOS UIwebview代码
    JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    context[@"postBridgeMessage"] = ^(NSArray<NSArray *> *calls) {
        // Native 逻辑
    };
    
    //js代码
    window.postBridgeMessage(message);
               
    Android端实例:
    //Android 代码
    public class JavaScriptInterfaceDemoActivity extends Activity {
        private WebView Wv;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            Wv = (WebView)findViewById(R.id.webView);     
            final JavaScriptInterface myJavaScriptInterface = new JavaScriptInterface(this);         
    
            Wv.getSettings().setJavaScriptEnabled(true);
            Wv.addJavascriptInterface(myJavaScriptInterface, "nativeBridge");
    
            // TODO 显示 WebView
    
        }
    
        public class JavaScriptInterface {
             Context mContext;
    
             JavaScriptInterface(Context c) {
                 mContext = c;
             }
    
             public void postMessage(String webMessage){            
                 // Native 逻辑
             }
         }
    }
    //注:
    //在 4.2 之前,Android 注入 JavaScript 对象的接口是 addJavascriptInterface,但是这个接口有漏洞,可以被不法分子利用,危害用户的安全,因此在 4.2 中引入新的接口 @JavascriptInterface(上面代码中使用的)来替代这个接口,解决安全问题。所以 Android 注入对对象的方式是 有兼容性问题的。(4.2 之前很多方案都采用拦截 prompt 的方式来实现)
    
    //js代码
    window.nativeBridge.postMessage(message);
               

    (2)拦截URL Scheme:该方式是拦截h5以某种方式(如iframe.src、alert等)发送的URL Scheme(比如test1://ab/url?a=2,具体拦截哪种特定请求需要两端协商好)请求,如以自定义协议“test1://ab”开头的都拦截下来,执行相应的native方法;其他开头的放行。

    2.2 native调用js:其实就是直接调用挂载在context环境(window)中的对象或方法,执行拼接的js字符串。

    ios端实例:

    //IOS UIwebview代码
    result = [uiWebview stringByEvaluatingJavaScriptFromString:javaScriptString];
    //或者IOS WKwebview代码
    [wkWebView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
               
    Android端实例:
    //Android代码
    webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
        @Override
        public void onReceiveValue(String value) {
    
        }
    });
               
    2.3 相互调用时的callback实现:用一个自增的唯一 id,来标识并存储回调函数,并把此 id 以参数形式传递给 Native,而 Native 也以此 id 作为回溯的标识。这样,即可实现 Callback 回调逻辑。
  3. JsBridge代码实现:
(function () {
    var id = 0,
        callbacks = {},
        registerFuncs = {};

    window.JSBridge = {
        // 调用 Native
        invoke: function(bridgeName, callback, data) {
            // 判断环境,获取不同的 nativeBridge
            var thisId = id ++; // 获取唯一 id
            callbacks[thisId] = callback; // 存储 Callback
            nativeBridge.postMessage({
                bridgeName: bridgeName,
                data: data || {},
                callbackId: thisId // 传到 Native 端
            });
        },
        receiveMessage: function(msg) {
            var bridgeName = msg.bridgeName,
                data = msg.data || {},
                callbackId = msg.callbackId, // Native 将 callbackId 原封不动传回
                responstId = msg.responstId;
            // 具体逻辑
            // bridgeName 和 callbackId 不会同时存在
            if (callbackId) {
                if (callbacks[callbackId]) { // 找到相应句柄
                    callbacks[callbackId](msg.data); // 执行调用
                }
            } else if (bridgeName) {
                if (registerFuncs[bridgeName]) { // 通过 bridgeName 找到句柄
                    var ret = {},
                        flag = false;
                    registerFuncs[bridgeName].forEach(function(callback) => {
                        callback(data, function(r) {
                            flag = true;
                            ret = Object.assign(ret, r);
                        });
                    });
                    if (flag) {
                        nativeBridge.postMessage({ // 回调 Native
                            responstId: responstId,
                            ret: ret
                        });
                    }
                }
            }
        },
        register: function(bridgeName, callback) {
            if (!registerFuncs[bridgeName])  {
                registerFuncs[bridgeName] = [];
            }
            registerFuncs[bridgeName].push(callback); // 存储回调
        }
    };
})()
           
  1. JsBridge引入方式:

    (1)由native端进行注入,直接执行JsBridge的全部代码

    (2) 由js端执行JsBridge全部代码

参考1:移动混合开发中的JsBridge

继续阅读