JsBride源码分析

近期做项目,想要做一套native和js交互的规范,在网上搜索了一番之后发现,JsBride相对比较火。

本文就JsBride的实现流程做一个分析。

1. 几个基本类

1、BridgeWebView:Android端的一些必要的初始化在这里,入口在此
2、BridgeWebViewClient:js的一些初始化,以及事件的拦截在此
3、BridgeUtil:初始化以及交互数据解析的工具类
4、Message:交互的数据bean
5、BridgeHandler:js数据返回后的回调处理
6、WebViewJavascriptBridge:向js发送消息
7、WebViewJavascriptBridge.js:这个文件在assets下,初始化了js中的消息队列等
8、CallBackFunction:具体的回调
复制代码

2. 主要流程图

  • 先看***android调用js***方法的时序图(图片源自网络):

  • 再看***js调用android***方法的时序图(图片源自网络):

3. 源码分析

  • 先从入口BridgeWebView开始看起,BridgeWebView的主要方法以及成员变量入下图:

  • 主要的成员变量
public static final String toLoadJs = "WebViewJavascriptBridge.js";//用于js的初始化,指定assets下的文件
   Map<String, CallBackFunction> responseCallbacks = new HashMap<String, CallBackFunction>();//根据方法名或者callbackStr保存的js调用native后的回调列表
   Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>();//根据注册的方法名保存的js调用处理的handler
   BridgeHandler defaultHandler = new DefaultHandler();//默认的js调用处理

   private List<Message> startupMessage = new ArrayList<Message>();//在js loadFinish前发送的消息
复制代码
  • 初始化,主要就是最后一句,初始化了一个WebViewClient(),这个就是最开头提到的BridgeWebViewClient,具体里面实现,后面再做分析。
private void init() {
   this.setVerticalScrollBarEnabled(false);
   this.setHorizontalScrollBarEnabled(false);
   this.getSettings().setJavaScriptEnabled(true);
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
   	WebView.setWebContentsDebuggingEnabled(true);
   }
   this.setWebViewClient(generateBridgeWebViewClient());
}
复制代码
  • 注册native方法,注册方法很简单:
public void registerHandler(String handlerName, BridgeHandler handler) {
   	if (handler != null) {
   		messageHandlers.put(handlerName, handler);
   	}
}
复制代码

以方法名为key,回调处理为value本地维护一个map

  • 调用js注册的方法:
public void callHandler(String handlerName, String data, CallBackFunction callBack) {
       doSend(handlerName, data, callBack);
   }
复制代码

最终内部实际调用是loadurl的形式

实际js调用native流程分析
function testClick1() {
	var str1 = document.getElementById("text1").value;
   var str2 = document.getElementById("text2").value;
   var data = "name=" + str1 + ",pass=" + str2;
   //call native method
   window.WebViewJavascriptBridge.callHandler(
   		'submitFromWeb'
       , {'param': data }
       , function(responseData) {
       			document.getElementById("show").innerHTML = "send get responseData from java, data = " + responseData
            }
        );
    }
复制代码

上面一段代码很简单,就是定义了一个方法,通过js来调用Native的submitFromWeb(),并传递了data参数,以及回调方法。 通过源码可以看到,实际调用为

//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;
    }
复制代码

这里自定义了协议头,同时保存了callback 以及相对应的id。实际发送的链接形式为:“yy://QUEUE_MESSAGE/“开头的形式

我们接着来看native是如何处理调用的:

在BridgeWebViewClient中

@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);
        }
    }
复制代码

这里可以知道,js过来的url为“yy://QUEUE_MESSAGE/“ 走webView.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);
							}
						}
					}
				}
			});
		}
	}
复制代码

这里开始真正进入数据传递以及回调的流程了,有点绕。

先看loadurl(),这里BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA,可以看到是调用了

// 提供给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
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
    }
复制代码

用来把真正要传递的给native的数据传递过来。我们来看调用了_fetchQueue()之后发生了什么。

js重新组装了一个url,链接形式为“yy://return/_fetchQueue + encodeURIComponent(messageQueueString)”

可以在BridgeWebViewClient中的shouldOverrideUrlLoading()中,走的是 webView.handlerReturnData(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;
		}
	}
复制代码

可以看到,这里把我们之前获取js传递给native数据时缓存的responseCallback取了出来。在responseCallback就是真正的数据处理了。 里面的主要处理就是,根据js过来的message数据,判断js是否需要在处理完数据后回调给他(根据responseId来判断),若需要则在CallBackFunction中定义Message,并在回调时加入message队列中,再次通过loadurl的形式传递给js并处理,处理逻辑如下

//提供给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);
                    }
                }
            }
        });
    }
复制代码
native调用js
  1. native调用js流程和上面基本一致,只是native在shouldOverrideUrlLoading()根据responseId能查询到callback并处理数据

转载于:https://juejin.im/post/5b961a10e51d450ea131fd14

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值