js與native交互之JsBridge

接上篇

JsBridge

1.使用方法

1.1,java調用js指定方法,并有回調:

//java端的代碼
webView.callHandler("functionInJs", param, new CallBackFunction() {
            @Override
            public void onCallBack(String data) {
                //拿到H5端回調回來的參數
            }
        });
//H5端的代碼
bridge.registerHandler("functionInJs", function(data, responseCallback) {
             document.getElementById("show").innerHTML = ("data from Java: = " + data);
             var responseData = "Javascript Says Right back aka!";
             //將參數回調給native端
             responseCallback(responseData);
        });

java調用callHandler()的方法,必須要在H5中registerHandler(),而且方法名一樣才能匹配到,
所以執行上可以看成 java—>js—>java。

1.2,js調用java

1,js發送消息給native端,并有回調

因為沒有指定要處理的方法,則由java端的DefaultHandler攔截處理

//js代碼
window.WebViewJavascriptBridge.send(
                data
                , function(responseData) {
                    //拿到H5端回調回來的參數
                    document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
                }
            );
//java代碼
//設置默認回調給JS的方法
webView.setDefaultHandler(new DefaultHandler());

public class DefaultHandler implements BridgeHandler {
    String TAG = "DefaultHandler";
    @Override
    public void handler(String data, CallBackFunction function) {
        if (function != null) {
             //將參數回調到H5端
            function.onCallBack("DefaultHandler response data"+ data);
        }
    }
}

js調用window.WebViewJavascriptBridge.send()的方法,會被java的DefaultHandler攔截
所以執行上可以看成 js—>java—>js。

2,js調用java指定方法,并有回調

//js代碼
window.WebViewJavascriptBridge.callHandler(
                'submitFromWeb'
                , {'param': '中文測試'}
                , function(responseData) {
                //拿到H5端回調回來的參數
                document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
                }
            );
//java代碼
webView.registerHandler("submitFromWeb", new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                ToastUtil.showShort(ActivityDemo7.this,"hadler = submitFromWeb, data from web = " + data);
                //將參數回調到H5端
                function.onCallBack("submitFromWeb exe, response data 中文 from Java");
            }
        });

2.執行流程分析

2.1,java調用js指定方法,并有回調。

在java代碼中,首先找到方法的入口,callHandler()。

webView.callHandler("functionInJs", params, new CallBackFunction() {
            @Override
            public void onCallBack(String data) {

            }
        });

進入callHandler()方法,調用了doSend()方法。

public void callHandler(String handlerName, String data, CallBackFunction callBack) {
        doSend(handlerName, data, callBack);
    }

看doSend()代碼

/**
     * native主動調用js代碼
     * @param handlerName js中的對應方法的名稱
     * @param data 要傳遞的參數
     * @param responseCallback js代碼執行后回調到這里的接口
     */
    private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
        Message m = new Message();
        if (!TextUtils.isEmpty(data)) {
            m.setData(data);
        }
        if (responseCallback != null) {
            String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
//            doSend()中callbackStr:JAVA_CB_3_998
//            doSend()中callbackStr:JAVA_CB_4_1022
            Log.i("BridgeWebView","doSend()中callbackStr:"+callbackStr);
            responseCallbacks.put(callbackStr, responseCallback);
            m.setCallbackId(callbackStr);
        }
        if (!TextUtils.isEmpty(handlerName)) {
            m.setHandlerName(handlerName);
        }
        queueMessage(m);
    }

這里組裝Message對象來保存調用方法的信息,假如存入了回調函數responseCallback ,則生成一個callbackStr 保存到本地的responseCallbacks中,繼續進入queueMessage()方法

private void queueMessage(Message m) {
        //假如native調用js代碼,則startupMessage為null
        if (startupMessage != null) {
            startupMessage.add(m);
        } else {
            dispatchMessage(m);
        }
    }

假如native調用js代碼,則startupMessage為null,所以進入到dispatchMessage()方法中

void dispatchMessage(Message m) {
        String messageJson = m.toJson();
        //escape special characters for json string
        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
//        dispatchMessage()中javascriptCommand:javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"data\":\"hello\"}');
//        dispatchMessage()中javascriptCommand:javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"callbackId\":\"JAVA_CB_1_877\",\"data\":\"{\\\"location\\\":{\\\"address\\\":\\\"SDU\\\"},\\\"name\\\":\\\"大頭鬼\\\"}\",\"handlerName\":\"functionInJs\"}');
        Log.i("BridgeWebView","dispatchMessage()中javascriptCommand:"+javascriptCommand);
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            this.loadUrl(javascriptCommand);
        }
    }

這段代碼就是native調用js代碼的關鍵,方法中先從message對象中解析json數據將信息解析,拼接出類似

javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"callbackId\":\"JAVA_CB_1_877\",\"data\":\"{\\\"location\\\":{\\\"address\\\":\\\"SDU\\\"},\\\"name\\\":\\\"大頭鬼\\\"}\",\"handlerName\":\"functionInJs\"}

的代碼,再通過

if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            this.loadUrl(javascriptCommand);
        }

在主線程中webView.loadUrl(),所以其實java調用js的核心還是webView.loadUrl()這個方法,但是JsBridge庫對其進行了封裝,這樣java就不能調用js中的任意代碼(本來webView.loadUrl()這個方法只要傳入拼接對的js代碼,就能調用該js代碼)。
追蹤到js的WebViewJavascriptBridge._handleMessageFromNative代碼

function _handleMessageFromNative(messageJSON) {
        console.log(messageJSON);
        if (receiveMessageQueue && receiveMessageQueue.length > 0) {
            receiveMessageQueue.push(messageJSON);
        } else {
            _dispatchMessageFromNative(messageJSON);
        }
    }

這里還沒搞懂receiveMessageQueue 的作用是什么,只知道調用了_dispatchMessageFromNative()方法,所以繼續進入方法

//按照messageJson進行事件的調用,假如有callback,則調用完之后callback
    function _dispatchMessageFromNative(messageJSON) {
        setTimeout(function() {
            //解析出callbackId(假如存在回調)和參數,
            var message = JSON.parse(messageJSON);
            var responseCallback;
            //java call finished, now need to call js callback function
            //假如存在responseId,則回調完成時去除callId
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
            } else {
                //直接發送
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        //js回調給java
                        _doSend({
                            responseId: callbackResponseId,
                            responseData: responseData
                        });
                    };
                }
                var handler = WebViewJavascriptBridge._messageHandler;
                if (message.handlerName) {
                    handler = messageHandlers[message.handlerName];
                }
                //查找指定handler
                try {
                    handler(message.data, responseCallback);
                } catch (exception) {
                    if (typeof console != 'undefined') {
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
                    }
                }
            }
        });
    }

這段代碼中,因為是java主動調用js,所以message.responseId為null,直接走else,先組裝回調函數responseCallBack()

if (message.callbackId) {
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        //js回調給java
                        _doSend({
                            responseId: callbackResponseId,
                            responseData: responseData
                        });
                    };
                }

接著根據handler = messageHandlers[message.handlerName];拿到handler

//查找指定handler
                try {
                    handler(message.data, responseCallback);
                } catch (exception) {
                    if (typeof console != 'undefined') {
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
                    }
                }

最后就是調用方法,追蹤到注冊的方法

connectWebViewJavascriptBridge(function(bridge) {
            ......
            //為js注冊Handler,
            bridge.registerHandler("functionInJs", function(data, responseCallback) {
                document.getElementById("show").innerHTML = ("data from Java: = " + data);
                var responseData = "Javascript Says Right back aka!";
                responseCallback(responseData);
            });
        })

最后調用了responseCallback(responseData),responseCallback()在上面已經組裝成功,就是調用了_doSend()這個方法

//sendMessage add message, 觸發native處理 sendMessage
//將數據返回到native,回調到native時就是調用這個方法
function _doSend(message, responseCallback) {
        if (responseCallback) {
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message.callbackId = callbackId;
        }
        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

這里涉及到一些前端的知識,利用iframe.src的變化,觸發了BridgeWebViewClient的shouldOverrideUrlLoading將數據返回到native端,這樣就實現了js回調到native了。

所以總體上流程是:

Paste_Image.png

3,js調用java指定方法,并有回調

找到js代碼的入口,window.WebViewJavascriptBridge.callHandler(...)

//function的responseData是回調的值
            //js調用特定的java方法
            window.WebViewJavascriptBridge.callHandler(
                'submitFromWeb'
                , {'param': '中文測試'}
                , function(responseData) {
                    document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
                }
            );

進入callHandler()方法,也是調用了_doSend()方法,將數據傳遞到native

function callHandler(handlerName, data, responseCallback) {
        _doSend({
            handlerName: handlerName,
            data: data
        }, responseCallback);
    }

與上面js調用java流程中的_doSend()一樣,通過改變iframe.src觸發native的shouldOverrideUrlLoading()方法

 @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
            Log.i("BridgeWebViewClient","查看觸發的schema:"+url);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回數據
            webView.handlerReturnData(url);
            return true;
        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
            webView.flushMessageQueue();
            return true;
        } else {
            return super.shouldOverrideUrlLoading(view, url);
        }
    }

接著走的是webView.flushMessageQueue()這個方法

void flushMessageQueue() {
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {

                @Override
                public void onCallBack(String data) {
                    // deserializeMessage
                    List<Message> list = null;
                    try {
                        list = Message.toArrayList(data);
                    } catch (Exception e) {
                        e.printStackTrace();
                        return;
                    }
                    if (list == null || list.size() == 0) {
                        return;
                    }
                    for (int i = 0; i < list.size(); i++) {
                        Message m = list.get(i);
                        String responseId = m.getResponseId();
                        // 是否是response
                        if (!TextUtils.isEmpty(responseId)) {
                            CallBackFunction function = responseCallbacks.get(responseId);
                            String responseData = m.getResponseData();
                            function.onCallBack(responseData);
                            responseCallbacks.remove(responseId);
                        } else {
                            CallBackFunction responseFunction = null;
                            // if had callbackId
                            final String callbackId = m.getCallbackId();
                            if (!TextUtils.isEmpty(callbackId)) {
                                responseFunction = new CallBackFunction() {
                                    @Override
                                    public void onCallBack(String data) {
                                        Message responseMsg = new Message();
                                        responseMsg.setResponseId(callbackId);
                                        responseMsg.setResponseData(data);
                                        queueMessage(responseMsg);
                                    }
                                };
                            } else {
                                responseFunction = new CallBackFunction() {
                                    @Override
                                    public void onCallBack(String data) {
                                        // do nothing
                                    }
                                };
                            }
                            BridgeHandler handler;
                            if (!TextUtils.isEmpty(m.getHandlerName())) {
                                handler = messageHandlers.get(m.getHandlerName());
                            } else {
                                handler = defaultHandler;
                            }
                            if (handler != null) {
                                handler.handler(m.getData(), responseFunction);
                            }
                        }
                    }
                }
            });
        }
    }

這個方法是在BridgeWebView中定義的,做的是在主線程中調用loadUrl(javascript:WebViewJavascriptBridge._fetchQueue(), new CallBackFunction());這個方法。
所以,追蹤到js中的_fetchQueue()中

// 提供給native調用,該函數作用:獲取sendMessageQueue返回給native,由于android不能直接獲取返回的內容,所以使用url shouldOverrideUrlLoading 的方式返回內容
    function _fetchQueue() {
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        //android can't read directly the return data, so we can reload iframe src to communicate with java
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
    }

這里將js傳遞的參數拼接入Url中,傳遞到native,而相應的shouldOverrideUrlLoading 中調用handlerReturnData()這個方法

/**
     * 處理返回的數據
     *
     * @param url
     */
    void handlerReturnData(String url) {
        String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
        CallBackFunction f = responseCallbacks.get(functionName);
        String data = BridgeUtil.getDataFromReturnUrl(url);
        if (f != null) {
            f.onCallBack(data);
            responseCallbacks.remove(functionName);
            return;
        }
    }

這里就是調用native本地方法

if (f != null) {
            f.onCallBack(data);
}

所以總體上流程是

Paste_Image.png

留意整個流程,js的第一次_doSend()只不過是通知native調用javascript:WebViewJavascriptBridge._fetchQueue();來取消息,包括要調用的方法,參數,回調的id。所以其實真正調用native方法的是js層的_fetchQueue()。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容