JsBridge源码分析

来源:互联网 发布:mask rcnn tensorflow 编辑:程序博客网 时间:2024/06/07 20:04

一.项目介绍

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/

原创粉丝点击