JsBridge就是一个简化Android与JS通信的框架,源码:https://github.com/lzyzsd/JsBridge
一、Javascript 与 Java 通信的原理
1、JS调用Android方法
WebView.addJavascriptInterface
:addJavascriptInterface有很大的安全隐患(通过js获取高权限,盗取用户信息、运行病毒代码等等),特别是4.2之前,4.2及以上版本(API >= 17)新增了@JavascriptInterface注解来提高安全等级,没有注解的方法,js无法调用,并在4.4版本增加了带回调的方法webview.evaluateJavascript(s,valuecallback)
/**
* 添加javascriptInterface
* 第一个参数:这里需要一个与js映射的java对象
* 第二个参数:该java对象被映射为js对象后在js里面的对象名,在js中要调用该对象的方法就是通过这个来调用
*/
webView.addJavascriptInterface(new JSInterface(), "android");
@SuppressWarnings("unused")
private final class JSInterface{
/**
* 注意这里的@JavascriptInterface注解, target是4.2以上都需要添加这个注解,否则无法调用
* @param text
*/
@JavascriptInterface
public void showToast(String text){
Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
}
@JavascriptInterface
public void showJsText(String text){
webView.loadUrl("javascript:jsText('"+text+"')");
}
}
/**
* js中调用java方法
*/
javascript:android.showToast('toast');
WebViewClient.shouldOverrideUrlLoading
:方法中截取到url
在加载一个新的 url 连接时的一个回调。目的是给宿主应用一个机会是否拦截该新的 url 连接请求。 如果返回 true,表示应用处理了该 url,返回false 则意味着将 url 请求交给当前的 WebView 来处理。
2、Android调用JS
loadUrl
:调用JS代码
// js方法
function setICCID(iccid){
document.getElementById("ICCID").innerHTML = iccid;
}
//java调用该方法
webView.loadUrl("javascript:setICCID('" + No:007 + "')")
evaluateJavascript
:调用带返回值得JS方法
4.4版本以上,调用带返回值js方法
//js定义被调用方法,并返回处理结果
function sum2Java(number1, number2) {
return number1 + number2;
}
//java调用带返回值的js方法
@TargetApi(Build.VERSION_CODES.KITKAT)
@JavascriptInterface
public void Android2JsHaveParmHaveResult2() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.evaluateJavascript("sum2Java(3,4)", new ValueCallback() {
@Override
public void onReceiveValue(String Str) {
Toast.makeText(mActivity, "我是android调用js方法(4.4后),入参是3和4,js返回结果是" + Str, Toast.LENGTH_LONG).show();
}
});
}
});
}
二、JsBridge使用
1、添加Gradle依赖
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
compile 'com.github.lzyzsd:jsbridge:1.0.4'
}
2、在Java中注册方法供JS调用
在Java中注册一个Handler供JS调用
webView.registerHandler("submitFromWeb", new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
function.onCallBack("submitFromWeb exe, response data from Java");
}
});
在JS中调用在Java中注册的Handler
WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': str1}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
也可以在Java中使用一个默认的Handler,JS在调用的时候就不用指定handlerName了
//设置默认Handler
webView.setDefaultHandler(new DefaultHandler());
//JS中调用默认Handler
window.WebViewJavascriptBridge.send(
data
, function(responseData) {
document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
}
);
3、JS中注册方法供Java调用
WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) {
document.getElementById("show").innerHTML = ("data from Java: = " + data);
var responseData = "Javascript Says Right back aka!";
responseCallback(responseData);
});
// Java can call this js handler function "functionInJs" through:
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
三、JsBridge源码分析
Native向JS端传递数据,并接受JS回调数据时序图如下所示
接下来我们来一步一步跟踪上面的调用过程:
1、callHandler
调用javascript注册的Handler
/**
* 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);
}
2、doSend(handlerName, data, callBack)
/**
* 保存message到消息队列
* @param handlerName handlerName
* @param data data
* @param responseCallback CallBackFunction
*/
private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
// 创建一个Message
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()));
responseCallbacks.put(callbackStr, responseCallback);
m.setCallbackId(callbackStr);
}
// JS中注册的方法名称
if (!TextUtils.isEmpty(handlerName)) {
m.setHandlerName(handlerName);
}
queueMessage(m);
}
创建了一个callBackId,并将对应的引用存储在Map
,这样JS相应方法处理结束后,将JS的处理结果返回来的时候,Native可通过该callbackId找到对应的CallBackFunction,从而完成数据回调。
3、queueMessage
处理message
/**
* list != null 添加到消息集合否则分发消息
* @param m Message
*/
private void queueMessage(Message m) {
if (startupMessage != null) {
startupMessage.add(m);
} else {
dispatchMessage(m);
}
}
4、dispatchMessage
分发Message
通过load javascript:WebViewJavascriptBridge._handleMessageFromNative('%s');
将Message数据传递到JS方法的_handleMessageFromNative
当中
/**
* 分发message 必须在主线程才分发成功
* @param m Message
*/
void dispatchMessage(Message m) {
// 转化为JSon字符串
String messageJson = m.toJson();
//escape special characters for json string 为json字符串转义特殊字符
messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
messageJson = messageJson.replaceAll("(?<=[^\\\\])(\')", "\\\\\'");
messageJson = messageJson.replaceAll("%7B", URLEncoder.encode("%7B"));
messageJson = messageJson.replaceAll("%7D", URLEncoder.encode("%7D"));
messageJson = messageJson.replaceAll("%22", URLEncoder.encode("%22"));
String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
// 必须要找主线程才会将数据传递出去 --- 划重点
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
// 调用JS中_handleMessageFromNative方法
this.loadUrl(javascriptCommand);
}
}
5、JS中的_handleMessageFromNative
接收Native方法
这里将Native发送过来的消息添加到receiveMessageQueue
数组中(貌似只是添加到数组中没有使用过)
//提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
function _handleMessageFromNative(messageJSON) {
console.log(messageJSON);
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON);
}
_dispatchMessageFromNative(messageJSON);
}
6、_dispatchMessageFromNative
分发Native消息
//提供给native使用,
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function() {
var message = JSON.parse(messageJSON);
var responseCallback;
// java调用完成后,需要回调JS的 callback function
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
// 消息中有callbackId 说明需要回调,处理完成后回调通知Native端
if (message.callbackId) {
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);
}
}
}
});
}
7、在_dispatchMessageFromNative
中找到demo.html中注册的Handler并调用注册的Handler
这里调用到JS的functionInJs方法,并将JS的处理结果Javascript Says Right back aka!返回,回调到WebViewJavascriptBridge.js _dispatchMessageFromNative注册的responseCallback,从而调用到WebViewJavascriptBridge.js 的_doSend方法之中。
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);
}
});
8、 调用_doSend
1、将Native发送过来的Message数据,存储到sendMessageQueue消息队列中
2、_doSend 中 reload iframe yy://QUEUE_MESSAGE/
触发native的 shouldOverrideUrlLoading方法
//sendMessage add message, 触发native处理 sendMessage
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message.callbackId = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
9、_doSend 中 reload iframe yy://QUEUE_MESSAGE/
触发native的 shouldOverrideUrlLoading方法
public boolean shouldOverrideUrlLoading(WebView view, String 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 this.onCustomShouldOverrideUrlLoading(url)?true:super.shouldOverrideUrlLoading(view, url);
}
}
10、刷新消息队列flushMessageQueue
这里又去加载了一段JS:javascript:WebViewJavascriptBridge._fetchQueue();
void flushMessageQueue() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
});
}
}
11、_fetchQueue
:获取sendMessageQueue并返回给native
// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
function _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
if (messageQueueString !== '[]') {
bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
}
这里通过 reload iframe yy://return/_fetchQueue/ + encodeURIComponent(messageQueueString)
将数据发送给Native的shouldOverrideUrlLoading方法中。
12、处理返回数据
/**
* 获取到CallBackFunction data执行调用并且从数据集移除
* @param url
*/
void handlerReturnData(String url) {
String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
CallBackFunction f = responseCallbacks.get(functionName);
String data = BridgeUtil.getDataFromReturnUrl(url);
// 回调 Native flushMessageQueue callback方法
if (f != null) {
f.onCallBack(data);
responseCallbacks.remove(functionName);
return;
}
}
这里的CallBackFunction 回调到了flushMessageQueue方法的onCallBack中。
@Override
public void onCallBack(String data) {
// deserializeMessage 反序列化消息
List 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 CallBackFunction
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 如果有回调Id
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执行
BridgeHandler handler;
if (!TextUtils.isEmpty(m.getHandlerName())) {
handler = messageHandlers.get(m.getHandlerName());
} else {
handler = defaultHandler;
}
if (handler != null) {
handler.handler(m.getData(), responseFunction);
}
}
}
}