一.项目介绍
jsbridege是一个开源的项目。主要实现webview和native之间的交互。是支撑Hybrid模式非常重要的基础组件。
基本上各个大厂都有自己的SDK。比如腾讯,美团,携程等等,一些公司也在向外输出自己的专业能力。但是基本上原理都差不多,不过大厂的SDK在兼容性、功能性、安全性上更好一些。
项目地址:
https://github.com/lzyzsd/JsBridge
二.JsBridge原理
在 Js 和 WebView 交互的过程中,主要实现两个方向可以通信即可。
WebView 向 Js 传递数据是通过 WebView.loadUrl(String url) 实现的
在 WebView 中接收 Js 传递的数据是通过 WebViewClient 中的 shouldOverrideUrlLoading(WebView view, String url) 拦截加载链接 String url 参数实现的。
三.JsBridge简单用法
public class JSBridgeActivity extends AppCompatActivity {
private BridgeWebView mBridgeWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_jsbridge);
initView();
}
private void initView() {
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
//写一个最简单的双向的调用
//jsbridge目的就是为了实现webview和native的通信。
mBridgeWebView = (BridgeWebView) findViewById(R.id.webView);
mBridgeWebView.loadUrl("file:///android_asset/demo.html");
jsSendToNative();
//java传递给前端js
}
private void jsSendToNative() {
//前端传递给java:
//点击前端对应的按钮:DefaultHandler方式,调用的代码如下:
//前端对应的代码
///*用来展示默认方式*/
// function testClick() {
// var str1 = document.getElementById("text1").value;
// var str2 = document.getElementById("text2").value;
// //send message to native
// var data = {id: 1, content: "我是内容哦"};
// window.WebViewJavascriptBridge.send(
// data
// , function(responseData) {
// document.getElementById("show").innerHTML = "data = " + responseData
// }
// );
// }
mBridgeWebView.setDefaultHandler(new BridgeHandler() {
@Override
public void handler(String data, CallBackFunction function) {
//data是js返回的数据
Toast.makeText(JSBridgeActivity.this, data, Toast.LENGTH_LONG).show();
}
});
}
}
查看更多demo可移步下面的博客链接。
Android之利用JSBridge库实现Html,JavaScript与Android的所有交互
http://www.cnblogs.com/zhangqie/p/6724252.html
四.JsBridge总体设计
客户端调用JavaSript
web前端调用native
五.demo流程源码分析
运行环境设计java和js两个部分,调用流程在2部分之间交互。需要移动端同学了解一些js语法。
这里具体看一下demo中点击html中的DefaultHandler方式按钮后,js里的数据怎么传递给Android native的。
///*用来展示默认方式*/
// function testClick() {
// var str1 = document.getElementById("text1").value;
// var str2 = document.getElementById("text2").value;
// //send message to native
// var data = {id: 1, content: "我是内容哦"};
// window.WebViewJavascriptBridge.send(
// data
// , function(responseData) {
// document.getElementById("show").innerHTML = "data = " + responseData
// }
// );
// }
1.js:中的testClick调用到WebViewJavascriptBridge.js中的send方法
function send(data, responseCallback) {
_doSend({
data: data
}, responseCallback);
}
//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;
}
这里调用到doSend,将消息存放在sendMessageQueue中,将responseCallback放在responseCallbacks数组中。
这里着重关注几点:
1.message的callbackId.callbackId由uniqueId配合时间生成,用于后续查找responseCallback回调。callbackId用于html页面中send方法的回调。
2.更换iFrame的src,触发BridgeWebViewClient的shouldOverrideUrlLoading方法。
3.更换src,前缀为yy://QUEUE_MESSAGE/。
2.webview:匹配到shouldOverrideUrlLoading,进入到BridgeWebView的flushMessageQueue方法
void flushMessageQueue() {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() {
@Override
public void onCallBack(String data) {
// deserializeMessage
List<Message> 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
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
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 handler;
if (!TextUtils.isEmpty(m.getHandlerName())) {
handler = messageHandlers.get(m.getHandlerName());
} else {
handler = defaultHandler;
}
if (handler != null){
handler.handler(m.getData(), responseFunction);
}
}
}
}
});
}
}
flushMessageQueue通过主要调用到了loadUrl方法:
public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
this.loadUrl(jsUrl);
responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
}
loadUrl方法:
1.首先会去调用BridgeWebView的loadUrl方法。去执行javascript:WebViewJavascriptBridge._fetchQueue()js语句调用。
2.注册了一个回调函数。还将对应的回调函数放在responseCallbacks中, key是_fetchQueue。
3.js:_fetchQueue方法
// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
//add by hq
if (isIphone()) {
return messageQueueString;
//android can't read directly the return data, so we can reload iframe src to communicate with java
} else if (isAndroid()) {
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
}
这里讲sendMessageQueue数组中的所有消息,序列化为json字符串,通过更改iFrame的src,触发shouldOverrideUrlLoading方法。
4.webview:handlerReturnData方法
将关注点放到shouldOverrideUrlLoading里去调用到的是handlerReturnData方法。
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;
}
}
这里就和步骤2对应上了。即webview loadjs的_fetchQueue后,fetchqueue的返回结果会被传递到 onCallback的参数data中:
new CallBackFunction() {
@Override
public void onCallBack(String data) {
// deserializeMessage
List<Message> 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。即java发消息给web时,传入的回调函数
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
final String callbackId = m.getCallbackId();
//即web端发消息给native时,注册的回调函数。需要通过native->js触发
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 handler;
if (!TextUtils.isEmpty(m.getHandlerName())) {
handler = messageHandlers.get(m.getHandlerName());
} else {
//默认Handler
handler = defaultHandler;
}
if (handler != null){
handler.handler(m.getData(), responseFunction);
}
}
}
}
}
这里算是比较核心的部分了。
这里会调用到handler的handle方法。
即调用到了handler的handle方法中。
这里只是分析了在web上点击一个按钮传递数据给native的情况。
六.模块分析
1.BridgeWebViewClient
是在BridgeWebView构造方法调用了init,init主要设置了
webviewclient = new 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 onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
@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);
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
super.onReceivedError(view, errorCode, description, failingUrl);
}
}
代码中有几个要点需要关注:
1.onPageFinished方法中会去调用webview的loadUrl方法加载一段js代码:WebViewJavascriptBridge.js.
2.shouldOverrideUrlLoading方法中,会去匹配url的起始,分别调用不同的方法。
七.阅读体会&优缺点&改进意见
iframe的问题
http://blog.csdn.net/u014099894/article/details/72673438
js简单学习
iframe也称作嵌入式框架,嵌入式框架和框架网页类似,它可以把一个网页的框架和内容嵌入在现有的网页中。iframe用于设置文本或图形的浮动图文框或容器。
修改iframe的src会调用webview的shouldOverrideUrlLoading方法。
参考资料
JSbridge系列解析(四):Web端发消息给Native代码流程具体分析
http://cdn2.jianshu.io/p/730eaba1a617
JsBridge源码解析
http://lijiankun24.com/JsBridge%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/