JsBridge源码分析

一.项目介绍

jsbridege是一个开源的项目。主要实现webview和native之间的交互。是支撑Hybrid模式非常重要的基础组件。

基本上各个大厂都有自己的SDK。比如腾讯,美团,携程等等,一些公司也在向外输出自己的专业能力。但是基本上原理都差不多,不过大厂的SDK在兼容性、功能性、安全性上更好一些。

项目地址:

https://github.com/lzyzsd/JsBridge

二.JsBridge原理

在 Js 和 WebView 交互的过程中,主要实现两个方向可以通信即可。
WebView 向 Js 传递数据是通过 WebView.loadUrl(String url) 实现的
在 WebView 中接收 Js 传递的数据是通过 WebViewClient 中的 shouldOverrideUrlLoading(WebView view, String url) 拦截加载链接 String url 参数实现的。

三.JsBridge简单用法

public class JSBridgeActivity extends AppCompatActivity {

    private BridgeWebView mBridgeWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jsbridge);
        initView();
    }

    private void initView() {
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
        //写一个最简单的双向的调用
        //jsbridge目的就是为了实现webview和native的通信。
        mBridgeWebView = (BridgeWebView) findViewById(R.id.webView);
        mBridgeWebView.loadUrl("file:///android_asset/demo.html");
        jsSendToNative();

        //java传递给前端js

    }

    private void jsSendToNative() {

        //前端传递给java:
        //点击前端对应的按钮:DefaultHandler方式,调用的代码如下:
        //前端对应的代码
///*用来展示默认方式*/
//        function testClick() {
//            var str1 = document.getElementById("text1").value;
//            var str2 = document.getElementById("text2").value;
//            //send message to native
//            var data = {id: 1, content: "我是内容哦"};
//            window.WebViewJavascriptBridge.send(
//                    data
//                    , function(responseData) {
//                document.getElementById("show").innerHTML = "data = " + responseData
//            }
//            );
//        }

        mBridgeWebView.setDefaultHandler(new BridgeHandler() {
            @Override
            public void handler(String data, CallBackFunction function) {
                //data是js返回的数据
                Toast.makeText(JSBridgeActivity.this, data, Toast.LENGTH_LONG).show();
            }
        });
    }

}

查看更多demo可移步下面的博客链接。
Android之利用JSBridge库实现Html,JavaScript与Android的所有交互
http://www.cnblogs.com/zhangqie/p/6724252.html

四.JsBridge总体设计

客户端调用JavaSript

web前端调用native

五.demo流程源码分析

运行环境设计java和js两个部分,调用流程在2部分之间交互。需要移动端同学了解一些js语法。
这里具体看一下demo中点击html中的DefaultHandler方式按钮后,js里的数据怎么传递给Android native的。

///*用来展示默认方式*/
//        function testClick() {
//            var str1 = document.getElementById("text1").value;
//            var str2 = document.getElementById("text2").value;
//            //send message to native
//            var data = {id: 1, content: "我是内容哦"};
//            window.WebViewJavascriptBridge.send(
//                    data
//                    , function(responseData) {
//                document.getElementById("show").innerHTML = "data = " + responseData
//            }
//            );
//        }

1.js:中的testClick调用到WebViewJavascriptBridge.js中的send方法

function send(data, responseCallback) {
        _doSend({
            data: data
        }, responseCallback);
    }

//sendMessage add message, 触发native处理 sendMessage
    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;
    }

这里调用到doSend,将消息存放在sendMessageQueue中,将responseCallback放在responseCallbacks数组中。
这里着重关注几点:
1.message的callbackId.callbackId由uniqueId配合时间生成,用于后续查找responseCallback回调。callbackId用于html页面中send方法的回调。
2.更换iFrame的src,触发BridgeWebViewClient的shouldOverrideUrlLoading方法。
3.更换src,前缀为yy://QUEUE_MESSAGE/。

2.webview:匹配到shouldOverrideUrlLoading,进入到BridgeWebView的flushMessageQueue方法

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 {
                        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++) {
                        Message m = list.get(i);
                        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
                            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;
                            if (!TextUtils.isEmpty(m.getHandlerName())) {
                                handler = messageHandlers.get(m.getHandlerName());
                            } else {
                                handler = defaultHandler;
                            }
                            if (handler != null){
                                handler.handler(m.getData(), responseFunction);
                            }
                        }
                    }
                }
            });
        }
    }

flushMessageQueue通过主要调用到了loadUrl方法:

public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
        this.loadUrl(jsUrl);
        responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl), returnCallback);
    }

loadUrl方法:
1.首先会去调用BridgeWebView的loadUrl方法。去执行javascript:WebViewJavascriptBridge._fetchQueue()js语句调用。
2.注册了一个回调函数。还将对应的回调函数放在responseCallbacks中, key是_fetchQueue。

3.js:_fetchQueue方法

// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
    function _fetchQueue() {
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        //add by hq
        if (isIphone()) {
            return messageQueueString;
            //android can't read directly the return data, so we can reload iframe src to communicate with java
        } else if (isAndroid()) {
            messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
        }
    }

这里讲sendMessageQueue数组中的所有消息,序列化为json字符串,通过更改iFrame的src,触发shouldOverrideUrlLoading方法。

4.webview:handlerReturnData方法

将关注点放到shouldOverrideUrlLoading里去调用到的是handlerReturnData方法。

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

这里就和步骤2对应上了。即webview loadjs的_fetchQueue后,fetchqueue的返回结果会被传递到 onCallback的参数data中:

new CallBackFunction() {

                @Override
                public void onCallBack(String data) {
                    // deserializeMessage
                    List<Message> list = null;
                    try {
                        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++) {
                        Message m = list.get(i);
                        String responseId = m.getResponseId();
                        // 是否是response。即java发消息给web时,传入的回调函数
                        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
                            final String callbackId = m.getCallbackId();
                            //即web端发消息给native时,注册的回调函数。需要通过native->js触发
                            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;
                            if (!TextUtils.isEmpty(m.getHandlerName())) {
                                handler = messageHandlers.get(m.getHandlerName());
                            } else {
                               //默认Handler
                                handler = defaultHandler;
                            }
                            if (handler != null){
                                handler.handler(m.getData(), responseFunction);
                            }
                        }
                    }
                }
            }

这里算是比较核心的部分了。
这里会调用到handler的handle方法。
即调用到了handler的handle方法中。
这里只是分析了在web上点击一个按钮传递数据给native的情况。

六.模块分析

1.BridgeWebViewClient

是在BridgeWebView构造方法调用了init,init主要设置了
webviewclient = new BridgeWebViewClient().

public class BridgeWebViewClient extends WebViewClient {
    private BridgeWebView webView;

    public BridgeWebViewClient(BridgeWebView webView) {
        this.webView = webView;
    }

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { // 如果是返回数据
            webView.handlerReturnData(url);
            return true;
        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
            webView.flushMessageQueue();
            return true;
        } else {
            return super.shouldOverrideUrlLoading(view, url);
        }
    }

    @Override
    public void onPageStarted(WebView view, String url, Bitmap favicon) {
        super.onPageStarted(view, url, favicon);
    }

    @Override
    public void onPageFinished(WebView view, String url) {
        super.onPageFinished(view, url);

        if (BridgeWebView.toLoadJs != null) {
            BridgeUtil.webViewLoadLocalJs(view, BridgeWebView.toLoadJs);
        }

        //
        if (webView.getStartupMessage() != null) {
            for (Message m : webView.getStartupMessage()) {
                webView.dispatchMessage(m);
            }
            webView.setStartupMessage(null);
        }
    }

    @Override
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
        super.onReceivedError(view, errorCode, description, failingUrl);
    }
}

代码中有几个要点需要关注:
1.onPageFinished方法中会去调用webview的loadUrl方法加载一段js代码:WebViewJavascriptBridge.js.
2.shouldOverrideUrlLoading方法中,会去匹配url的起始,分别调用不同的方法。

七.阅读体会&优缺点&改进意见

iframe的问题
http://blog.csdn.net/u014099894/article/details/72673438

js简单学习
iframe也称作嵌入式框架,嵌入式框架和框架网页类似,它可以把一个网页的框架和内容嵌入在现有的网页中。iframe用于设置文本或图形的浮动图文框或容器。
修改iframe的src会调用webview的shouldOverrideUrlLoading方法。

参考资料

JSbridge系列解析(四):Web端发消息给Native代码流程具体分析
http://cdn2.jianshu.io/p/730eaba1a617
JsBridge源码解析
http://lijiankun24.com/JsBridge%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值