WebViewJavascriptBridge工程结构和源码解析

来源:互联网 发布:荣威950 知乎 编辑:程序博客网 时间:2024/06/16 22:56

WebViewJavascriptBridge

javaScript调用Native其实是用重定向拼成url参数传递给Native

Native根据重定向的地址调用指定名称的回调函数


javascript生成url然后重定向使webview捕捉到重定向事件,解析重定向的过程。

shouldOverrideUrlLoading


Native调用javascript也是一样,实现用js代码注册好,

然后将指定参数名和回调函数转成可以供webview调用字符串格式。

实际就是调用webview.loadUrl


运行时结构如图





这里我简明扼要的的打个比喻


你拿银行卡(数据)去取钱(调用网页的window.WebViewJavascriptBridge.send方法)并给你一个小票(callBackID),大堂经理(android端) 给了

你一个pos机( 网页加载完成后注入WebViewJavaScriptBridge.js),pos机(WebViewJavaScriptBridge.js)和银行(android 端事先注册的一些监听重定向连接的方法)

之间交互之后通过小票(callBackid)告诉你取钱成功了。

为什么安全的原因是


1:pos机和银行交互 

对你是不透明,无法拦截无法调用。

2:pos机 无法伪造(网页加载完成后注入WebViewJavaScriptBridge.js) 是androd给你的

你不知道WebViewJavaScriptBridge.js里面的这2个校验

var CUSTOM_PROTOCOL_SCHEME = 'yy';
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'









以下是源码可以看的比较头晕。


从网站上下载工程:https://github.com/lzyzsd/JsBridge

我们来一步一步解析WebViewJavascriptBridge的框架结构,首先

BridgeWebView继承了webview
BridgeWebView设置WebViewClient为BridgeWebViewClient
BridgeWebViewClient有一个重要的时间在网页加载完成时注入js-》WebViewJavaScriptBridge.js
BridgeUtil.webViewLoadLoaclJs(View,BridgeWebView.toLoadJs)
 @Override    public void onPageFinished(WebView view, String url) {        super.onPageFinished(view, url);        if (BridgeWebView.toLoadJs != null) {            BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);        }       .....    }
让我们看看assets的
WebViewJavascriptBridge.js的源码怎么写的
WebViewJavascriptBridge基本属性和初始化的几个方法
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {        init: init,        send: send,        registerHandler: registerHandler,        callHandler: callHandler,        _fetchQueue: _fetchQueue,        _handleMessageFromNative: _handleMessageFromNative    };    var doc = document;    _createQueueReadyIframe(doc);    var readyEvent = doc.createEvent('Events');    readyEvent.initEvent('WebViewJavascriptBridgeReady');    readyEvent.bridge = WebViewJavascriptBridge;    doc.dispatchEvent(readyEvent);
你点击了发消息给Nativie这个按钮调用如下方法
function testClick() {    var str1 = document.getElementById("text1").value;    var str2 = document.getElementById("text2").value;    //send message to native    var data = {id: 1, content: "这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"};    window.WebViewJavascriptBridge.send(        data        , function(responseData) {            document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData        }    );}
这里window.WebViewJavascriptBridge.send指的是send function
 function send(data, responseCallback) {        _doSend({            data: data        }, responseCallback);    }


此时调用
WebViewJavascriptBridge.js中的WebViewJavascriptBridge的doSend方法
 //sendMessage add message, 触发native处理 sendMessage    function _doSend(message, responseCallback) {        if (responseCallback) {//如果回调函数不为空,为回调函数创建一个CallbackId            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();            responseCallbacks[callbackId] = responseCallback;            message.callbackId = callbackId;        }        sendMessageQueue.push(message);        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;    }
此时会发起一个重定向yy://_QUEUE_MESSAGE_/消息
然后由webview 捕捉网页重定向事件调用已注册的参数和对应的handler
url.startsWith(BridgeUtil.YY_RETURN_DATA 对应  yy://_QUEUE_MESSAGE_/
url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA  对应 <span style="font-family: Menlo; background-color: rgb(255, 255, 255);">yy://return/_fetchQueue/[{"data":{"id":1,"content":"这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"},"callbackId":"cb_1_1464075966655"}]
 @Override    public boolean shouldOverrideUrlLoading(WebView view, String url) {        try {            url = URLDecoder.decode(url, "UTF-8");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        Toast.makeText(view.getContext(), url, Toast.LENGTH_LONG).show();        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.handlerReturnData(url);的实际作用是一次性调用<span style="font-family: Menlo; font-size: 12pt; background-color: rgb(255, 255, 255);">_fetchQueue然后移除</span>
<pre name="code" class="javascript">void handlerReturnData(String url) {String functionName = BridgeUtil.getFunctionFromReturnUrl(url);System.out.println("functionName:"+functionName);CallBackFunction f = responseCallbacks.get(functionName);String data = BridgeUtil.getDataFromReturnUrl(url);if (f != null) {f.onCallBack(data);responseCallbacks.remove(functionName);return;}}



调用javacript的_fetchQueue
function _fetchQueue() {            alert("_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);    }

然后会重定向这个新的url yy://return/_fetchQueue/[{"data":{"id":1,"content":"这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"},"callbackId":"cb_1_1464075966655"}]
拼接回调函数和数据转成字符串由webview调用,格式和代码如下
void flushMessageQueue() {if (Thread.currentThread() == Looper.getMainLooper().getThread()) {loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {@Overridepublic void onCallBack(String data) {// deserializeMessageList<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();// 是否是responseif (!TextUtils.isEmpty(responseId)) {CallBackFunction function = responseCallbacks.get(responseId);String responseData = m.getResponseData();function.onCallBack(responseData);responseCallbacks.remove(responseId);} else {CallBackFunction responseFunction = null;// if had callbackIdfinal String callbackId = m.getCallbackId();if (!TextUtils.isEmpty(callbackId)) {responseFunction = new CallBackFunction() {@Overridepublic void onCallBack(String data) {Message responseMsg = new Message();responseMsg.setResponseId(callbackId);responseMsg.setResponseData(data);queueMessage(responseMsg);}};} else {responseFunction = new CallBackFunction() {@Overridepublic 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);}}}}});}}


关键核心代码:
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);        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {            this.loadUrl(javascriptCommand);        }    }

javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"responseData\":\"DefaultHandler response data\",\"responseId\":\"cb_1_1464076358507\"}');
在javascript中完成回调(取出根据回调函数的callbackId,获得引用并调用
//提供给native使用,    function _dispatchMessageFromNative(messageJSON) {        setTimeout(function() {            var message = JSON.parse(messageJSON);            var responseCallback;            //java call finished, now need to call js callback function            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) {                        _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);                    }                }            }        });    }

0 0