发布时间: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的频繁交互。
不太想说了,就到这吧
Wondershare MobileGo for Android v7.1.0 破解版
511K
BurningVocabulary单词学习标记插件绿色版 v4.0
1.87M
天工100单词王(英语单词学习软件) v2.0.1.0 最新版
118.3M
小学英语同步课堂(英语学习软件) v5.6.1.20 免费版
203M
成语接龙查询器(成语学习工具) v1.0 绿色最新版
2.3M
标准拼音学习软件 v1.0.0.0
4.46 MB
锡育看电影学英语软件(英语学习工具)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
UltData下载
1.7M
小学数学同步课堂
72.95 MB
我要学英语
47.47 MB
糍粑英语
55.5M
美术宝小班课下载
71.4M
聪聪数学
1.12 MB
阿卢元素周期表
490 KB
2020-05-30
Windows 8.1系统在建立ReFS格式相关键值导致0x81000203报错
使用Docker搭建 Java Web运行环境
Lenovo settings下载及使用介绍
Lenovo笔记本新版Veriface Pro(人像识别)软件介绍
IdeaPad Y510在windows XP中如何使用声卡的混音功能
cacti监控磁盘IO
如何查看网卡的MAC地址?
笔记本电池使用注意事项
联想手机A789无线网络无法连接或无法上网如何解决