WebViewJavascriptBridge核心原理就是2个方法:
原生调用js(直接执行js方法)
- WKWebView:
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
- UIWebView:
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
js调用原生(拦截URL的形式)
- WKWebView:并未用到WKScriptMessageHandler代理方法
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
- UIWebView:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
浅析:
WebViewJavaScriptBridge在原生和js端各自维护了WebViewJavaScriptBridge对象,完成各自消息收发工作,管理相应事件的messageHandlers容器,和callBack回调函数等
原生调用js:
- js端需要先注册相应的js方法
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)
})
定义一个messageHandlers容器通过handlerName保存回调方法
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
- 原生通过CallHandler调用相应的js方法
id data = @{ @"greetingFromObjC": @"Hi there, JS!" };
[_bridge callHandler:@"testJavascriptHandler" data:data responseCallback:^(id response) {
NSLog(@"testJavascriptHandler responded: %@", response);
}];
callHandler内部会调用sendData:responseCallback:handlerName:方法,包装message字典(包含
data = @{ @"greetingFromObjC": @"Hi there, JS!" },
callbackId = @"objc_cb_1",
handlerName = @"testJavascriptHandler" )
并通过属性responseCallbacks字典通过key:callbackId保存responseCallback回调函数,responseCallbacks[@"objc_cb_1"] = [responseCallback copy];接着调用_queueMessage:方法
- (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];
}
内部会调用_dispatchMessage:方法,最终会执行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];
});
}
}
WebViewJavascriptBridge._handleMessageFromObjC('{"callbackId":"objc_cb_1","data":{"greetingFromObjC":"Hi there, JS!"},"handlerName":"testJavascriptHandler"}');
_handleMessageFromObjC会调用_dispatchMessageFromObjC方法,通过messageHandlers[message.handlerName]取到之前注册保存的js方法并执行,执行完之后会回调
responseCallback(responseData)方法
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
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);
}
}
}
}
_doSend({ handlerName:"testJavascriptHandler", responseId:"objc_cb_1", responseData:{ 'Javascript Says':'Right back atcha!' } });其中responseId = message.callbackId
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;
}
接着更改iframe.src路径,移动端会拦截此url,调用js方法WebViewJavascriptBridge._fetchQueue();取到{ handlerName:"testJavascriptHandler", responseId:"objc_cb_1", responseData:{ 'Javascript Says':'Right back atcha!' }
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base 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);
}
}
}
通过之前responseCallbacks[responseId]取到之前原生callHandler:responseCallback:方法中的block回调函数并执行,收到js端的回复
总结
原生调用js时通过执行WebViewJavascriptBridge._handleMessageFromObjC('%@')方法,传递参数给js端,然后js回调结果通过更改url的形式拦截,再调用js方法WebViewJavascriptBridge._fetchQueue()取到js的回调处理结果给原生, js调用原生和原生调用js相似
两边都维护了一个WebViewJavaScriptBridge的对象,消息都封装成为一个message,然后所有的callback,都巧妙的转换成了id。通过直接传递id,然后根据id分别去对应的地方去寻找到对应的callback。这种方式,其实也是值得我们去学习和使用的