简介 java跟js交互的框架。
git地址:https://github.com/lzyzsd/JsBridge
使用 git地址:https://github.com/kbjay/kj_jsbridge_use
关于导包,可以按照jsbridge的使用说明来
1 2 3 4 5 6 7 8 repositories { maven { url "https://jitpack.io" } } dependencies { compile 'com.github.lzyzsd:jsbridge:1.0.4' }
为了方便分析源码,我这边的demo(kj_kjbrigde_use)是将jsbridge作为一个module(library)引入。
java传递消息给js:两种方式一种是默认方式,一种是spec方式
java中代码如下:
webView.callHandler(tag, msg,callback):发送消息msg给js中tag对应的function,function执行完成之后有一个callback。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void java2jsSpec () { webView.callHandler("functionInJs" , "发送消息给js(spec方式!!)" , new CallBackFunction() { @Override public void onCallBack (String data) { Toast.makeText(MainActivity.this , data, Toast.LENGTH_SHORT).show(); } }); } private void java2jsDefault () { webView.send("发送消息给js(默认方式!)" , new CallBackFunction() { @Override public void onCallBack (String data) { Toast.makeText(MainActivity.this , data, Toast.LENGTH_SHORT).show(); } }); }
对应的js代码如下:
js中需要注册接受消息handler(同样有两种类型:默认跟spec),处理完消息之后调用responseCallback(callbackMsg)将处理结果回传给java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function initJsBridge (callback ) { if (window .WebViewJavascriptBridge) { callback(WebViewJavascriptBridge) } else { document .addEventListener( 'WebViewJavascriptBridgeReady' , function ( ) { callback(WebViewJavascriptBridge) }, false ); } } initJsBridge(function (bridge ) { bridge.init(function (message, responseCallback ) { document .getElementById("show" ).innerHTML = '默认接收到Java的数据: ' + message; var responseData = 'js默认接收完毕,并回传数据给java' ; responseCallback(responseData); }); bridge.registerHandler("functionInJs" , function (data, responseCallback ) { document .getElementById("show" ).innerHTML = '指定接收到Java的数据: ' + data; var responseData = 'js指定接收完毕,并回传数据给java' ; responseCallback(responseData); }); })
js传递消息给java,两种方式:默认跟spec
js中发送消息,接收callback代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function jsToJavaDefault ( ) { var msg = "js发送给java-->默认消息" ; window .WebViewJavascriptBridge.send(msg, function (resp ) { document .getElementById("show" ).innerHTML = resp; }); } function jsToJavaSpec ( ) { var msg = "js发送给java-->指定消息" ; window .WebViewJavascriptBridge.callHandler('submitFromWeb' , msg, function (resp ) { document .getElementById("show" ).innerHTML = resp; }); }
java中处理消息,回传callback代码如下:同样两种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void initJs () { webView.setDefaultHandler(new BridgeHandler() { @Override public void handler (String data, CallBackFunction function) { Toast.makeText(MainActivity.this , data, Toast.LENGTH_SHORT).show(); function.onCallBack("java默认接收完毕,并回传数据给js" ); } }); webView.registerHandler("submitFromWeb" , new BridgeHandler() { @Override public void handler (String data, CallBackFunction function) { Toast.makeText(MainActivity.this , data, Toast.LENGTH_SHORT).show(); function.onCallBack("java指定接收完毕,并回传数据给js" ); } }); }
源码解析 基础知识
java跟js交互的原始方式
java->js:
webView.loadUrl(“javascript:functionInJs(msg)”) jsBridge中选择的方式 webView.evaluateJavascript(“javascript:functionInJs(msg)”,callback) API19(4.4)之后都可以,带有callback
js->java:
在java声明JsJavaBridge.class,类中的方法用@JavaScriptInterface标记,设置webView.addJavascriptInterface(new JsJavaBridge(),”bridge”)之后,可以在js中使用bridge.xxx()调用JsJavaBridge中的方法,这种方式在4.4之前有安全问题,不建议使用(不过现在基本上不会去适配4.4以下的了)
自定义WebViewClient,在shouldOverrideUrlLoading()中拦截url之后调用java代码。(这个是jsbridge中选择的方式)需要注意版本适配 API24 (7.0)
自定义WebChromeClient,拦截console之后调用java代码
自定义WebChromeClient,拦截弹框事件之后调用Java 代码
参考:https://www.jianshu.com/p/eb52ae7633b5
可以通过重写webviewClient的onPageFinished方法,在方法中通过loadurl的方式给webview注入js代码。
关于H5中的iframe:通过修改iframe的src来触发webViewClient的shouldOverrideUrlLoading方法。
流程分析 先简单描述下整个流程:
首先在webViewClient的onPageFinished的方法里加载了assets中的WebViewJavascriptBridge.js。
java->js :
Java处理消息体:生成一个唯一标示(callbackId)+ msg实体 + functionTag封装成message对象;其中functionTag是js中消息map 的key,js可以根据这个找到java希望js调用的方法。
将callbackId作为key,handler作为value放入java中的消息map;(我们给js发一个request,js处理完成之后会有一个response,这个handler就是处理response的 )
上面的js消息map 需要我们在js中注册,其实就是把functionTag作为key,把回调的方法作为value放入map中。
将message对象通过loadurl的方式传递给js
js收到消息会先判读是否有callbackId(原因在js传递消息给java中),因为我们传递过来了,那么肯定是有的,之后会去判断js的消息map 中是否有functionTag这个key(原因在java传递消息给js的默认方式中),因为我们在js中注册过了,那么肯定是有的,然后取出key对应的回调,处理java传过来的msg实体。
将callbackId跟处理完成的结果放入js中的消息队列queue。
之后会修改iframe控件src的url,在url上会拼接上“我有新消息了”,这样就会触发WebViewClient中的shouldOverrideUrlLoading方法,这样java就知道了js消息队列中有新消息了。
java通过loadurl的方式通知js取出js消息队列中的所有消息,搞成jsonString。
修改iframe控件的url,在url上会拼接上“我的所有消息:jsonString”,此时会触发WebViewClient中的shouldOverrideUrlLoading方法,这样java就收到了js消息队列中的所有消息。
最后我们取出消息,根据消息中的callbackId在java的消息map中找到对应的handler,之后处理js的response。
对应的源码流程如下:
1 2 3 4 5 6 7 webView.callHandler("functionInJs" , "发送消息给js(spec方式!!)" , new CallBackFunction() { @Override public void onCallBack (String data) { Toast.makeText(MainActivity.this , data, Toast.LENGTH_SHORT).show(); } });
进入callHandler方法:
1 2 3 4 5 6 7 8 9 10 11 @Override public void callHandler (String handlerName, String data, CallBackFunction callBack) { send(handlerName, data, callBack); }
进入send方法,作用就是上面提到的封装message。并且把callbackId跟处理response的handler放入了responseCallbacks中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private void send (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); }
进入queueMessage方法
1 2 3 4 5 6 7 8 9 10 11 private void queueMessage (Message m) { if (startupMessage != null ) { startupMessage.add(m); } else { dispatchMessage(m); } }
进入dispatchMessage,重点最后一句(将消息传递给了js)。
1 2 3 4 5 6 7 8 9 10 11 void dispatchMessage (Message m) { String messageJson = m.toJson(); ... if (Thread.currentThread() == Looper.getMainLooper().getThread()) { this .loadUrl(javascriptCommand); } }
进入js的_handleMessageFromNative方法
1 2 3 4 5 6 function _handleMessageFromNative (messageJSON ) { ... _dispatchMessageFromNative(messageJSON); }
进入_dispatchMessageFromNative方法(根据functionTag找到在js中注册处理方法handler并调用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function _dispatchMessageFromNative (messageJSON ) { setTimeout(function ( ) { var message = JSON .parse(messageJSON); var responseCallback; ... var callbackResponseId = message.callbackId; responseCallback = function (responseData ) { _doSend({ responseId: callbackResponseId, responseData: responseData }); }; ... handler = messageHandlers[message.handlerName]; ... handler(message.data, responseCallback); }); }
handler处理完成之后有个回调responseCallback,会调用_doSend方法。(将java传递过来的callbackid和js处理完成之后的response封装成一个对象作为参数)
进入_doSend方法,将消息放入消息队列 。之后修改iframe控件的src。
1 2 3 4 5 6 function _doSend (message, responseCallback ) { ... sendMessageQueue.push(message); messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; }
进入WebViewClient的shouldOverrideUrlLoading方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request) { ... String url = request.getUrl().toString(); ... url = URLDecoder.decode(url, "UTF-8" ); ... if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { webView.handlerReturnData(url); return true ; } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { webView.flushMessageQueue(); return true ; }
进入flushMessageQueue方法,其实就调用了一个方法loadUrl,有一个callback(可以在回调的时候再看)
1 2 3 4 5 6 7 8 9 10 11 void flushMessageQueue () { ... loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA, new CallBackFunction() { @Override public void onCallBack (String data) { 。。。 }); }
loadUrl方法:调用js的_fetchQueue方法并把“_fetchQueue”作为key,上面的callback作为value放入responseCallbacks中。
1 2 3 4 5 public void loadUrl (String jsUrl, CallBackFunction returnCallback) { this .loadUrl(jsUrl); responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); }
进入js中的_fetchQueue方法:取出js中所有的消息,放在url的后面
1 2 3 4 5 6 7 8 9 function _fetchQueue ( ) { var messageQueueString = JSON .stringify(sendMessageQueue); sendMessageQueue = []; if (messageQueueString !== '[]' ) { bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent (messageQueueString); } }
进入WebViewClient的shouldOverrideUrlLoading方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request) { ... String url = request.getUrl().toString(); ... url = URLDecoder.decode(url, "UTF-8" ); ... if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { webView.handlerReturnData(url); return true ; } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { webView.flushMessageQueue(); return true ; }
进入handlerReturnData方法,根据callbackid在responseCallbacks中找到处理response的handler。执行handler的callback方法。流程结束!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 ; } }
js->java:
跟java->js在本质上没有什么区别,有兴趣可以自己跟一下(如果理解了上面的流程,会很顺利的跟下来的~)。
源码中的一个小问题 源码中BridgeWebView实现了WebViewJavascriptBridge接口,该接口是对外暴露的发送消息给js的接口,源码里面只声明了两种方式(1->默认方式发送消息给js,不带回调 ; 2->默认方式发送消息给js,带回调 ),但是实际上BridgeWebView对外暴露的有3种方式,还有一种(3-> callHandler(spec方式,带回调的)),所以我把callHandler也声明到了接口里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public interface WebViewJavascriptBridge { void send (String data) ; void send (String data, CallBackFunction responseCallback) ; void callHandler (String funNameInJs, String data, CallBackFunction responseCallback) ; }