JSBridge 实现原理探究
JSBridge 主要用于移动端混合开发,html5 与 native 进行通信的一种实现方案。现针对 iOS 端与网页通信方式进行探究,主要使用的是WebViewJavascriptBridge
先看下HTML部分引入的文件
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) {
...
}
以上为部分 HTML 引入文件,主要实现创建 iframe,并通过 bridge_loaded 的请求标识通知 native bridge 开始载入 WebViewJavascriptBridge_JS 中的内容
- (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;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
/// WebViewJavascriptBridge_JS 中的 js 会被执行,建立bridge
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
/// 下面有提及,QUEUE_HAS_MESSAGE 时,从web中获取数据
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
通过 OC 的代码可以看出 webview 在截取到 bridge_loaded 的网络请求时,开始将JS进行注入,建立一个相互通信的bridge
[_webView stringByEvaluatingJavaScriptFromString:javascriptCommand]
HTML 调用何如调用 NATIVE
HTML 中是使用callHandler调用的
/// bridge.callHandler(native method, json参数, 回调函数)
bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
log('JS got response', response)
})
在 WebViewJavascriptBridge_JS 中可以看到 callHandler 的具体实现
var CUSTOM_PROTOCOL_SCHEME = 'https';
var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
function callHandler(handlerName, data, responseCallback) {
...
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
/// 注意 responseCallbacks key:自动生成_s value:回调函数
/// message key: callbackId value:自动生成_s key:callbackId value:自动生成_s
/// 这样sendMessageQueue 中就能找到回调函数
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;
}
从中可以发现,HTML调用方法都是发送 CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; (方法命名可以看出,只是通知NATIVE有消息需要更新)这样的一个请求.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
...
if ([_base isQueueMessageURL:url]) {
/// QUEUE_HAS_MESSAGE 时,从web中获取数据
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
...
html 通过发送请求 QUEUE_HAS_MESSAGE 传递参数 (_doSend({ handlerName:handlerName, data:data }, responseCallback) 中的 { handlerName:handlerName, data:data }, 其中responseCallback为 html 内部持有)给native,flushMessageQueue为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;
}
/// messageQueueString 的形式:handlerName:handlerName, data:data
/// typedef NSDictionary WVJBMessage;
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];
....
/// 不包含 responseId
....
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
/// html 调用native时,WVJBResponseCallback 回调内容
responseCallback = ^(id responseData) {
...
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
/// key: callbackId value: responseData, 传给html
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
/// typedef void (^WVJBHandler)(id data, WVJBResponseCallback responseCallback);
/// 这里涉及到一个 messageHandlers,他是由native注册handle时生成的,这样可以通过html传入name,调用native的responseCallback事件
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
/// 传递数据给registerhandler,并通过WVJBMessage 将回调数据给html
handler(message[@"data"], responseCallback);
}
}
}
上面提到了messageHandlers,看下他是如何来的
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
handler 中还有 responseCallback ,返回消息如何传递给html
/// html 调用native时,WVJBResponseCallback 回调内容
responseCallback = ^(id responseData) {
...
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
/// key: callbackId value: responseData, 传给html
[self _queueMessage:msg];
};
HTML 调用 NATIVE 基本已经完成,现在做一个总结:
1.native 需要在html调用之前注册好方法
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}];
2.html传入下列参数 通过
- callbackId var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
- { handlerName:handlerName, data:data }
3.native获取参数拆分 handlerName 调用 messageHandlers 中的方法,如果需要回掉,执行WebViewJavascriptBridge._handleMessageFromObjC('%@'); 传入responseId:callbackid responseData:native返回参数
NATIVE 调用何如调用 HTML
看下native如何调用html的方法
[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];
消息组装 handlerName:testJavascriptHandler callbackId: @"objc_cb_%ld", ++_uniqueId
- (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;
}
/// 执行 @"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
[self _queueMessage:message];
}
native将handlerName、callbackId、data 传递给 html
function _dispatchMessageFromObjC(messageJSON) {
...
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);
}
HTML部分
bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
log('ObjC called testJavascriptHandler with', data)
var responseData = { 'Javascript Says':'Right back atcha!' }
log('JS responding with', responseData)
responseCallback(responseData)
})
/// 和 native 方式一样,将注册的方法先加入messageHandlers
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
这边可以看到 _doSend ,就会进入 _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) {
/// html responseCallBack 处理后,数据回调 CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 通知native获取数据
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
/// messageHandlers 为 HTML 中注册的
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);
}
html 处理完成时如何进行数据回调的? _doSend通过请求QUEUE_HAS_MESSAGE,native 调用 WebViewJavascriptBridge._fetchQueue();获取数据调用flushMessageQueue处理数据
- (void)flushMessageQueue:(NSString *)messageQueueString{
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
}
}
_responseCallbacks? 调用时已经注册,所以,html的回调信息能够回到native中
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
NSMutableDictionary* message = [NSMutableDictionary dictionary];
...
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
...
}
针对native调用html做一个总结:
- native方法调用,记录responseCallbacks
- native执行html的javascript,等待html中执行完成,调用QUEUE_HAS_MESSAGE,等待native来获取数据,最终将数据回传
由此可见jsbridge是通过URL截取的这么一种形式进行数据处理的,其中,html部分,数据处理完成后,如果需要回掉,需要通知native进行fetch数据,再将数据进行返回。native部分也是,处理完消息通过 _handleMessageFromObjC 通知html。