天天看點

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

繼續閱讀