我们知道在 WebView 的使用中,WebView 与 Js 代码的交互是非常重要的一部分,在上篇文章 WebView 的使用 中,简单介绍了 WebView 与 Js 代码的交互。JsBridge 是一个 WebView 与 Js 代码交互的库,封装的非常完善,但是源码也不算难,在 GitHub 上有 3000+ Star,值得分析一下。

1. WebView 与 Js 交互回顾
在 WebView 的使用 中已经对 WebView 与 Js 代码进行了简单的介绍:
-
Js 调用
WebView的方法- 设置
WebView支持 JavaScript - 定义用于交互的类,并用
@JavascriptInterface关键字注解其中可以被 Js 调用的方法 - 使用
WebView的addJavascriptInterface(Object object, String name)方法将该类的方法传入WebView中,Js 便可以通过name调用该对象
- 设置
-
WebView调用 Js 代码- 设置
WebView支持 JavaScript - 使用
WebView的loadUrl(String url)便可以调用 Js 中的方法,如:webView.loadUrl("javascript:testConfirm()"),testConfirm()便是一个 Js 函数。
- 设置
2. JsBridge 源码解析
JsBridge 使用起来很简单,在 JsBridge 的 README 中有详细的介绍,这里就不多做介绍,直接分析其源码。
在 JsBridge 中有两个非常重要的类:BridgeWebView 和 BridgeWebViewClient
2.1 Android 调用 Js 代码
首先看一下,在 JsBridge 中,Android 是如何调用 Js 代码的
-
在 Js 代码中,声明 Android 可以调用的方法,如下所示:
WebViewJavascriptBridge.registerHandler("functionInJs", function(data, responseCallback) { document.getElementById("show").innerHTML = ("data from Java: = " + data); var responseData = "Javascript Says Right back aka!"; responseCallback(responseData); }); bridge.init(function(message, responseCallback) { console.log('JS got a message', message); var data = { 'Javascript Responds': 'Wee!' }; console.log('JS responding with', data); responseCallback(data); }); -
Java 调用此 JavaScript 函数的方法如下所示:
webView.callHandler("functionInJs", new Gson().toJson(user), new CallBackFunction() { @Override public void onCallBack(String data) { } });
* 同时在 Android 中可以使用如下方法,向 JavaScript 发送数据:
``` Java
webView.send("hello");
接下来分析一下 JsBridge 中是如何实现 Java 调用 Js 方法的
@SuppressLint("SetJavaScriptEnabled")
public class BridgeWebView extends WebView implements WebViewJavascriptBridge {
......
Map<String, CallBackFunction> responseCallbacks = new HashMap<>();
private List<Message> startupMessage = new ArrayList<>();
public List<Message> getStartupMessage() {
return startupMessage;
}
public void setStartupMessage(List<Message> startupMessage) {
this.startupMessage = startupMessage;
}
private void init() {
this.setVerticalScrollBarEnabled(false);
this.setHorizontalScrollBarEnabled(false);
this.getSettings().setJavaScriptEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
this.setWebViewClient(generateBridgeWebViewClient());
}
protected BridgeWebViewClient generateBridgeWebViewClient() {
return new BridgeWebViewClient(this);
}
void handlerReturnData(String url) {
String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
CallBackFunction f = responseCallbacks.get(functionName);
String data = BridgeUtil.getDataFromReturnUrl(url);
if (f != null) {
f.onCallBack(data);
responseCallbacks.remove(functionName);
return;
}
}
@Override
public void send(String data) {
send(data, null);
}
@Override
public void send(String data, CallBackFunction responseCallback) {
doSend(null, data, responseCallback);
}
private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
Message m = new Message();
if (!TextUtils.isEmpty(data)) {
m.setData(data);
}
if (responseCallback != null) {
String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
responseCallbacks.put(callbackStr, responseCallback);
m.setCallbackId(callbackStr);
}
if (!TextUtils.isEmpty(handlerName)) {
m.setHandlerName(handlerName);
}
queueMessage(m);
}
private void queueMessage(Message m) {
if (startupMessage != null) {
startupMessage.add(m);
} else {
dispatchMessage(m);
}
}
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);
}
}
public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
this.loadUrl(jsUrl);
responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
}
/**
* call javascript registered handler
*
* @param handlerName
* @param data
* @param callBack
*/
public void callHandler(String handlerName, String data, CallBackFunction callBack) {
doSend(handlerName, data, callBack);
}
}
- 从上面 BridgeWebView 源码中可以看到,不论是通过
callHandler(String handlerName, String data, CallBackFunction callBack)方法还是通过send(String data, CallBackFunction responseCallback)方法,最后都走到queueMessage(Message m)中。 - 在
queueMessage(Message m)方法中
- 如果
startupMessage为空,则直接通过dispatchMessage(Message m)方法,在其中生成调用 Js 方法的命令行,并通过WebView.loadUrl(String url)方法调用 Js 中的方法 - 若
startupMessage不为空,则将该Message对象加入到startupMessage消息列表中,那么消息列表是在何处调用呢?
- BridgeWebViewClient 的源码如下:
public class BridgeWebViewClient extends WebViewClient {
private BridgeWebView webView;
public BridgeWebViewClient(BridgeWebView webView) {
this.webView = webView;
}
@Override
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 super.shouldOverrideUrlLoading(view, url);
}
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (BridgeWebView.toLoadJs != null) {
BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);
}
//
if (webView.getStartupMessage() != null) {
for (Message m : webView.getStartupMessage()) {
webView.dispatchMessage(m);
}
webView.setStartupMessage(null);
}
}
}
可以看到,在 BridgeWebViewClient 的 onPageFinished(WebView view, String url) 方法中,会将 BridgeWebView 中的 List<Message> startupMessage 对象中的消息都通过 dispatchMessage(Message m) 将消息发送出去,也就是 Java 调用 Js 中的方法。
- 在
BridgeWebView.doSend(String handlerName, String data, CallBackFunction responseCallback)方法中会将CallBackFunction responseCallback方法加入Map<String, CallBackFunction> responseCallbacks回调集合中,在BridgeWebViewClient.shouldOverrideUrlLoading(WebView view, String url)拦截传递进来的String url数据,并进行判断,若是 Java 调用 Js 方法处理的结果,则找到对应的CallBackFunction对象将结果返回。
可以看到,在 JsBridge 中还是通过 WebView.loadUrl(String url) 的方法实现 Java 调用 Js 方法的功能的。
2.2 Js 调用 Android 方法
看一下 Js 代码如何调用 Android 中的方法
在 Java 中注册 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 中进行调用:
WebViewJavascriptBridge.callHandler(
'submitFromWeb'
, {'param': str1}
, function(responseData) {
document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
}
);
- 首先将回调方法
BridgeHandler handler对象,通过registerHandler(String handlerName, BridgeHandler handler)方法添加到BridgeWebView中的Map<String, BridgeHandler> messageHandlers集合中 - 当 Js 代码调用 Java 方法时,通过
BridgeWebViewClient.shouldOverrideUrlLoading(WebView view, String url)方法进行拦截,并通过BridgeWebView.flushMessageQueue()方法去主动获取 Js 中的消息队列,再通过BridgeWebViewClient.shouldOverrideUrlLoading(WebView view, String url)拦截得到获取到的数据 - 得到获取到的数据之后,找到
Map<String, BridgeHandler> messageHandlers中的回调方法,再将结果通过BridgeHandler handler对象回调
3.总结
其实,在 Js 和 WebView 交互的过程中,主要实现两个方向可以通信即可。
-
WebView向 Js 传递数据是通过WebView.loadUrl(String url)实现的 - 在
WebView中接收 Js 传递的数据是通过WebViewClient中的shouldOverrideUrlLoading(WebView view, String url)拦截加载链接String url参数实现的。
参考资料: