WebViewJavascriptBridge框架可以同时支持UIWebView及WKWebView,完成native与web的交互。其主要核心思路是native在webView的代理中拦截url,根据url来做不同处理!
框架结构主要结构为:
WebViewJavascriptBridge、WKWebViewJavascriptBridge
这两个类分别为UIWebView与WKWebView与web交互桥梁,native与web的交互工作主要通过这两个类处理
WebViewJavasciptBridge_JS
该类是web的JS环境,客户端需要在网页初始化的时候注入到web端,
只有一个返回值为NSString的方法
NSString* WebViewJavascriptBridge_js(void)
WebViewJavascriptBridgeBase
该类有两个重要的属性:
- NSMutableDictionary *responseCallbacks--存储内容有
- callbackId为key,objc_cb_(_uniqueId)为值,其中_uniqueId会递增
- objc_cb_(_uniqueId)为key,回调方法为值
- handlerName为key,参入的该参数为值
- NSMutableDictionary *messageHandlers—存储客户端注册方法的方法及实现
本文会从以下几点分析该框架:
1.js注入
2.web调用native的实现
3.native调用web的实现
js注入
web端通过执行setupWebViewJavascriptBridge,完成js注入
function setupWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge);
}
if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
window.WVJBCallbacks = [callback];
var WVJBIframe = document.createElement('iframe');
WVJBIframe.style.display = 'none';
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
web执行WVJBIframe.src = 'wvjbscheme://BRIDGE_LOADED'向native发送消息,native通过webView:decidePolicyForNavigationAction:decisionHandler:
拦截消息
wvjbscheme://__BRIDGE_LOADED__
,并执行[_base injectJavascriptFile]
- (void)injectJavascriptFile {
NSString *js = WebViewJavascriptBridge_js();
[self _evaluateJavascript:js];
if (self.startupMessageQueue) {
NSArray* queue = self.startupMessageQueue;
self.startupMessageQueue = nil;
for (id queuedMessage in queue) {
[self _dispatchMessage:queuedMessage];
}
}
通过_evaluateJavascript:js将WebViewJavascriptBridge_js中内容进行注入
web调用native的实现
- native本地注册方法
[self.jsBridge registerHandler:@"JSCallOC"handler:^(iddata,WVJBResponseCallbackresponseCallback) {
responseCallback(@"hahaha");
NSLog(@"data:%@----responseCallback:%@", data, responseCallback);
}];
该注册方法内部实现主要是将注册方法名和方法实现存储到messageHandlers中
- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handlercopy];
}
- web端通过按钮的点击调用
WebViewJavascriptBridge.callHandler('JSCallOC',{'lmf':'cool'},function(response) {
alert(response);
})
上面方法中的WebViewJavascriptBridge为注入的js环境提供的,callHandler方法也是环境中的代码实现的,这个js环境是在加载网页时,注入的
接下来看下callHandler的实现
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
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;
}
这个方法中主要做了:
- 在对象中加了一个callbackId字段,这个字段是为了客户端在调完方法后,网页可以找到对应的回调,同时以回调方法为value,callbackId的值为key存入responseCallbacks中
最终的message为:
{
handlerName : ‘JSCallOC’,
data : {‘lmf’:’cool’},
callbackId : ‘cb_’+(uniqueId++)+’_’+new Date().getTime(),
}
- 以callbackId为key,回调 function(response) { alert(response); }为value,存入responseCallbacks
- 将上面的message,push到sendMessageQueue中,并执行
messageIframe.src = CUSTOM_PROTOCOL_SCHEME+'://'+QUEUE_HAS_MESSAGE
即messageIframe.src = 'https://__wvjb_queue_message__'
- native通过
webView:decidePolicyForNavigationAction:decisionHandler:
拦截消息https://__wvjb_queue_message__
,进而执行WKFlushMessageQueue
- (void)WKFlushMessageQueue {
[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
if (error != nil) {
NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
}
[_base flushMessageQueue:result];
}];
}
webViewJavascriptFetchQueryCommand的实现如下
- (NSString*)webViewJavascriptFetchQueyCommand {
return@"WebViewJavascriptBridge._fetchQueue();";
}
即WKFlushMessageQueue方法会执行web端的_fetchQueue
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
result数据内容为:
[
{“handlerName”:”JSCallOC”,
”data”:{“lmf”:”cool”},
”callbackId”:”cb_1_15709533997161"}
]
再通过执行[_base flushMessageQueue:result];
- (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);
}
}
}
存在callbackId,生成responseCallback
//responseCallback
^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
messageHandlers通过message[@"handlerName"]获取native注册的方法handler,并执行
handler(message[@"data"], responseCallback);
- 执行注册的方法,并调用responseCallback传入参数hahaha,得到msg-@{ @"responseId":@"cb_1_1587911907256", @"responseData":@"hahaha" }
再执行_queueMessage
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
//执行js回调
- (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];
});
}
}
该方法会执行web端的WebViewJavascriptBridge._handleMessageFromObjC(message.string);
- 执行js端的_handleMessageFromObjC
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
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);
}
}
}
}
判断存在responseId,通过responseId从responseCallbacks获取回调,并传入responseData执行回调,再从responseCallbacks中删除responseId的数据
至此完成JS调用native的一次交互
native调用web的实现
native调用web与web调用native的思路大致相似。
- 在native调用前,web已经注册了方法,下面为web注册方法的代码:
//web
bridge.registerHandler('OCCallJS',function(data, responseCallback) {
alert('JS方法被调用:'+data);
responseCallback('js执行过了');
})
//注入的js环境
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
额额额,是否似曾相识,在哪里曾见过它!!!
- native调用web端方法
[self.jsBridge callHandler:@"OCCallJS"data:@"OC调用JS"responseCallback:^(idresponseData) {
NSLog(@"currentThread:%@", [NSThreadcurrentThread]);
NSLog(@"responseData:%@", responseData);
}];
//WebViewJavascriptBridge
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}
//WebViewJavascriptBridgeBase
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionarydictionary];
if(data) {
message[@"data"] = data;
}
if(responseCallback) {
NSString* callbackId = [NSStringstringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallbackcopy];
message[@"callbackId"] = callbackId;
}
if(handlerName) {
message[@"handlerName"] = handlerName;
}
[self _queueMessage:message];
}
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
NSLog(@"!!!!!!!!startupMessageQueue addObject 执行了!!!!!!!!");
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
//执行js回调
- (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];
});
}
}
首先看下sendData里做了什么
- 封装一个新的对象,增加key为data、handlerName、callbackId(有回调就生成该值),并以callbackId的值为key,回调方法为value存入responseCallbacks中
新对象值为:
{
callbackId = "objc_cb_1",
data = "OC\U8c03\U7528JS",
handlerName = "OCCallJS"
}
接着将新对象传入_queueMessage中,最终又回到了web端的_handlerMessageFromObjc方法中
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
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);
}
}
}
}
这个方法是不是在哪里见过,原理与native端的flushMessageQueue是一样的!
不存在reponseId,存在callbackId,生成闭包responseCallback,闭包中内容为封装的新对象{ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData }
传入_doSend,接着通过handlerName在messageHandlers中找到js端注册方法,并执行,方法执行中会执行responseCallback闭包,触发_doSend,将传入对象添加到sendMessageQueue中,并通过src=https://wvjb_queue_message向native发送消息,native截取到该消息,又会执行WKFlushMessageQueue流程,通过_fetchQueue获取sendMessageQueue中内容,执行flushMessageQueue方法,存在responseId,通过responseId在_responseCallbacks中取到回调,最终responseCallback(message[@"responseData"])执行回调,当然执行完也要删除_responseCallbacks中responseId为key的数据,最终完成交互
至此我们完成了WebViewJavascriptBridge框架的分析,大致整理下主要逻辑,以JS调用OC为例:
- 客户端注册方法
- js端将调用信息存入sendMessageQeuue中,
并调用messageIframe.src = CUSTOM_PROTOCOL_SCHEME+’://‘+QUEUE_HAS_MESSAGE(https://wvjb_queue_message/) - 客户端通过url拦截获取到调用信息
- 客户端生成一个新的回调,回调中存入callbackId,key为responseId,然后通过handlerName获取注册的方法,并执行,执行完成后在执行新的回调,回调web端方法
- web端通过传入的responseId进行js端回调
生活如此美好,今天就点到为止。。。