前言
小编之前写的 iOS WebView和JS的交互 这篇文章介绍了iOS
和js
交互的几种方式。其中现在最常用的是JSBridge
的方式,我们在上一篇也介绍了具体的使用,本文详细介绍JSBridge(WebViewJavascriptBridge)
的实现原理。android
版本的JSBridge
实现原理由于能力有限,不作介绍。不过可以脑补一下,其实现思路应该和iOS
版本的JSBridge
是一样的。下边我们正式介绍了
交互详细流程图梳理
WebViewJavascriptBridge源码解读
目录和相关文件功能介绍
首先先看一下WebViewJavascriptBridge
插件的目录
-
WebViewJavascriptBridge
在使用的时候,初始化对象做了对UIWebView
和WKWebView
的兼容。对外暴露外界使用的方法,以及拦截UIWebView的代理方法 -
WebViewJavascriptBridge_JS
注入js代码,主要是js和native之间相互调用的js代码 -
WebViewJavascriptBridgeBase
主要是管理js和native交互的数据,以及native和js之间相互调用的工具方法 -
WKWebViewJavascriptBridge
为WKWebView的定制的对象,主要实现拦截WKWebView的代理方法,以及暴露外界使用的方法
WebViewJavascriptBridge的初始化流程
要想了解源码,必定要先会使用,那么我们先从使用的最开始,初始化一步步了解
首先初始化native端的WebViewJavascriptBridge
初始化,会执行WebViewJavascriptBridge
对象的bridge
方法
WebViewJavascriptBridge
的初始化
+ (instancetype)bridge:(id)webView {
#if defined supportsWKWebView
if ([webView isKindOfClass:[WKWebView class]]) {
return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
}
#endif
if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
WebViewJavascriptBridge* bridge = [[self alloc] init];
[bridge _platformSpecificSetup:webView];
return bridge;
}
[NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
return nil;
}
- (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView {
_webView = webView;
_webView.policyDelegate = self;
_base = [[WebViewJavascriptBridgeBase alloc] init];
_base.delegate = self;
}
这里有两个变量:
-
supportsWKWebView
判断系统是否支持WKWebView
-
WVJB_WEBVIEW_TYPE
在ios端是UIWebView
1、如果支持WKWebView
,且传入的webView
是WKWebView
类型,使用WKWebViewJavascriptBridge
对象初始化,代码如下所示
+ (instancetype)bridgeForWebView:(WKWebView*)webView {
WKWebViewJavascriptBridge* bridge = [[self alloc] init];
[bridge _setupInstance:webView];
[bridge reset];
return bridge;
}
- (void) _setupInstance:(WKWebView*)webView {
_webView = webView;
_webView.navigationDelegate = self;
_base = [[WebViewJavascriptBridgeBase alloc] init];
_base.delegate = self;
}
其实WKWebViewJavascriptBridge
和WebViewJavascriptBridge
对象做的事情基本一样,只是两者在内部分别处理了UIWebView
和WKWebView
本身api的调用和实现的代理方法,暴露给外界的方法一模一样。所以这里我们就只介绍WebViewJavascriptBridge
这个对象的实现,WKWebViewJavascriptBridge
这个对象的实现可以自己看一下。这里就省略了。
2、 不满足1的条件,就使用WebViewJavascriptBridge
对象初始化
3、设置UIWebView/WKWebView
的代理,在各自对象里边实现UIWebView/WKWebView
的代理方法,最后初始化WebViewJavascriptBridgeBase
对象。
native
端WebViewJavascriptBridgeBase
的初始化
这里初始化了messageHandlers
,startupMessageQueue
,responseCallbacks
,_uniqueId
四个属性,代码如下
- (id)init {
if (self = [super init]) {
self.messageHandlers = [NSMutableDictionary dictionary];
self.startupMessageQueue = [NSMutableArray array];
self.responseCallbacks = [NSMutableDictionary dictionary];
_uniqueId = 0;
}
return self;
}
messageHandlers
:存放native注册的事件交互的字典对象
startupMessageQueue
:存放在js注入之前,native端调用js的事件交互的队列。因为js端需要手动触发通知native端注入js代码,在这时候注入完成需要立即执行队列里的任务
responseCallbacks
:存在native端调用js传入的回调函数字典对象
_uniqueId
:这是一个记录自增数字,主要用来生成不同的`callBackId
这时候,native端的初始化已经完成。但是有的小伙伴存在一个疑问,js全局Bridege初始化呢?js端和native端是怎么相互调用的呢?这一切都是疑问,下边我们一步步解开她的面纱
js端初始化和js代码的注入
js端触发初始化
官方文档提示js
端需要执行下边代码。那我们就从这里开始分析js代码是如何注入的。js
端执行代码如下:
function iosSetupWebViewJavaScriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(window.WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
const WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function timeCB() {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
iosSetupWebViewJavaScriptBridge(function(JSBridge){
console.log(JSBridge); // js端获取到的WebViewJavascriptBridge对象
})
我们可以看到这里js
先创建了iframe
,挂在到document
文档。然后设置了src=wvjbscheme://__BRIDGE_LOADED__
,最后把iframe移除。
native的webView拦截初始化url
在上边的创建iframe
,挂载iframe
,设置iframe
的src
属性的过程结束后,native端webView
会发生重新请求,会触发native
端WebViewJavascriptBridge
对象实现的UIWebView
代理方法,在这一步对请求的url做拦截,判断是初始化动作还是执行事件交互的动作。如下代码
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener {
if (webView != _webView) { return; }
NSURL *url = [request URL];
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) { // 初始化会执行
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) { // js端调用native端会执行
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
[listener ignore];
} else if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:request:frame:decisionListener:)]) {
[_webViewDelegate webView:webView decidePolicyForNavigationAction:actionInformation request:request frame:frame decisionListener:listener];
} else {
[listener use];
}
}
因为这里webview
获取到的url
是iframe
的src='wvjbscheme://__BRIDGE_LOADED__'
,这里native
端执行[_base isBridgeLoadedURL:url]
返回true
,然后会执行WebViewJavascriptBridgeBase
对象injectJavascriptFile
的实例方法,这个方法其实就是注入js代码的方法,代码如下
- (void)injectJavascriptFile {
NSString *js = WebViewJavascriptBridge_js(); // 获取将要注入的js代码
[self _evaluateJavascript:js]; // webview注入js代码 UIWebView和WKWebView注入的方法不一样
if (self.startupMessageQueue) {
NSArray* queue = self.startupMessageQueue;
self.startupMessageQueue = nil;
for (id queuedMessage in queue) {
[self _dispatchMessage:queuedMessage];
}
}
}
这里主要是获取WebViewJavascriptBridge_js
文件中的js
代码,然后webview
注入这一段js
代码。那么我们来看一下注入的js
代码究竟是什么东西,代码里边我做了一些注释,可以看一下,方法具体的执行内容这里先不讲解,后边会详细介绍讲解
native端WebViewJavascriptBridge_js对象(这个对象维护的是注入的js代码)
;(function() {
if (window.WebViewJavascriptBridge) {
return;
}
if (!window.onerror) {
window.onerror = function(msg, url, line) {
console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
}
}
// 注入的全局对象和方法
window.WebViewJavascriptBridge = {
registerHandler: registerHandler,
callHandler: callHandler,
disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
};
// iframe
var messagingIframe;
// 存放js调用native的事件交互的队列,因为js端调用native的事件交互是异步的过程,
// js可能会同时调用多个native端注册的事件交互,所以会把js端调用的事件交互放到这里,native端一起执行这些任务
var sendMessageQueue = [];
// 存放js注册的事件交互对象,数据结构:{name:function(){}}
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'https';
var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
// 存放js调用native的回调responseCallback数据,数据结构:{callBackId:responseCallback}
var responseCallbacks = {};
var uniqueId = 1; // 用来生成自增的callBackId
var dispatchMessagesWithTimeoutSafety = true;
// js注册native端调用的事件交互的全局方法
function registerHandler(handlerName, handler) {
....省略
}
// js 调用 native 端事件交互的全局方法
function callHandler(handlerName, data, responseCallback) {
....省略
}
function disableJavscriptAlertBoxSafetyTimeout() {
....省略
}
function _doSend(message, responseCallback) {
....省略
}
// 提供给native端调用,目的是获取js端调用native的所有待执行事件
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
function _dispatchMessageFromObjC(messageJSON) {
....省略
}
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
// 创建iframe全局对象,设置src,隐藏iframe
messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);
registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
// js代码注入后,执行到这里,执行js传过来的callBack,把WebViewJavascriptBridge返回出去,
//这样外界就可以获取到全局的WebViewJavascriptBridge对象
setTimeout(_callWVJBCallbacks, 0);
function _callWVJBCallbacks() {
var callbacks = window.WVJBCallbacks;
delete window.WVJBCallbacks;
for (var i=0; i
熟悉js
的同学应该一眼就能看懂这里的逻辑
1、初始化js
全局WebViewJavascriptBridge
对象,给WebViewJavascriptBridge
对象暴露一些方法和window下的全局对象和私有方法,
2、最后创建iframe对象,插入到document
文档中
3、获取WVJBCallbacks
全局对象(其实是一个回调的数组),最后把WebViewJavascriptBridge
全局对象通过回调的方式传递出去。也就是在执行iosSetupWebViewJavaScriptBridge
方法时候传入的回调来接收
主要的全局对象
messageHandlers
存放js
端注册的事件交互messageHandlers[name] = function(data){};
sendMessageQueue
存放未执行的js
端调用native
的事件交互
responseCallbacks
存放js
调用native
传入的responseCallback回调,responseCallbacks[callBackId] = responseCallback
暴露的方法
registerHandler(handlerName, handler)
: 注册事件交互
callHandler(handlerName, data, responseCallback)
:调用native
的事件交互,
_fetchQueue()
: 获取js
端调用native
端即将执行的事件交互队列
_handleMessageFromObjC(messageJSON)
: native
端调用js
的事件交互执行的方法
到这里,native端和js端都已经完成了初始化的工作。已经弄清楚js
端代码是如何注入以及注入的js
代码做了什么事情。
下边我们开始讲解js
和native
互相调用的流程
js调用native事件交互
js调用native事件交互,前提需要native端注册这个事件交互
native端注册事件交互流程
我们找到native
端WebViewJavascriptBridge
对象的registerHandler
的实例方法
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
这个方法最终是为native
端的WebViewJavascriptBridgeBase
对象的messageHandlers
(这个值下边后边会用到比较重要)字典添加键是handlerName
,值是handler
的blcok
(这里可以理解是回调方法)
js端调用流程
首先我们找到WebViewJavascriptBridge_js
文件js代码的callHandler
方法
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
这个方法传入name,data,responseCallback
三个参数,对data
和responseCallback
进行了调和,然后调用私有方法_doSend
,这里传入的第一个参数message
都有哪些成员属性,后边会用到
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;
}
这个方法执行(js端):
1、判断是否有responseCallback
回调,如果有,生成一个js
端唯一的callBackId
,放入到js端responseCallbacks
对象中,在message
对象中添加callBackId
的属性和callBackId
值。这里的message对象是{handlerName,data,callBackId},callBackId可能不存在。
2、然后放入js端sendMessageQueue
数组中,这个数组存放的是js
端调用native
待执行的事件交互
3、设置iframe
的src=https://__wvjb_queue_message__
,这时候js端的任务已经结束。下边把执行交给native
端
4、native
端webView
会触发请求,这里又会执行到webView
拦截url
的方法,判断到请求的url
是https://__wvjb_queue_message__
这个值,会执行下边`native端代码
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
[_base webViewJavascriptFetchQueyCommand]
返回的字符串是@"WebViewJavascriptBridge._fetchQueue();"
,那么其实就是webview
执行js
端的WebViewJavascriptBridge
对象的_fetchQueue
方法
_fetchQueue
方法如下
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
这个方法作用是把待执行的js
端调用native
事件交互的数组转化成字符串,接下来回到native端拿到这个字符串后执行WebViewJavascriptBridgeBase
对象的flushMessageQueue:messageQueueString
实例方法
flushMessageQueue方法
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
这个方法执行流程:
1、把需要执行的任务字符串转成OC
字典
2、循环遍历每一个任务
3、执行每一个任务(每一个任务这里的数据结构{handlerName,data,callBackId}
,callBackId
可能不存在,上边已经强调过)
4、取出每一个任务的callBackId
,构造一个responseCallBack
回调。如果callBackId
是空,那么这回调什么也不执行;callBackId
有值,构造一个@{ @"responseId":callbackId, @"responseData":responseData }
字典,然后执行这个任务_queueMessage
这个方法。这一步只是构造一个回调。(这里不会走到有reponseId
的逻辑)
5、从messageHandlers
字典取出js
端传入的handleName
值对应的回调。如果存在这个回调(这个回调是native
端注册),执行这个方法传入js
端传入的data
值,和native
端构造的responseCallBack
,这样就实现了js
端调用native
的方法。
那么这就算是一个完整的js调用native的流程了吗?显然不是,还差最后一步,就是怎么执行js在调用事件传入的第三个回调参数呢?(也就是callHandler(name,data,responseCallBack)
方法第三个参数responseCallBack
回调方法)
接着上边第五步,handler(message[@"data"], responseCallback);
在执行这一步的时候如果传入的responseCallback
回调得到执行,最终会执行[self _queueMessage:msg];
(这里传入的参数数据结构@{ @"responseId":callbackId, @"responseData":responseData }
)
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
这个时候startupMessageQueue
是空,就会执行_dispatchMessage
方法
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[self _log:@"SEND" json:messageJSON];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
这里把传入的字典转成字符串,然后在主线程,webview
执行js
端WebViewJavascriptBridge
对象的_handleMessageFromObjC(message)
方法,这时候执行交给js端
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
最终执行_dispatchMessageFromObjC
私有方法
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
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({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
注意这里的messageJSON
数据结构是{responseId,responseData}
,这一步的reponseId
就是在js
在执行callHandler(name,data,responseCallBack)
这一步,js端构造的唯一的callBackId
1、把messageJSON
字符串转化成对象
2、取出reponseId
,这里一定存在
3、从responseCallbacks
中取出responseId
值对应的回调函数,上边已经提到responseCallbacks
这个对象就是存放callBackId
对应的回调函数。
4、执行第三步取出回调,传入native
端传入reponseData
5、执行结束,从responseCallbacks
移除这个callBackId
到这里一个完整的js执行native事件的链路执行结束。
native调用js的事件交互
native
调用js
,js
需要提前注册事件
js端注册事件交互流程
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
js
端执行上边代码,在js
端messageHandlers
对象中存入键时handlerName
的值,值为handler
的方法
native端调用流程
native端提供的有三个方法
- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;
这三个方法最后都会WebViewJavascriptBridgeBase
对象的- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName
方法
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];
if (data) {
message[@"data"] = data;
}
if (responseCallback) {
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}
if (handlerName) {
message[@"handlerName"] = handlerName;
}
[self _queueMessage:message];
}
这个方法其实就是js
端_doSend
方法的翻译版本
1、创建一个字典
2、判断responseCallback
是否有值,如果有生成一个native
端唯一的callBackId
,放入到native
端responseCallbacks
字典中,这里的message
字典数据结构是{handlerName,data,callBackId}
,callBackId
可能不存在。
3、调用_queueMessage
方法,执行message
这个任务。上边也提到了,调用这个方法,会执行到_dispatchMessage
方法,_dispatchMessage
方法如下
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self _serializeMessage:message pretty:NO];
[self _log:@"SEND" json:messageJSON];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
这个方法的执行流程,最终会执行到js
端WebViewJavascriptBridge
对象_handleMessageFromObjC
方法,然后调用_dispatchMessageFromObjC
方法,但是这里执行的流程和上边有区别
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
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({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
这里首先把messageJSON
转成js
对象
这里的message
的数据结构时{handlerName,data,callBackId}
,和上边讲到调用这个方法的数据结构不一样,不要混淆。
1、message.responseId
这里一定是空,不会执行这个判断分支逻辑
2、执行else
分支逻辑,如果message.callBackId
存在,那么会构造一个js
的回调函数responseCallback
;在执行responseCallback
方法时,会执行_doSend
这个方法,传入构造的{ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData }
对象,来执行这个任务。这一步只是构造一个回调
3、从js
端messageHandlers
对象取出message.handlerName
值对应的方法,如果存在这个方法(这个方法是js
端注册),执行这个方法,传入native
传过来的data
值,和js
端构造的responseCallBack
,这样就实现了native
端调用`js的方法。
那么这就算是一个完整的native
调用js
的流程了吗?显然不是,还差最后一步,就是怎么执行native
在调用js
方法时传入的回调函数呢?(也就是- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName
方法的第二个参数responseCallBack
回调函数)
接着上边第三步,handler(message.data, responseCallback)
在执行这一步的时候如果传入的responseCallback
回调得到执行,最终会执行_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
那么其实我们回到了js
调用native
的流程,这里只是执行_doSend
传入的参数不一样,(_doSend
方法这里不粘贴了...)
这个执行上下文是在js端:
1、把传入的参数对象{ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData }
放入js
端sendMessageQueue
数组
2、设置iframe
的src=https://__wvjb_queue_message__
,下边就会把执行交给native
端了
3、native
端webView
会触发请求,这里又会执行到webView
拦截url
的方法,判断到请求的url
是https://__wvjb_queue_message__
这个值,会经过native
方法之间调用(上边已经指出)最后执行下边代码
- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
handler(message[@"data"], responseCallback);
}
}
}
这里会取出每一个任务message
的responseId
,不存在responseId
流程上边已经讲过。如果存在responseId
值会从native
端responseCallbacks
取出responseId
值对应的回调函数,上边已经提到responseCallbacks
这个对象就是存放callBackId
对应的回调函数。
1、如果存在这个回调,执行这个回调,传入js
端传递过来的reponseData
2、执行结束,native
端从responseCallbacks
移除这个callBackId
,也就是把这个回调移除
主流程这里已经完结,可以边看源码边看上边的详细交互流程图,这样更清晰易懂!有些细致末叶这里不再赘述,有需要的小伙伴可以再仔细的看源码
谢谢