参考:
Android 与 JS 之 JsBridge 使用与源码分析
demo地址:
JsBridgeDemo
今天分析的是大头鬼的https://github.com/lzyzsd/JsBridge, 废话不多说, 先从一个例子开始.
用法简析
在h5页面中有一个”调用相机”的按钮, 点击按钮会传递用户的id给原生方法, 同时调起相机拍照.拍照完之后将图片上传到服务器, 这一步网路请求会有校验所以需要用户id这个参数.将图片上传完成之后原生再将服务器返回的图片地址传递给js, h5拿到图片地址之后将图片显示到页面上.当然为了简化, 我这里只是用了一些假的代码来模拟这些操作.java代码如下:
1 | public class WebActivity extends Activity { |
web.html的代码如下:
1 | <html> |
注意到html代码中callHandler方法的第一个参数’takePhoto’和java代码中registerHandler中的第一个参数”takePhoto”是一样的.另外, assets文件夹中还有一个”WebViewJavascriptBridge.js”的js文件也是不可或缺的.如果是实际使用这套方案, 应该将这个文件交给前端人员去调用.
源码分析
现在从源码, 一步步分析上面这个过程的代码调用流程.
首先从js代码开始, 点击”调用相机”按钮, 会调用jsCallJava()方法.在这个方法里, 调用了WebViewJavascriptBridge的callHandler方法
1 | function jsCallJava() { |
1 | function callHandler(handlerName, data, responseCallback) { |
callHandler方法会调用_doSend方法
1 | // sendMessage add message, 触发native处理 sendMessage |
首先responseCallback是不为空的, 所以会走if里面的语句.首先会生成一个唯一的callbackId(避免重复), 类似cb_1_1533191074448
这样的, 然后以callbackId为key, 回调函数为value, 放在responseCallbacks这个对象里, 同时在message这个对象里也存一份.然后再往sendMessageQueue这个数组里push一个message对象.接着会变更messagingIframe元素的的src属性.在这里, messagingIframe是一个iframe元素
1 | function _createQueueReadyIframe(doc) { |
iframe元素的src属性变更后,java部分触发shouldOverrideUrlLoading 方法, 这部分代码在BridgeWebViewClient里面
1 |
|
这里会走第二个if, 调用BridgeWebView的flushMessageQueue()方法
1 | /** |
在这个flushMessageQueue方法里, 如果当前是主线程就调用一个loadUrl方法
1 | public void loadUrl(String jsUrl, CallBackFunction returnCallback) { |
在这个方法里, 首先会调用WebViewJavascriptBridge的_fetchQueue()方法, 然后解析方法名字, 因为这里的方法名字是写死的, 其实就是_fetchQueue, 请记住这个名字, 因为后面会用到.然后将以这个_fetchQueue为key, 回调方法为value, 放到一个map里面.然后我们再去看js那端的方法.
1 | // 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容 |
sendMessageQueue这个数组我们在_doSend()方法中用到过, 里面push了一个message对象, json格式化之后字符串就是[{"handlerName":"takePhoto","data":1234,"callbackId":"cb_2_1532852750705"}]
这样的, 然后将sendMessageQueue这个数组置空, 接着再次变更iframe的src属性, 触发java的shouldOverrideUrlLoading方法, 传递的url是类似yy://return/_fetchQueue/%5B%7B%22handlerName%22%3A%22takePhoto%22%2C%22data%22%3A1234%2C%22callbackId%22%3A%22cb_1_1532853343154%22%7D%5D
这样的.
现在又回到java端的方法了
1 |
|
首先将url解码, 这里会走第一个if, 调用handlerReturnData(url)方法
1 | /** |
在这里会在url中解析出方法名和传递的数据, 方法名就是_fetchQueue.先根据方法名从Map中取出回调方法, 然后给回调方法传递数据, 然后调用回调方法, 完成之后Map中移除这个回调方法.
接着我们看这个回调方法中的逻辑, 也就是flushMessageQueue中的回调逻辑
1 | void flushMessageQueue() { |
首先将数据解析成一个Message的list, 数据是类似[{"handlerName":"takePhoto","data":1234,"callbackId":"cb_3_1532855579967"}]
这样的.
这个Message是自定义的类, 有callbackId, responseId, responseData, data, handlerName这几个成员变量.接着遍历这个list中的每一个Message, 因为没有responseId, 并且callbackId不为空, 所以会生成一个CallBackFunction, 这里面的逻辑我们暂时不看.接着会根据Message中的handlerName, 从messageHandlers中获取一个BridgeHandler对象, 这个对象是我们在WebActivity中注册放到messageHandlers这个map里的, 看一下代码
1 | mBridgeWebView.registerHandler("takePhoto", new BridgeHandler() { |
在这里我们就能明白, 为什么java代码中的方法名字和js代码中的方法名字都要取做takePhoto
再回过来, 然后我们就调用这个BridgeHandler的handler方法, 传递的参数一个是Message中的data, 另一个是上面的CallBackFunction.
接着在handler方法中, 我们先做了一些自定义的操作, 然后调用了CallBackFunction的onCallBack方法, 参数是一张图片的url.
看一下这个onCallBack方法中的逻辑
1 | responseFunction = new CallBackFunction() { |
创建了一个responseMsg, 然后调用了queueMessage()方法
1 | private void queueMessage(Message m) { |
这里会走else, 接着看dispatchMessage()方法
1 | /** |
首先将这个Message转化成json格式的字符串, 去掉一些特殊字符, 然后再主线程调用js方法, 方法是WebViewJavascriptBridge._handleMessageFromNative方法, 然后我们去看一下这个代码
1 | // 提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以 |
这里receiveMessageQueue是空的, 所以会走else
1 | // 提供给native使用, |
首先这里的messageJSON
是类似{"responseData":"http:\/\/ww3.sinaimg.cn\/mw690\/96a29af5jw8fdfu43tnvlj20ro0rotab.jpg","responseId":"cb_1_1532864396479"}
这样的, 所以responseId是有的, 接着根据responseId从responseCallbacks中取出responseCallback, 这个responseCallback是在_doSend()方法中存进去的, 也就是一开始js代码中callHandler方法中传进去的一个方法.将message.responseData传递给这个方法, 执行完之后从responseCallbacks这个对象里删除responseCallback方法.至于responseCallback里的方法具体是什么逻辑就是将图片地址设置到页面上了
1 | function jsCallJava() { |
到这里, js调用java代码的一次完整流程就分析完了, 还是有点绕的.
最后盗一张图总结一下
一次流程估计不容易记住, 多看几遍就顺了.
结语
最后是我对于这个jsbridge的看法, 首先优点是封装的比较方便, java端和js端的代码写起来都更加方便了.
可以改进的地方就是java调用js可以分api版本使用loadUrl()或者evaluateJavascript()方法.然后js调用java我更喜欢在onJsPrompt()中回调.还有就是来回的交互流程太多了, 感觉可以再简化一些, 比如可以在第一次就将js给java的数据传递过去的, 而现在第一次传递过去的只是个标记而已。最后一点是js调用java没有办法全局调用, 每个webview都需要注册一个handler, 然后单独在里面写逻辑.不过这一点其实不算什么缺点, 毕竟每个项目的要求都不一样, 不能强求.