简化Android与JS交互,JsBridge框架全面解析
今日科技快讯
近日,滴滴顺风车披露了一组数字,预测春运前后,跨城出行以7天为一个周期,呈“潮汐式”变化。2月13号,有将近100万人乘滴滴顺风车踏上归途;除夕当天,还有近40万在回家的顺风车上。返程巅峰集中在2月23日前后,23日预计有超过110万人乘坐滴滴顺风车返回工作岗位。
作者简介
本篇文章来自
juexingzhe
的投稿。主要介绍了Android与JS的交互框架JsBridge源码分析的相关知识
,希望对大家有所帮助!
juexingzhe
的博客地址
:
http://www.jianshu.com/u/ea71bb3770b4
前言
在Android开发中,由于Native开发的成本较高,H5页面的开发更灵活,修改成本更低,因此前端网页JavaScript(下面简称JS)与Java之间的互相调用越来越常见。
JsBridge就是一个简化Android与JS通信的框架,源码地址:
http://github.com/lzyzsd/JsBridge
正文
我们今天通过一个简单例子来分析下开源框架JsBridge的源码。例子的代码我也放在Github,有需要的可以seesee:
http://github.com/juexingzhe/Android_JS
例子很简单,随便输入信息登陆,会加载一个H5页面,在H5界面点击按钮,Java执行getUserInfo()然后将UserInfo回传给JS,H5页面再显示UserInfo。
1.png
2.png
3.png
JS调用Android基本有下面三种方式
webView.addJavascriptInterface()
WebViewClient.shouldOverrideUrlLoading()
WebChromeClient.onJsAlert()/onJsConfirm()/onJsPrompt() 方法分别回调拦截JS对话框alert()、confirm()、prompt()消息
Android调用JS
webView.loadUrl();
webView.evaluateJavascript()
常用方法的使用后面例子中会用到,更细节的介绍各位同学可以去网上搜搜看看。
JsBridge使用
我们先来看下Java层的代码,首先引入依赖和仓库
dependencies
{ ……compile
"com.github.lzyzsd:jsbridge:1.0.4"
compile
"com.google.code.gson:gson:2.7"
}repositories
{ jcenter() maven { url"http://jitpack.io"
} }准备工作就是这样,下面可以开始撸代码,首先就是点击按钮登陆,这个简单:
Intent intent =
new
Intent(LoginActivity.this
, WebActivity.class); intent.putExtra("email"
, mEmailView.getText().toString()); startActivity(intent);布局文件中要使用BridgeWebView:
<
LinearLayout
xmlns:android
="http://schemas.android.com/apk/res/android"
android:layout_width
="match_parent"
android:layout_height
="match_parent"
android:orientation
="vertical"
><
com.github.lzyzsd.jsbridge.BridgeWebView
android:id
="@+id/web_view"
android:layout_width
="match_parent"
android:layout_height
="match_parent"
/></
LinearLayout
>在跳转后的页面,获取登陆信息并存储,再通过loadUrl加载H5页面:
Intent intent =
this
.getIntent(); String email = intent.getStringExtra("email"
); mUserInfo =new
UserInfo(email); mBridgeWebView = (BridgeWebView) findViewById(R.id.web_view); mBridgeWebView.setDefaultHandler(new
DefaultHandler()); mBridgeWebView.loadUrl("file:///android_asset/getuserinfo.html"
); registerHandler();主要是要注册Handler,供JS调用,getUserInfo就是注册供JS调用的Handler的id,data是JS传过来的参数,CallBackFunction 函数中需要把JS需要的response返回给JS
private
void
registerHandler
()
{ mBridgeWebView.registerHandler("getUserInfo"
,new
BridgeHandler() {@Override
public
void
handler
(String data, CallBackFunction function)
{ Log.i(TAG,"handler = getUserInfo, data from web = "
+ data); function.onCallBack(new
Gson().toJson(mUserInfo)); } }); }Java层的代码就这么简单,下面看下JS层工作:
首先需要一个js文件,我们写一个getuserinfo.html文件放在assets目录下,文件内容,不建议把js代码直接放在html文件中,我为了方便直接就写在这了。代码放了两个段落,一个类似于TextView用来显示用户信息,一个Button。点击按钮会调用callHandler,三个参数和Java层一一对应,在Java层返回的时候,会调用function(responseData)函数,显示用于信息。
<
html
><
head
><
meta
content
="text/html; charset=utf-8"
http-equiv
="content-type"
><
title
> js调用java</
title
></
head
><
body
><
p
><
xmp
id
="show"
></
xmp
></
p
><
div
align
="center"
><
p
><
input
type
="button"
id
="enter"
value
="获取用户信息"
onclick
="getUserInfo();"
/></
p
></
div
></
body
><
script
>
function
getUserInfo
()
{window
.WebViewJavascriptBridge.callHandler("getUserInfo"
, {"info"
:"I am JS, want to get UserInfo from Java"
},function
(responseData)
{document
.getElementById("show"
).innerHTML ="repsonseData from java,\ndata = "http://www.gunmi.cn/v/
+ responseData; } ) }</
script
></
html
>使用基本就是这样了,可以看出来JsBridge通过封装,JS和Java之间的通信只需要实现两个步骤,使用起来很方便。
JsBridge源码分析
分析之前我把JS调用Java画了个简易交互图,Java调用JS的过程类似:
4.png
是不是感觉反而更复杂了???其实只要捉住主要的三点,JsBridge就原形毕露:
Android调用JS是通过loadUrl(url),url中可以拼接要传给JS的对象
JS调用Android是通过shouldOverrideUrlLoading
JsBridge将沟通数据封装成Message,然后放进Queue,再将Queue进行传输
接下来我们来一步一步跟踪上面例子的调用过程:
JS层点击按钮调用callHandler
handlerName,Java和JS要一致,data是Java层handlerName函数执行的参数。responseCallback是Java执行完handlerName返回时,JS回调的接口,是JS执行
onclick=
"getUserInfo();"
function
getUserInfo
()
{window
.WebViewJavascriptBridge.callHandler("getUserInfo"
, {"info"
:"I am JS, want to get UserInfo from Java"
},function
(responseData)
{document
.getElementById("show"
).innerHTML ="repsonseData from java,\ndata = "http://www.gunmi.cn/v/
+ responseData; } ) }callHandler会调用_doSend。如果JS需要回调,就将回调的callbackId放进message中,Java执行完会传回callbackId,这里是cb_1_1495182409011,构造完message放进队列sendMessageQueue。通过iframe属性给Java发送通知消息,消息结构为:
yy://QUEUE_MESSAGE/
function
callHandler
(handlerName, data, responseCallback)
{ _doSend({ handlerName: handlerName, data: data }, responseCallback); }function
_doSend
(message, responseCallback)
{if
(responseCallback) {var
callbackId ="cb_"
+ (uniqueId++) +"_"
+new
Date
().getTime(); responseCallbacks[callbackId] = responseCallback; message.callbackId = callbackId; } sendMessageQueue.push(message); messagingIframe.src = http://www.gunmi.cn/v/CUSTOM_PROTOCOL_SCHEME +"://"
+ QUEUE_HAS_MESSAGE; }Java收到通知消息
WebView在shouldOverrideUrlLoading拦截到url:yy://QUEUE_MESSAGE/
然后会执行webView.flushMessageQueue(),在主线程执行loadUrl通知JS层推送队列到Java;
void
flushMessageQueue
()
{if
(Thread.currentThread() == Looper.getMainLooper().getThread()) { loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,new
CallBackFunction() {@Override
public
void
onCallBack
(String data)
{//
}); } }public
void
loadUrl
(String jsUrl, CallBackFunction returnCallback)
{this
.loadUrl(jsUrl); responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback); }JS 发送Request Queue
执行_fetchQueue,将sendMessageQueue转化成JSON。通过iframe属性给Java发送通知消息。
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
messagingIframe.src = http://www.gunmi.cn/v/CUSTOM_PROTOCOL_SCHEME +"://return/_fetchQueue/"
+encodeURIComponent
(messageQueueString); }Java收到调用通知
进行处理并发送Response Queue到JS,WebView在shouldOverrideUrlLoading会拦截到url,执行webView.handlerReturnData(url);根据函数名_fetchQueue拿到之前注册的回调函数CallBackFunction returnCallback,执行回调函数,并且从注册中移除。
void
handlerReturnData
(String url)
{ String functionName = BridgeUtil.getFunctionFromReturnUrl(url); CallBackFunction f = responseCallbacks.get(functionName); String data = http://www.gunmi.cn/v/BridgeUtil.getDataFromReturnUrl(url);if
(f !=null
) { f.onCallBack(data); responseCallbacks.remove(functionName);return
; } }接下来就是对Request Queue的解析然后找到JS希望调用Handler并且执行,代码中我写了注释,可以直接看:
//回调接口执行onCallBack函数
//其中data [{"handlerName":"getUserInfo","data":{"info":"I am JS, want to get UserInfo from Java"},"callbackId":"cb_1_1495180503779"}]
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
{//将JSON数组转化成Java list
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++) {//从list中取出Message
Message m = list.get(i);//在我们的例子中没有responseId,因此到else分支
String responseId = m.getResponseId();// 是否是response
if
(!TextUtils.isEmpty(responseId)) { CallBackFunction function = responseCallbacks.get(responseId); String responseData = http://www.gunmi.cn/v/m.getResponseData(); function.onCallBack(responseData); responseCallbacks.remove(responseId); }else
{ CallBackFunction responseFunction =null
;// if had callbackId
//如果有callbackId就说明JS需要回调,因此Java层需要构造responseMsg
//从message中取出callbackId,放进responseMsg
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;//从message中取出Handler名字,再从messageHandlers中取
//如果没有就使用默认的Handler
if
(!TextUtils.isEmpty(m.getHandlerName())) { handler = messageHandlers.get(m.getHandlerName()); }else
{ handler = defaultHandler; }if
(handler !=null
){//执行handler
handler.handler(m.getData(), responseFunction); } } } } }); } }那么这个handler是什么?就是Java调用registerHandler注册的getUserInfo
private
void
registerHandler
()
{ mBridgeWebView.registerHandler("getUserInfo"
,new
BridgeHandler() {@Override
public
void
handler
(String data, CallBackFunction function)
{ Log.i(TAG,"handler = getUserInfo, data from web = "
+ data); function.onCallBack(new
Gson().toJson(mUserInfo)); } }上面的function就是在flushMessageQueue 解析时构造的responseFunction,在message中包括JS层需要回调的函数Id,然后就是getUserInfo执行的结果。调用queueMessage
responseFunction =
new
CallBackFunction() {@Override
public
void
onCallBack
(String data)
{ Message responseMsg =new
Message(); responseMsg.setResponseId(callbackId); responseMsg.setResponseData(data); queueMessage(responseMsg); } };通过构造String指令,然后loadUrl执行JS代码,注意对象也是通过这样方式传递过去的,就类似调用本地函数,不发起网络请求
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); } }其中
BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA =
"javascript:WebViewJavascriptBridge._handleMessageFromNative("%s");"
javascriptCommand = javascript:WebViewJavascriptBridge._handleMessageFromNative(
"{\"responseData\":\"{\\\"email\\\":\\\"jue@126.com\\\"}\",\"responseId\":\"cb_1_1495182558893\"}"
);//data = http://www.gunmi.cn/v/{/"responseData\":\"{\\\"email\\\":\\\"jue@126.com\\\"}\",\"responseId\":\"cb_1_1495182409011\"}
JS收到Response JSON
来到_handleMessageFromNative
function
_handleMessageFromNative
(messageJSON)
{console
.log(messageJSON);if
(receiveMessageQueue && receiveMessageQueue.length >0
) { receiveMessageQueue.push(messageJSON); }else
{ _dispatchMessageFromNative(messageJSON); } }最后都会调用到_dispatchMessageFromNative,由于是JS主动调用Java,因此有responseId,执行registerHandler时传入的CallBack,也就是显示用户信息。我在代码里加了注释,很容易看懂。
function
_dispatchMessageFromNative
(messageJSON)
{ setTimeout(function
()
{//将数据解析成JSON
var
message =JSON
.parse(messageJSON);var
responseCallback;//java call finished, now need to call js callback function
//根据responseId:cb_1_1495182409011拿到responseCallback,就是我们前门注册的alert
if
(message.responseId) { responseCallback = responseCallbacks[message.responseId];if
(!responseCallback) {return
; } responseCallback(message.responseData);delete
responseCallbacks[message.responseId]; }else
{//直接发送
if
(message.callbackId) {var
callbackResponseId = message.callbackId; responseCallback =function
(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); } } } }); }源码的分析就到这结束了,代码不多,但是封装的接口很是好用。
总结
最后总结下,使用上很方便主要两个步骤
被调用方注册Handler
registerHandler(String handlerName, BridgeHandler handler)
调用方调用Handler
callHandler(String handlerName, String data, CallBackFunction callBack)
原理上还是那三句话,请原谅我从上面直接copy过来:
Android调用JS是通过loadUrl(url),url中可以拼接要传给JS的对象
JS调用Android是通过shouldOverrideUrlLoading
JsBridge将沟通数据封装成Message,然后放进Queue,再将Queue进行传输
好了,今天我们JsBridge的使用和源码分析就到这了,谢谢!
文中例子的链接:
http://github.com/juexingzhe/Android_JS
欢迎长按下图 -> 识别图中二维码
或者 扫一扫 关注我的公众号
- Android 9.0 开发代号定为“Pistachio Ice Cream”,有哪些功能
- 冯骥才:简化或淡化年俗,是文化的怠慢与缺失
- Android 9.0正式确认!代号:开心果冰淇淋
- 交互原型工具:好的工具是利器,坏的工具是钝器
- 你知道吗?以色列电子设备3D打印机的新增插件能简化产品设计
- 三星终于升级Android8.0 苹果Airpods发生冒烟起火事件 | 动点播
- Fuchsia OS能补上Android的硬伤么?
- 开发者不买账! Android Go发布半年仅十余款App可用
- Android-x86 7.1-rc2 发布,PC 上的安卓系统
- Canvas的那些事,图解Android中的绘制技巧