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基本属性和初始化的几个方法
你点击了发消息给Nativie这个按钮调用如下方法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);
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方法此时会发起一个重定向yy://_QUEUE_MESSAGE_/消息//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; }
然后由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的_fetchQueuefunction _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() { @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); } } } } }); } }
关键核心代码:
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); } } } }); }