WebViewJavascriptBridge工程结构和源码解析

WebViewJavascriptBridge

javaScript调用Native其实是用重定向拼成url参数传递给Native

Native根据重定向的地址调用指定名称的回调函数


javascript生成url然后重定向使webview捕捉到重定向事件,解析重定向的过程。

shouldOverrideUrlLoading


Native调用javascript也是一样,实现用js代码注册好,

然后将指定参数名和回调函数转成可以供webview调用字符串格式。

实际就是调用webview.loadUrl


运行时结构如图





这里我简明扼要的的打个比喻


你拿银行卡(数据)去取钱(调用网页的window.WebViewJavascriptBridge.send方法)并给你一个小票(callBackID),大堂经理(android端) 给了

你一个pos机( 网页加载完成后注入WebViewJavaScriptBridge.js),pos机(WebViewJavaScriptBridge.js)和银行(android 端事先注册的一些监听重定向连接的方法)

之间交互之后通过小票(callBackid)告诉你取钱成功了。

为什么安全的原因是


1:pos机和银行交互 

对你是不透明,无法拦截无法调用。

2:pos机 无法伪造(网页加载完成后注入WebViewJavaScriptBridge.js) 是androd给你的

你不知道WebViewJavaScriptBridge.js里面的这2个校验

var CUSTOM_PROTOCOL_SCHEME = 'yy';
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/'









以下是源码可以看的比较头晕。


从网站上下载工程:https://github.com/lzyzsd/JsBridge

我们来一步一步解析WebViewJavascriptBridge的框架结构,首先

BridgeWebView继承了webview
BridgeWebView设置WebViewClient为BridgeWebViewClient
BridgeWebViewClient有一个重要的时间在网页加载完成时注入js-》WebViewJavaScriptBridge.js
BridgeUtil.webViewLoadLoaclJs(View,BridgeWebView.toLoadJs)

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

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


让我们看看assets的
WebViewJavascriptBridge.js的源码怎么写的
WebViewJavascriptBridge基本属性和初始化的几个方法

var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
        init: init,
        send: send,
        registerHandler: registerHandler,
        callHandler: callHandler,
        _fetchQueue: _fetchQueue,
        _handleMessageFromNative: _handleMessageFromNative
    };

    var doc = document;
    _createQueueReadyIframe(doc);
    var readyEvent = doc.createEvent('Events');
    readyEvent.initEvent('WebViewJavascriptBridgeReady');
    readyEvent.bridge = WebViewJavascriptBridge;
    doc.dispatchEvent(readyEvent);
你点击了发消息给Nativie这个按钮调用如下方法
function testClick() {
    var str1 = document.getElementById("text1").value;
    var str2 = document.getElementById("text2").value;

    //send message to native
    var data = {id: 1, content: "这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"};
    window.WebViewJavascriptBridge.send(
        data
        , function(responseData) {
            document.getElementById("show").innerHTML = "repsonseData from java, data = " + responseData
        }
    );

}
这里window.WebViewJavascriptBridge.send指的是send function
 function send(data, responseCallback) {
        _doSend({
            data: data
        }, responseCallback);
    }


此时调用
WebViewJavascriptBridge.js中的WebViewJavascriptBridge的doSend方法
 //sendMessage add message, 触发native处理 sendMessage
    function _doSend(message, responseCallback) {
        if (responseCallback) {//如果回调函数不为空,为回调函数创建一个CallbackId
            var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
            responseCallbacks[callbackId] = responseCallback;
            message.callbackId = callbackId;
        }

        sendMessageQueue.push(message);
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }
此时会发起一个重定向yy://_QUEUE_MESSAGE_/消息
然后由webview 捕捉网页重定向事件调用已注册的参数和对应的handler

url.startsWith(BridgeUtil.YY_RETURN_DATA 对应  yy://_QUEUE_MESSAGE_/
url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA  对应 <span style="font-family: Menlo; background-color: rgb(255, 255, 255);">yy://return/_fetchQueue/[{"data":{"id":1,"content":"这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"},"callbackId":"cb_1_1464075966655"}]
 @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {

        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Toast.makeText(view.getContext(), url, Toast.LENGTH_LONG).show();
        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);
        }
    }
webView.handlerReturnData(url);的实际作用是一次性调用<span style="font-family: Menlo; font-size: 12pt; background-color: rgb(255, 255, 255);">_fetchQueue然后移除</span>
<pre name="code" class="javascript">void handlerReturnData(String url) {
		String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
		System.out.println("functionName:"+functionName);
		CallBackFunction f = responseCallbacks.get(functionName);
		String data = BridgeUtil.getDataFromReturnUrl(url);
		if (f != null) {
			f.onCallBack(data);
			responseCallbacks.remove(functionName);
			return;
		}
	}



调用javacript的_fetchQueue
function _fetchQueue() {
            alert("_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);
    }

然后会重定向这个新的url yy://return/_fetchQueue/[{"data":{"id":1,"content":"这是一个图片 <img src=\"a.png\"/> test\r\nhahaha"},"callbackId":"cb_1_1464075966655"}]





拼接回调函数和数据转成字符串由webview调用,格式和代码如下
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);
							}
						}
					}
				}
			});
		}
	}


关键核心代码:
 
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);
        }
    }


javascript:WebViewJavascriptBridge._handleMessageFromNative('{\"responseData\":\"DefaultHandler response data\",\"responseId\":\"cb_1_1464076358507\"}');
在javascript中完成回调(取出根据回调函数的callbackId,获得引用并调用
//提供给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) {
                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);
                    }
                }
            }
        });
    }

 
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值