简化Android与JS交互,JsBridge框架全面解析
2018-01-25 12:44 阅读(340)

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

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

https://github.com/lzyzsd/JsBridge


正文


我们今天通过一个简单例子来分析下开源框架JsBridge的源码。例子的代码我也放在Github,有需要的可以seesee:

https://github.com/juexingzhe/Android_JS

例子很简单,随便输入信息登陆,会加载一个H5页面,在H5界面点击按钮,Java执行getUserInfo()然后将UserInfo回传给JS,H5页面再显示UserInfo。

1.png

2.png

3.png

JS调用Android基本有下面三种方式

Android调用JS

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

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:

<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 = " + responseData; 
            } 
        ) 
    } 
</script> 
</html>

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

JsBridge源码分析

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

4.png

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

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

  b. JS调用Android是通过shouldOverrideUrlLoading

  c. JsBridge将沟通数据封装成Message,然后放进Queue,再将Queue进行传输

接下来我们来一步一步跟踪上面例子的调用过程:

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

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

执行_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); 
}

进行处理并发送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 = 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 = 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 = {\"responseData\":\"{\\\"email\\\":\\\"jue@126.com\\\"}\",\"responseId\":\"cb_1_1495182409011\"}

来到_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过来:

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

   b. JS调用Android是通过shouldOverrideUrlLoading

   c. JsBridge将沟通数据封装成Message,然后放进Queue,再将Queue进行传输

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

文中例子的链接:

https://github.com/juexingzhe/Android_JS




作者简介


本篇文章来自 juexingzhe 的投稿。主要介绍了Android与JS的交互框架JsBridge源码分析的相关知识,希望对大家有所帮助!

juexingzhe 的博客地址

https://www.jianshu.com/u/ea71bb3770b4



返回顶部