Architecture
WKWebViewJavascriptBridgeUML(class)
WKWebViewJavascriptBridgeUML(Timing).png
What
WebViewJavascriptBridge是iOS上简化H5 Hybrid方案中Native端与JS端交互通信的框架,适用于UIWebView与WKWebView。
1.OC Call JS
-
Native端
/**
OC调用JS
@param callHandler 方法名
@param data 参数
@param responseCallback 调用方法后的返回值
*/
[_bridge callHandler:@"OCCallJS" data:@{} responseCallback:^(id responseData) {
}];
-
JS端
setupWebViewJavascriptBridge(function(bridge) {
bridge.registerHandler('oc_js', function(data, responseCallback) {
var responseData = { 'Javascript Says':'js callBack' }
responseCallback(responseData)
})
通过上面在js端进行方法的注册,oc端就可以通过进行直接的调用以及得到回调的结果。
2.JS Call OC
-
OC端
/**
JS call OC
@param animated 方法名
@param animated js传递的参数
@param animated OC回调
*/
[_bridge registerHandler:@"js_oc" handler:^(id data, WVJBResponseCallback responseCallback) {
responseCallback(@{@"OC says":@"OC Response"});
}];
-
JS端
setupWebViewJavascriptBridge(function(bridge) {
var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
callbackButton.innerHTML = 'Fire testObjcCallback'
callbackButton.onclick = function(e) {
bridge.callHandler('js_oc', {'foo': 'bar'},
}
})
同样的通过在OC端注册相应的方法,然后在js端通过在事件方法中进行oc端方法的直接调用
How
WebViewJavascriptBridge是如何做到灵活的实现两端相互通信的呢,我们知道目前OC与JS交互有两种方式:
//1.通过webView提供的原生的api可以直接调用js
//UIWebView
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
//WKWebView
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler
//2.通过iOS 7以后javaScript Core来进行交互
、、、、、、、
通过javaScript Core的局限性在于版本的要求是ios7及以上,还有一个就是JsCore的所有的操作都依赖与JSContext这个东西,通过webView能否获取到成为了关键。UIWebView我们可以通过在网页加载完毕之后再mianFrame上是可以获取到JSContext的,对于WKWebView由于存在于一个独立的进程中,获取其JSContext成为困难。而作者在这里采用的是第一种来实现交互。
OC调用JS的方法有了。那么JS调用OC是怎么获取到调用时机的呢,这里作者采用了拦截URL的方式进行通信,作者这里将加载的URL分为三种:
//1.注入js的bridge
https://__bridge_loaded__
//2.消息事件的查询
https://__wvjb_queue_message__
//3.正常的网页请求加载
通过webView相应的代理的方法进行请求的拦截判断进行不同的处理。
//UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (webView != _webView) { return YES; }
NSURL *url = [request URL];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
//判断是否为js注入URL或消息查询URL
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
//js注入URL ,注入jsBridge
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
//消息查询URL,进行可执行的消息进行查询并执行
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
//正常的网页加载URL
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
//WKWebView
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
} else {
decisionHandler(WKNavigationActionPolicyAllow);
}
}
这些URL的拦截是如何合适进行启动加载拦截的,这个框架的使用是需要前端加载一些固定的生成Bridge的代码的:
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 = 'https://__bridge_loaded__';
document.documentElement.appendChild(WVJBIframe);
setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
setupWebViewJavascriptBridge(function(bridge) {
//这里进行相应的js方法的注册和调用
})
当我们加载网页的时候就会执行上面的setupWebViewJavascriptBridge方法,在这个方法中如果有全局的WebViewJavascriptBridge实例,则将WebViewJavascriptBridge传入callback作为参数直接调用callBack,接着讲callBack存入一个全局的WVJBCallbacks数组中。如果没有WebViewJavascriptBridge这个实例,则会创建一个临时的iframe,并加载https://bridge_loaded然后立刻删除iframe。
这个时候就会触发webView的上面加载代理方法,并判断出是js注入URL,进行jsBridge的注入。
(function() {
if (window.WebViewJavascriptBridge) {
return;
}
if (!window.onerror) {
window.onerror = function(msg, url, line) {
console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
}
}
//挂载一个全局的WebViewJavascriptBridge
window.WebViewJavascriptBridge = {
registerHandler: registerHandler,
callHandler: callHandler,
disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
};
var messagingIframe;
// 记录正在进行中的消息,便于oc端获取消息
var sendMessageQueue = [];
//注册的消息对象
var messageHandlers = {};
var CUSTOM_PROTOCOL_SCHEME = 'https';
var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
//记录保存要回调的函数
var responseCallbacks = {};
var uniqueId = 1;
var dispatchMessagesWithTimeoutSafety = true;
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
function disableJavscriptAlertBoxSafetyTimeout() {
dispatchMessagesWithTimeoutSafety = false;
}
//消息进行发送,触发iframe的改变(核心方法)
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;
}
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
//处理来自oc端调用的消息
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);
}
}
}
}
function _handleMessageFromObjC(messageJSON) {
_dispatchMessageFromObjC(messageJSON);
}
messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
document.documentElement.appendChild(messagingIframe);
registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
setTimeout(_callWVJBCallbacks, 0);
function _callWVJBCallbacks() {
var callbacks = window.WVJBCallbacks;
delete window.WVJBCallbacks;
for (var i=0; i<callbacks.length; i++) {
callbacks[i](WebViewJavascriptBridge);
}
}
})();
上面大致就是js端的bridge的过程,两端是如何实现的相互的调用的尼,先看下两端用于数据传送的格式定义:
调用方发送的数据格式:
var message = {
handleName:"xxx"
data:object
callbackId:"xxx"
}
接受方进行回调发送的数据格式:
var message = {
responseId:"xxx"(等于上面的callbackId)
responseData:"xxx"
}
- JS call OC 的过程:
1.封装消息,调用_doSend进行发送
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
2.存在回调则在消息中加入回调的id,并保存回调的函数,触发iframe变化,在OC端进行url拦截
function _doSend(message, responseCallback) {
if (responseCallback) {
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
message['callbackId'] = callbackId;
}
//记录已经发送的message
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
3.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
3.1.调用js中的WebViewJavascriptBridge._fetchQueue()获取到消息json字符串
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
3.2.在OC端进行消息的处理
[_base flushMessageQueue:messageQueueString];
}
4.
- (void)flushMessageQueue:(NSString *)messageQueueString{
4.1将消息转化为对象
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
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];
}
4.2.对回调的message数据格式进行包装
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;
}
4.3.对注册的方法进行相应的方法的调用
handler(message[@"data"], responseCallback);
}
}
}
5.
- (void)_queueMessage:(WVJBMessage*)message {
5.1.对回调的message进行发送
[self _dispatchMessage:message];
}
- (void)_dispatchMessage:(WVJBMessage*)message {
5.2.对回调的消息进行序列化
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]) {
5.3.调用js的WebViewJavascriptBridge._handleMessageFromObjC方法将消息传递到js端
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
6.
function _dispatchMessageFromObjC(messageJSON) {
。。。。。。
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
if (message.responseId) {
6.获取到回调的函数,进行回调并删除保存的回调函数,调用结束
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
。。。。。。。。
}
}
}
- OC Call JS的过程
1.
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
1.对消息进行封装,并保存记录下回调Block
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];
}
2.
- (void)_dispatchMessage:(WVJBMessage*)message {
2.1.将消息进行序列化
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];
2.2.调用js的WebViewJavascriptBridge._handleMessageFromObjC方法将消息传递到js端
if ([[NSThread currentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
3.
function _dispatchMessageFromObjC(messageJSON) {
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
if (message.responseId) {
。。。。。。
} else {
3.1.设置回调
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
3.3.发起对回调的调用
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
3.2通过函数名称进行函数调用
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
4.
function _doSend(message, responseCallback) {
4.1.触发iframe的url,OC端进行拦截
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
5.
- (void)flushMessageQueue:(NSString *)messageQueueString{
5.1将message的字符串转化为对象
id messages = [self _deserializeMessageJSON:messageQueueString];
for (WVJBMessage* message in messages) {
NSString* responseId = message[@"responseId"];
5.2.通过responeId获取回调block进行回调
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
}
}
网友评论