博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android与JS之JsBridge使用与源码分析
阅读量:6691 次
发布时间:2019-06-25

本文共 14357 字,大约阅读时间需要 47 分钟。

hot3.png

 

在Android开发中,由于Native开发的成本较高,H5页面的开发更灵活,修改成本更低,因此前端网页JavaScript(下面简称JS)与Java之间的互相调用越来越常见。

JsBridge就是一个简化Android与JS通信的框架,源码:

我们今天通过一个简单栗子来分析下开源框架JsBridge的源码。栗子的代码我也放在Github,有需要的可以seesee:
栗子很简单,随便输入信息登陆,会加载一个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()

常用方法的使用后面栗子中会用到,更细节的介绍各位同学可以去网上搜搜看看。

1.JsBridge使用

我们先来看下Java层的代码

首先引入依赖和仓库

dependencies {   ……    compile 'com.github.lzyzsd:jsbridge:1.0.4'    compile 'com.google.code.gson:gson:2.7'}
repositories {    jcenter()    maven { url "https://jitpack.io" }}

准备工作就是这样,下面可以开始撸代码,首先就是点击按钮登陆,这个简单:

Intent intent = new Intent(LoginActivity.this, WebActivity.class);intent.putExtra("email", mEmailView.getText().toString());startActivity(intent);

布局文件中要使用BridgeWebView:

在跳转后的页面,获取登陆信息并存储,再通过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)函数,显示用于信息。

    
js调用java

<div></div>

使用基本就是这样了,可以看出来JsBridge通过封装,JS和Java之间的通信只需要实现两个步骤,使用起来很方便。

我们来看下源码是怎么个玩法,先来个华丽丽的分割线

2.JsBridge源码分析

分析之前我把JS调用Java画了个简易交互图,Java调用JS的过程类似:

4.png

是不是感觉反而更复杂了???其实只要捉住主要的三点,JsBridge就原形毕露:

1.Android调用JS是通过loadUrl(url),url中可以拼接要传给JS的对象

2.JS调用Android是通过shouldOverrideUrlLoading
3.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 = " + 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 = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;}
  • Java收到通知消息
    WebView在shouldOverrideUrlLoading拦截到url:yy://QUEUE_MESSAGE/
    然后会执行webView.flushMessageQueue(),在主线程执行loadUrl通知JS层推送队列到Java;

JS_FETCH_QUEUE_FROM_JAVA = "javascript:WebViewJavascriptBridge._fetchQueue();"

调用JS层的_fetchQueue,通知JS层发送队列到Java层
在responseCallbacks中注册回调接口,接口id是函数名_fetchQueue,在JS推送消息队列时进行回调

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 = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);}
  • Java收到调用通知,进行处理并发送Response Queue到JS
    WebView在shouldOverrideUrlLoading会拦截到url:
yy://return/_fetchQueue/[{"handlerName":"getUserInfo","data":{"info":"I am JS, want to get UserInfo from Java"},"callbackId":"cb_1_1495180503779"}]

执行webView.handlerReturnData(url);

根据函数名_fetchQueue拿到之前注册的回调函数CallBackFunction returnCallback

执行回调函数,并且从注册中移除

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;          }}

接下来就是对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
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 = 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);          }};

queueMessage调用dispatchMessage发送message给JS

通过构造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 = {\"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);                    }                }            }        });}

源码的分析就到这结束了,代码不多,但是封装的接口很是好用。最后再来个分割线~~

3.总结

最后总结下,使用上很方便主要两个步骤

被调用方注册Handler

registerHandler(String handlerName, BridgeHandler handler)

调用方调用Handler

callHandler(String handlerName, String data, CallBackFunction callBack)

原理上还是那三句话,请原谅我从上面直接copy过来:

1.Android调用JS是通过loadUrl(url),url中可以拼接要传给JS的对象

2.JS调用Android是通过shouldOverrideUrlLoading
3.JsBridge将沟通数据封装成Message,然后放进Queue,再将Queue进行传输

好了,今天我们JsBridge的使用和源码分析就到这了,谢谢!

文中栗子的链接:

作者:juexingzhe
链接:https://www.jianshu.com/p/e4f7fb571cc0
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://my.oschina.net/JiangTun/blog/1612700

你可能感兴趣的文章
操作系统的发展
查看>>
HEVC码流简单分析
查看>>
搭建蚂蚁笔记(服务器)
查看>>
lnmp
查看>>
二分查找
查看>>
Cloud Test 在手,宕机时让您不再措手不及
查看>>
Centos7.2安装Vmware Tools
查看>>
深入理解Java内存模型(一)——基础
查看>>
美图秀秀下载|美图秀秀电脑版下
查看>>
生产者消费者模式
查看>>
tomcat的Context配置,虚拟访问数据
查看>>
ORACLE---添加控制文件
查看>>
Qt中QString,char,int,QByteArray之间到转换
查看>>
Exchange Server 2007邮箱存储服务器的集群和高可用性技术(上)
查看>>
磁盘管理与磁盘阵列RAID
查看>>
Linux学习笔记4-软件安装
查看>>
8.python之面相对象part.8(类装饰器)
查看>>
Spark通过Java Web提交任务
查看>>
Javascript动态加载脚本与样式
查看>>
LINUX用户和组小练习
查看>>