众所周知Android 4.2以下的WebView存在addJavascriptInterface漏洞的问题,不太了解的同学可参考Android4.2下 WebView的addJavascriptInterface漏洞解决方案 @JavascriptInterface 因此,公司项目中很早便使用 JsBridge
实现 “JS与Native的通信” 了。
Native与JS通信原理
Android端WebView启动时,会加载一段WebViewJavascriptBridge.js
的js脚本代码。
Native调用JS代码: 当Native需要向JS端传递数据时,直接在Android WebView中使用
WebView.loadURL(javascript:WebViewJavascriptBridge.xxxxx)
调用在WebViewJavascriptBridge.js
中提前定义好的xxxxx
方法,将数据传递到JS端;JS调用Native代码: 当JS需要将数据传递给Native时,通过JS
reload iframe
将数据传递到Native的shouldOverrideUrlLoading(WebView view, String url)
方法的url参数中,Android端通过截获url获取JS传递过来的参数。
以此来实现Native与JS的通信。
GitHub源码
lzyzsd/JsBridge
我注释的JsBridge
Native调用JS代码,向JS端传递数据
以下是**“ Native向JS端传递数据,并接受JS回调数据 ”**的时序图
BridgeWebView.java
callHandler("functionInJs", "Native向JS问好", mCallBackFunction);
/** * Native调用JS * <p> * call javascript registered handler * 调用javascript处理程序注册 * * @param handlerName JS中注册的handlerName * @param data Native传递给JS的数据 * @param callBack JS处理完成后,回调到Native */ public void callHandler(String handlerName, String data, CallBackFunction callBack) { doSend(handlerName, data, callBack); }
注释很全,看注释吧,不作讲解
BridgeWebView.java
doSend(handlerName, data, responseCallback)
/** * Native 调用 JS * <p> * 保存message到消息队列 * * @param handlerName JS中注册的handlerName * @param data Native传递给JS的数据 * @param responseCallback JS处理完成后,回调到Native */ private void doSend(String handlerName, String data, CallBackFunction responseCallback) { LogUtils.e(TAG, "doSend——>data: " + data); LogUtils.e(TAG, "doSend——>handlerName: " + handlerName); // 创建一个消息体 Message m = new Message(); // 添加数据 if (!TextUtils.isEmpty(data)) { m.setData(data); } // if (responseCallback != null) { // 创建回调ID String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis())); // 1、JS回调Native数据时候使用;key: id value: callback (通过JS返回的callbackID 可以找到相应的CallBack方法) responseCallbacks.put(callbackStr, responseCallback); // 1、JS回调Native数据时候使用;key: id value: callback (通过JS返回的callbackID 可以找到相应的CallBack方法) m.setCallbackId(callbackStr); } // JS中注册的方法名称 if (!TextUtils.isEmpty(handlerName)) { m.setHandlerName(handlerName); } LogUtils.e(TAG, "doSend——>message: " + m.toJson()); // 添加消息 或者 分发消息到JS queueMessage(m); }
做了一个
Message
来封装data数据创建了一个callBackId,并将对应的引用存储在
Map<String, CallBackFunction> responseCallbacks
,这样JS相应方法处理结束后,将JS的处理结果返回来的时候,Native可通过该callbackId找到对应的CallBackFunction,从而完成数据回调。
/** * BridgeWebView.java * list<message> != null 添加到消息集合否则分发消息 * * @param m Message */ private void queueMessage(Message m) { LogUtils.e(TAG, "queueMessage——>message: " + m.toJson()); if (startupMessage != null) { startupMessage.add(m); } else { // 分发消息 dispatchMessage(m); } } /** * BridgeWebView.java * 分发message 必须在主线程才分发成功 * * @param m Message */ void dispatchMessage(Message m) { LogUtils.e(TAG, "dispatchMessage——>message: " + m.toJson()); // 转化为JSon字符串 String messageJson = m.toJson(); //escape special characters for json string 为json字符串转义特殊字符 messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2"); messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\""); String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson); LogUtils.e(TAG, "dispatchMessage——>javascriptCommand: " + javascriptCommand); // 必须要找主线程才会将数据传递出去 --- 划重点 if (Thread.currentThread() == Looper.getMainLooper().getThread()) { // 调用JS中_handleMessageFromNative方法 this.loadUrl(javascriptCommand); } }
dispatchMessage中,通过load
javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');
将Message数据传递到JS方法的_handleMessageFromNative当中
// Native通过loadUrl(JS_HANDLE_MESSAGE_FROM_JAVA),调用JS中_handleMessageFromNative方法,实现Native向JS传递数据final static String JS_HANDLE_MESSAGE_FROM_JAVA = "javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');";
WebViewJavascriptBridge.js
// 1、收到Native的消息 // 提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 function _handleMessageFromNative(messageJSON) { // console.log(messageJSON); // 添加到消息队列 if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON); } // 分发Native消息 _dispatchMessageFromNative(messageJSON); }
这里将Native发送过来的消息添加到
receiveMessageQueue
数组中。
//2、分发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) { ... } else { // 消息中有callbackId 说明需要将处理完成后,需要回调Native端 //直接发送 if (message.callbackId) { // 回调消息的 回调ID var callbackResponseId = message.callbackId; // responseCallback = function(responseData) { // 发送JS端的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); } } } }); }
demo.html
bridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); if (responseCallback) { var responseData = "Javascript Says Right back aka!"; responseCallback(responseData); } });
这里调用到JS的functionInJs注册方法,并将JS的处理结果
Javascript Says Right back aka!
返回,回调到WebViewJavascriptBridge.js _dispatchMessageFromNative注册的responseCallback,从而调用到WebViewJavascriptBridge.js 的_doSend方法之中。
一下为WebViewJavascriptBridge.js 的_doSend
WebViewJavascriptBridge.js
// 发送JS端的responseData_doSend({ responseId: callbackResponseId, responseData: responseData });
// 3、JS将数据发送到Native端// sendMessage add message, 触发native的 shouldOverrideUrlLoading方法,使Native主动向JS取数据//// 把消息队列数据放到shouldOverrideUrlLoading 的URL中不就可以了吗?// 为什么还要Native主动取一次,然后再放到shouldOverrideUrlLoading的URL中返回?function _doSend(message, responseCallback) { // 发送的数据存在 if (responseCallback) { // var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } // 添加到消息队列中 sendMessageQueue.push(message); // 让Native加载一个新的页面 messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
1、将Native发送过来的Message数据,存储到
sendMessageQueue
消息队列中2、_doSend 中 reload iframe " yy://QUEUE_MESSAGE/ " 触发native的 shouldOverrideUrlLoading方法
BridgeWebViewClient.java
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { LogUtils.d(TAG, "shouldOverrideUrlLoading——>url: " + url); try { url = URLDecoder.decode(url, "UTF-8"); } 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); } }
_doSend 中 reload iframe " yy://QUEUE_MESSAGE/ " 触发native的 shouldOverrideUrlLoading方法,最终调用到webView.flushMessageQueue();方法中
/** * 1、调用JS的 _fetchQueue方法,获取JS中处理后的消息队列。 * JS 中_fetchQueue 方法 中将Message数据返回到Native的 {@link #BridgeWebViewClient.shouldOverrideUrlLoading}中 * <p> * 2、等待{@link #handlerReturnData} 回调 Callback方法 */ void flushMessageQueue() { LogUtils.d(TAG, "flushMessageQueue"); if (Thread.currentThread() == Looper.getMainLooper().getThread()) { // 调用JS的 _fetchQueue方法 BridgeWebView.this.loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack(String data) { // ... 此处暂时省略 } }); } }
flushMessageQueue中加载了一段JS脚本,JS_FETCH_QUEUE_FROM_JAVA,以下为JS脚本的代码。
// 调用JS的 _fetchQueue方法。_fetchQueue方法中将Message数据返回到Native的shouldOverrideUrlLoading中final static String JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();";
这段JS脚本代码调用到的是 WebViewJavascriptBridge.js中的 _fetchQueue方法。
WebViewJavascriptBridge.js
// 将数据返回给Native// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容 function _fetchQueue() { // json数据 var messageQueueString = JSON.stringify(sendMessageQueue); // message数据清空 sendMessageQueue = []; // 数据返回到shouldOverrideUrlLoading //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); }
这里通过 reload iframe " yy://return/_fetchQueue/ + encodeURIComponent(messageQueueString)"将数据发送给Native的shouldOverrideUrlLoading方法中。
/** * 1、获取到CallBackFunction data执行调用并且从数据集移除 * <p> * 2、回调Native{@link #flushMessageQueue()} Callback方法 * *&nbs
相关推荐
0评论