当前位置: 移动技术网 > IT编程>移动开发>Android > Android JsBridge源码学习

Android JsBridge源码学习

2020年03月09日  | 移动技术网IT编程  | 我要评论

小驴快运,克拉玛依二手房出售,线路检修

android jsbridge源码学习

众所周知android 4.2以下的webview存在addjavascriptinterface漏洞的问题,不太了解的同学可参考 @javascriptinterface 因此,公司项目中很早便使用 jsbridge 实现 “js与native的通信” 了。

native与js通信原理

android端webview启动时,会加载一段webviewjavascriptbridge.js的js脚本代码。

  • native调用js代码: 当native需要向js端传递数据时,直接在android webview中使用webview.loadurl(javascript:webviewjavascriptbridge.xxxxx)调用在webviewjavascriptbridge.js中提前定义好的xxxxx方法,将数据传递到js端;
  • js调用native代码: 当js需要将数据传递给native时,通过js reload iframe将数据传递到native的shouldoverrideurlloading(webview view, string url) 方法的url参数中,android端通过截获url获取js传递过来的参数。

以此来实现native与js的通信。

github源码

lzyzsd/jsbridge

我注释的jsbridge

native调用js代码,向js端传递数据

以下是**“ native向js端传递数据,并接受js回调数据 ”**的时序图

sequencediagram participant bridgewebview.java as clienta participant webviewjavascriptbridge.js as servera participant demo.html as serverb note over clienta: native向js端传递数据 clienta-->>clienta: bridgewebview.callhandler\n("functioninjs", \n"native向js问好",\n mcallbackfunction); clienta-->>clienta: dosend(handlername, data, responsecallback) clienta-->>clienta: queuemessage(m) clienta-->>clienta: dispatchmessage(m) clienta->>servera: bridgewebview.loadurl(javascriptcommand)\n调用js的_handlemessagefromnative方法 servera-->>servera: _handlemessagefromnative(messagejson) servera-->>servera: _dispatchmessagefromnative(messagejson) servera->>serverb: handler(message.data, responsecallback) serverb-->>serverb: bridge.registerhandler\n("functioninjs", \nfunction(data, responsecallback)) serverb-->>servera: responsecallback(responsedata) servera-->>servera: _dosend({responseid,responsedata}); servera-->>clienta: reload iframe "yy://__queue_message__/" clienta-->>clienta: shouldoverrideurlloading(view, url) clienta-->>clienta: flushmessagequeue() clienta->>servera: bridgewebview.loadurl(javascriptcommand)\n调用js的_fetchqueue()方法 servera-->>servera: _fetchqueue() servera-->>clienta: reload iframe "yy://return/_fetchqueue/[{"data"}]" clienta-->>clienta: handlerreturndata(string url) clienta-->>clienta: flushmessagequeue中oncallback clienta-->>clienta: mcallbackfunction.oncallback(responsedata)

bridgewebview.java

callhandler("functioninjs", "native向js问好", mcallbackfunction);

/**
     * native调用js
     * <p>
     * call javascript registered handler
     * 调用javascript处理程序注册
     *
     * @param handlername js中注册的handlername
     * @param data        native传递给js的数据
     * @param callback    js处理完成后,回调到native
     */
    public void callhandler(string handlername, string data, callbackfunction callback) {
        dosend(handlername, data, callback);
    }

注释很全,看注释吧,不作讲解

bridgewebview.java

dosend(handlername, data, responsecallback)

	/**
     * native 调用 js
     * <p>
     * 保存message到消息队列
     *
     * @param handlername      js中注册的handlername
     * @param data             native传递给js的数据
     * @param responsecallback js处理完成后,回调到native
     */
    private void dosend(string handlername, string data, callbackfunction responsecallback) {
        logutils.e(tag, "dosend——>data: " + data);
        logutils.e(tag, "dosend——>handlername: " + handlername);
        // 创建一个消息体
        message m = new message();
        // 添加数据
        if (!textutils.isempty(data)) {
            m.setdata(data);
        }
        //
        if (responsecallback != null) {
            // 创建回调id
            string callbackstr = string.format(bridgeutil.callback_id_format, ++uniqueid + (bridgeutil.underline_str + systemclock.currentthreadtimemillis()));
            // 1、js回调native数据时候使用;key: id value: callback (通过js返回的callbackid 可以找到相应的callback方法)
            responsecallbacks.put(callbackstr, responsecallback);
            // 1、js回调native数据时候使用;key: id value: callback (通过js返回的callbackid 可以找到相应的callback方法)
            m.setcallbackid(callbackstr);
        }
        // js中注册的方法名称
        if (!textutils.isempty(handlername)) {
            m.sethandlername(handlername);
        }
        logutils.e(tag, "dosend——>message: " + m.tojson());
        // 添加消息 或者 分发消息到js
        queuemessage(m);
    }
  • 做了一个message来封装data数据
  • 创建了一个callbackid,并将对应的引用存储在map<string, callbackfunction> responsecallbacks,这样js相应方法处理结束后,将js的处理结果返回来的时候,native可通过该callbackid找到对应的callbackfunction,从而完成数据回调。
	/**
	 * bridgewebview.java
     * list<message> != null 添加到消息集合否则分发消息
     *
     * @param m message
     */
    private void queuemessage(message m) {
        logutils.e(tag, "queuemessage——>message: " + m.tojson());
        if (startupmessage != null) {
            startupmessage.add(m);
        } else {
            // 分发消息
            dispatchmessage(m);
        }
    }

    /**
    * bridgewebview.java
     * 分发message 必须在主线程才分发成功
     *
     * @param m message
     */
    void dispatchmessage(message m) {
        logutils.e(tag, "dispatchmessage——>message: " + m.tojson());
        // 转化为json字符串
        string messagejson = m.tojson();
        //escape special characters for json string  为json字符串转义特殊字符
        messagejson = messagejson.replaceall("(\\\\)([^utrn])", "\\\\\\\\$1$2");
        messagejson = messagejson.replaceall("(?<=[^\\\\])(\")", "\\\\\"");
        string javascriptcommand = string.format(bridgeutil.js_handle_message_from_java, messagejson);

        logutils.e(tag, "dispatchmessage——>javascriptcommand: " + javascriptcommand);
        // 必须要找主线程才会将数据传递出去 --- 划重点
        if (thread.currentthread() == looper.getmainlooper().getthread()) {
            // 调用js中_handlemessagefromnative方法
            this.loadurl(javascriptcommand);
        }
    }
  • dispatchmessage中,通过load javascript:webviewjavascriptbridge._handlemessagefromnative('%s');将message数据传递到js方法的_handlemessagefromnative当中
// native通过loadurl(js_handle_message_from_java),调用js中_handlemessagefromnative方法,实现native向js传递数据
final static string js_handle_message_from_java = "javascript:webviewjavascriptbridge._handlemessagefromnative('%s');";

webviewjavascriptbridge.js

    // 1、收到native的消息
    // 提供给native调用,receivemessagequeue 在会在页面加载完后赋值为null,所以
    function _handlemessagefromnative(messagejson) {
        //
        console.log(messagejson);
        // 添加到消息队列
        if (receivemessagequeue) {
            receivemessagequeue.push(messagejson);
        }
        // 分发native消息
        _dispatchmessagefromnative(messagejson);
       
    }
  • 这里将native发送过来的消息添加到receivemessagequeue数组中。
    //2、分发native消息
    function _dispatchmessagefromnative(messagejson) {
        settimeout(function() {
            // 解析消息
            var message = json.parse(messagejson);
            //
            var responsecallback;
            //java call finished, now need to call js callback function
            if (message.responseid) {
            	...
            } else {
                // 消息中有callbackid 说明需要将处理完成后,需要回调native端
                //直接发送
                if (message.callbackid) {
                    // 回调消息的 回调id
                    var callbackresponseid = message.callbackid;
                    //
                    responsecallback = function(responsedata) {
                        // 发送js端的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);
                    }
                }
            }
        });
    }

demo.html

bridge.registerhandler("functioninjs", function(data, responsecallback) {
    document.getelementbyid("show").innerhtml = ("data from java: = " + data);
    if (responsecallback) {
        var responsedata = "javascript says right back aka!";
        responsecallback(responsedata);
    }
});
  • 这里调用到js的functioninjs注册方法,并将js的处理结果javascript says right back aka!返回,回调到webviewjavascriptbridge.js _dispatchmessagefromnative注册的responsecallback,从而调用到webviewjavascriptbridge.js 的_dosend方法之中。

一下为webviewjavascriptbridge.js 的_dosend

webviewjavascriptbridge.js

// 发送js端的responsedata
_dosend({
    responseid: callbackresponseid,
    responsedata: responsedata
});
// 3、js将数据发送到native端
// sendmessage add message, 触发native的 shouldoverrideurlloading方法,使native主动向js取数据
//
// 把消息队列数据放到shouldoverrideurlloading 的url中不就可以了吗?
// 为什么还要native主动取一次,然后再放到shouldoverrideurlloading的url中返回?
function _dosend(message, responsecallback) {
    // 发送的数据存在
    if (responsecallback) {
        //
        var callbackid = 'cb_' + (uniqueid++) + '_' + new date().gettime();
        responsecallbacks[callbackid] = responsecallback;
        message.callbackid = callbackid;
    }
    // 添加到消息队列中
    sendmessagequeue.push(message);
    // 让native加载一个新的页面
    messagingiframe.src = custom_protocol_scheme + '://' + queue_has_message;
}
  • 1、将native发送过来的message数据,存储到sendmessagequeue消息队列中
  • 2、_dosend 中 reload iframe " yy://queue_message/ " 触发native的 shouldoverrideurlloading方法

bridgewebviewclient.java

@override
    public boolean shouldoverrideurlloading(webview view, string url) {
        logutils.d(tag, "shouldoverrideurlloading——>url: " + 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);
        }
    }
  • _dosend 中 reload iframe " yy://queue_message/ " 触发native的 shouldoverrideurlloading方法,最终调用到webview.flushmessagequeue();方法中
	/**
     * 1、调用js的 _fetchqueue方法,获取js中处理后的消息队列。
     * js 中_fetchqueue 方法 中将message数据返回到native的 {@link #bridgewebviewclient.shouldoverrideurlloading}中
     * <p>
     * 2、等待{@link #handlerreturndata} 回调 callback方法
     */
    void flushmessagequeue() {
        logutils.d(tag, "flushmessagequeue");
        if (thread.currentthread() == looper.getmainlooper().getthread()) {
            // 调用js的 _fetchqueue方法
            bridgewebview.this.loadurl(bridgeutil.js_fetch_queue_from_java, new callbackfunction() {

                @override
                public void oncallback(string data) {
                    // ... 此处暂时省略
                }
            });
        }
    }
  • flushmessagequeue中加载了一段js脚本,js_fetch_queue_from_java,以下为js脚本的代码。
// 调用js的 _fetchqueue方法。_fetchqueue方法中将message数据返回到native的shouldoverrideurlloading中
final static string js_fetch_queue_from_java = "javascript:webviewjavascriptbridge._fetchqueue();";
  • 这段js脚本代码调用到的是 webviewjavascriptbridge.js中的 _fetchqueue方法。

webviewjavascriptbridge.js

// 将数据返回给native
// 提供给native调用,该函数作用:获取sendmessagequeue返回给native,由于android不能直接获取返回的内容,所以使用url shouldoverrideurlloading 的方式返回内容
    function _fetchqueue() {
        // json数据
        var messagequeuestring = json.stringify(sendmessagequeue);
        // message数据清空
        sendmessagequeue = [];
        // 数据返回到shouldoverrideurlloading
        //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);
    }
  • 这里通过 reload iframe " yy://return/_fetchqueue/ + encodeuricomponent(messagequeuestring)"将数据发送给native的shouldoverrideurlloading方法中。
	/**
     * 1、获取到callbackfunction data执行调用并且从数据集移除
     * <p>
     * 2、回调native{@link #flushmessagequeue()} callback方法
     *
     * @param url
     */
    void handlerreturndata(string url) {
        logutils.d(tag, "handlerreturndata——>url: " + url);
        // 获取js的方法名称
        // _fetchqueue
        string functionname = bridgeutil.getfunctionfromreturnurl(url);
        // 获取_fetchqueue 对应的回调方法
        callbackfunction f = responsecallbacks.get(functionname);
        // 获取body message消息体
        string data = bridgeutil.getdatafromreturnurl(url);

        // 回调 native flushmessagequeue callback方法
        if (f != null) {
            logutils.d(tag, "oncallback data" + data);
            f.oncallback(data);
            responsecallbacks.remove(functionname);
            return;
        }
    }
  • 这里的callbackfunction 回调到了flushmessagequeue方法的oncallback中。
@override
public void oncallback(string data) {
    logutils.d(tag, "flushmessagequeue——>data: " + data);
    // deserializemessage 反序列化消息
    list<message> list = null;
    try {
        list = message.toarraylist(data);
    } catch (exception e) {
        e.printstacktrace();
        return;
    }
    if (list == null || list.size() == 0) {
        logutils.e(tag, "flushmessagequeue——>list.size() == 0");
        return;
    }
    for (int i = 0; i < list.size(); i++) {
        message m = list.get(i);
        string responseid = m.getresponseid();
        /**
         * 完成native向js发送信息后的回调
         */
        // 是否是response  callbackfunction
        if (!textutils.isempty(responseid)) {
            callbackfunction function = responsecallbacks.get(responseid);
            string responsedata = m.getresponsedata();
            function.oncallback(responsedata);
            responsecallbacks.remove(responseid);
        } else {
            // ... 此处暂时省略
        }
    }
}
  • 这里循环了从js端获取到的message队列,并将js端获取的数据,回调到了native中对应的callbackfunction中。

到这里,jsbridge中native调用js代码的通信,则完成了。

一个问题

webviewjavascriptbridge.js的_dosend(message, responsecallback)方法中,把message消息队列 放到shouldoverrideurlloading 的url中直接返回给native不就可以了吗?

为什么还要用_dosend 中 reload iframe " yy://queue_message/ " 触发native的 shouldoverrideurlloading方法,让native主动向js请求一次message队列,然后再放到shouldoverrideurlloading的url中返回给native呢?

个人观点: 觉得,这样将message集中在一起,通过发送一个消息给native,让native主动将所有数据请求回来。避免了js与native的频繁交互。

js调用native代码,向native传递数据

不太想说了,就到这吧

========== the end ==========

wx_gzh.jpg

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网