2020-06-07 20:30:20
众所周知Android 4.2以下的WebView存在addJavascriptInterface漏洞的问题,不太了解的同学可参考
Android4.2下 WebView的addJavascriptInterface漏洞解决方案
@JavascriptInterface
因此,公司项目中很早便使用 JsBridge
实现 “JS与Native的通信” 了。
Android端WebView启动时,会加载一段WebViewJavascriptBridge.js
的js脚本代码。
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的通信。
lzyzsd/JsBridge
我注释的JsBridge
以下是**“ Native向JS端传递数据,并接受JS回调数据 ”**的时序图
sequenceDiagram participant BridgeWebView.java as clientA participant WebViewJavascriptBridge.js as serverA participant demo.html as serverB Note over clientA: Native向JS端传递数据 clientA-->>clientA: BridgeWebView.callHandler ("functionInJs", "Native向JS问好", mCallBackFunction); clientA-->>clientA: doSend(handlerName, data, responseCallback) clientA-->>clientA: queueMessage(m) clientA-->>clientA: dispatchMessage(m) clientA->>serverA: BridgeWebView.loadUrl(javascriptCommand) 调用JS的_handleMessageFromNative方法 serverA-->>serverA: _handleMessageFromNative(messageJSON) serverA-->>serverA: _dispatchMessageFromNative(messageJSON) serverA->>serverB: handler(message.data, responseCallback) serverB-->>serverB: bridge.registerHandler ("functionInJs", function(data, responseCallback)) serverB-->>serverA: responseCallback(responseData) serverA-->>serverA: _doSend({responseId,responseData}); serverA-->>clientA: reload iframe "yy://__QUEUE_MESSAGE__/" clientA-->>clientA: shouldOverrideUrlLoading(view, url) clientA-->>clientA: flushMessageQueue() clientA->>serverA: BridgeWebView.loadUrl(javascriptCommand) 调用JS的_fetchQueue()方法 serverA-->>serverA: _fetchQueue() serverA-->>clientA: reload iframe "yy://return/_fetchQueue/[{"data"}]" clientA-->>clientA: handlerReturnData(String url) clientA-->>clientA: flushMessageQueue中onCallBack clientA-->>clientA: mCallBackFunction.onCallBack(responseData)/**
* 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);
}
注释很全,看注释吧,不作讲解
/**
* 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方法
*
* @param url
*/
void handlerReturnData(String url) {
LogUtils.d(TAG, "handlerReturnData——>url: " + url);
// 获取js的方法名称
// _fetchQueue
String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
// 获取_fetchQueue 对应的回调方法
CallBackFunction f = responseCallbacks.get(functionName);
// 获取body Message消息体
String data = BridgeUtil.getDataFromReturnUrl(url);
// 回调 Native flushMessageQueue callback方法
if (f != null) {
LogUtils.d(TAG, "onCallBack data" + data);
f.onCallBack(data);
responseCallbacks.remove(functionName);
return;
}
}
这里的CallBackFunction 回调到了flushMessageQueue方法的onCallBack中。
@Override
public void onCallBack(String data) {
LogUtils.d(TAG, "flushMessageQueue——>data: " + data);
// deserializeMessage 反序列化消息
List<Message> list = null;
try {
list = Message.toArrayList(data);
} catch (Exception e) {
e.printStackTrace();
return;
}
if (list == null || list.size() == 0) {
LogUtils.e(TAG, "flushMessageQueue——>list.size() == 0");
return;
}
for (int i = 0; i < list.size(); i++) {
Message m = list.get(i);
String responseId = m.getResponseId();
/**
* 完成Native向JS发送信息后的回调
*/
// 是否是response CallBackFunction
if (!TextUtils.isEmpty(responseId)) {
CallBackFunction function = responseCallbacks.get(responseId);
String responseData = m.getResponseData();
function.onCallBack(responseData);
responseCallbacks.remove(responseId);
} else {
// ... 此处暂时省略
}
}
}
这里循环了从JS端获取到的Message队列,并将JS端获取的数据,回调到了Native中对应的CallBackFunction中。
到这里,JsBridge中Native调用JS代码的通信,则完成了。
WebViewJavascriptBridge.js的_doSend(message, responseCallback)方法中,把Message消息队列 放到shouldOverrideUrlLoading 的URL
中直接返回给Native不就可以了吗?
为什么还要用_doSend 中 reload iframe " yy://QUEUE_MESSAGE/ " 触发native的 shouldOverrideUrlLoading方法,让Native主动向JS请求一次Message队列,然后再放到shouldOverrideUrlLoading的URL中返回给Native呢?
个人观点: 觉得,这样将Message集中在一起,通过发送一个消息给Native,让Native主动将所有数据请求回来。避免了JS与Native的频繁交互。
不太想说了,就到这吧
Android Studio 查看源码出现throw new RuntimeException("Stub!"); 解决办法
07-03
Android 相机开发中的尺寸和方向问题
07-10
Android按钮单击事件的五种实现方式
04-22
Android的分屏模式开发注意事项
04-08
从0系统学Android--5.2 发送广播
04-22
在WIN10上使用cmd窗口命令编译Android OpenCV
03-17
CentOS7源码编译安装Nginx
06-11
Centos 7 Zabbix Agent 客户端源码编译安装配置
02-16
HashMap实现原理学习
06-03
Mysql 学习之EXPLAIN作用
06-08
Ubuntu 16.04源码编译安装Nginx 1.10.3
04-21
javaSE学习笔记(10)---List、Set
06-30
机器学习之数据探索——数据质量分析
03-14
Android databinding的使用技巧
04-18
ThinkPad 选件:小红点多功能蓝牙键盘介绍及使用方法(4X30K12182)
05-21
手机搜索不到内存卡音乐
04-17
联想手机:如何关闭手机的开机启动项、如qq、微信、音乐等
02-22
Tenorshare UltData for Android v6.5.2.7 免费中文版
1.7M
下载BurningVocabulary单词学习标记插件绿色版 v4.0
1.87M
下载天工100单词王(英语单词学习软件) v2.0.1.0 最新版
118.3M
下载标准拼音学习软件
4.46 MB
下载糍粑英语单词学习工具官方版下载 v2.0.1.4214官方版
55.5M
下载美术宝小班课(线美术学习软件) v1.4.4 官方PC版
71.4M
下载锡育看电影学英语软件(英语学习工具)201902 破解版
174.9M
下载高木学习(电子教学软件)v7.01 最新版
48.6M
下载ArduinoScratch(图形化编程软件)
58.23 MB
下载Firefox火狐浏览器 32位 v64.0.0.6914
40.71 MB
下载Praat(语音分析软件) 6.1.05 官方版
19.7M
下载Wondershare MobileGo下载
511K
下载小学数学同步课堂 v5.1.1.19
72.95 MB
下载小学英语同步课堂下载
203M
下载成语接龙查询器下载
2.3M
下载我要学英语
47.47 MB
下载聪聪数学 v1.3.0.0
1.12 MB
下载阿卢元素周期表 v v1.0
490 KB
下载