JsBridge分析

Posted by Haiden on April 13, 2019

JsBridge分析

核心是自定义url协议,通过不同的交互流程自定义不同的Schema。

一、核心类

BridgeWebViewClient:存放Java注册的messageHandler,发送Java消息,刷新处理消息。 BridgeWebView:重写shouldOverrideUrlLoading,拦截url。 WebViewJavascriptBridge:存放js注册messageHandler,发行js消息。

二、JSBridge通信交互的流程

2.1 java调用js方法的流程

1) 在html中注册了一个functionInJs的hangdler,hangdler里面的responseCallback处理回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
connectWebViewJavascriptBridge(function(bridge) {
    bridge.init(function(message, responseCallback) {
        console.log('JS got a message', message);
        var data = {
            'Javascript Responds': '测试中文!'
        };

        if (responseCallback) {
            console.log('JS responding with', data);
            responseCallback(data);
        }
    });

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

2)js注册的hangdler会在 WebViewJavascriptBridge.js 的messageHandlers中存放

1
2
3
4
// 注册线程 往数组里面添加值
function registerHandler(handlerName, handler) {
    messageHandlers[handlerName] = handler;
}

3) WebViewJavascriptBridge.js会通过BridgeWebView的loadUrl()加载

1
2
3
4
5
6
7
8
void dispatchMessage(Message m) {
			...
       String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);
       // 必须要找主线程才会将数据传递出去 --- 划重点
       if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
           this.loadUrl(javascriptCommand);
       }
   }

4)如果有响应数据,在WebViewJavascriptBridge.js中把message通过url return/协议回调到shouldOverrideUrlLoading中。

1
2
3
4
5
6
7
8
9
// 提供给native调用,该函数作用:获取sendMessageQueue返回给native,由于android不能直接获取返回的内容,所以使用url shouldOverrideUrlLoading 的方式返回内容
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
    if (messageQueueString !== '[]') {
        bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
    }
}

5)在BridgeWebViewClient中拦截shouldOverrideUrlLoading,判断是return/协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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 this.onCustomShouldOverrideUrlLoading(url)?true:super.shouldOverrideUrlLoading(view, url);
    }
}

6) 并在BridgeWebView中执行数据回调处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
    * 获取到CallBackFunction data执行调用并且从数据集移除
    * @param url
    */
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.2 js调用Java方法的流程

1)在java中注册一个registerHandler。

		webView.registerHandler("submitFromWeb", new BridgeHandler() {

			@Override
			public void handler(String data, CallBackFunction function) {
				Log.i(TAG, "handler = submitFromWeb, data from web = " + data);
                function.onCallBack("submitFromWeb exe, response data 中文 from Java");
			}
		});

2)BridgeWebView中会存放注册的Handler。

1
2
3
4
5
6
public void registerHandler(String handlerName, BridgeHandler handler) {
   if (handler != null) {
           // 添加至 Map<String, BridgeHandler>
      messageHandlers.put(handlerName, handler);
   }
}

3)在html调用一个Handler,主要的处理在WebViewJavascriptBridge.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
function testClick1() {
    var str1 = document.getElementById("text1").value;
    var str2 = document.getElementById("text2").value;

    //call native method
    window.WebViewJavascriptBridge.callHandler(
        'submitFromWeb'
        , {'param': '中文测试'}
        , function(responseData) {
            document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
        }
    );
}

4) 在WebViewJavascriptBridge.js中callHandler的方法在调用_doSend(),在doSend中设置一条message并带有callbackId,插到消息队列中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 调用线程
function callHandler(handlerName, data, responseCallback) {
    _doSend({
        handlerName: handlerName,
        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;
    }

5)在BridgeWebViewClient拦截url,并调用flushMessageQueue()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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 this.onCustomShouldOverrideUrlLoading(url)?true:super.shouldOverrideUrlLoading(view, url);
    }
}

6)在flushMessageQueue方法中,根据message中的handlerName在messageHandlers集合中查找在Java中注册的handler方法。查找到后调用handler(m.getData(), responseFunction)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CallBackFunction responseFunction = null;
// if had callbackId 如果有回调Id
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执行
BridgeHandler handler;
if (!TextUtils.isEmpty(m.getHandlerName())) {
   handler = messageHandlers.get(m.getHandlerName());
} else {
   handler = defaultHandler;
}
if (handler != null){
   handler.handler(m.getData(), responseFunction);
}