JS和Native交互之 -WebViewJavascriptBridge源码分析
2016-03-08 10:16
489 查看
源码地址:https://github.com/marcuswestin/WebViewJavascriptBridge
1、对外接口
三类API接口用于OC与JS之间交互:初始化接口;发送消息接口,并且可以添加发送消息完成的回调函数;事件注册和调用接口,需要先在一端注册事件,另一端可以根据事件名称调用函数
除了上述提到的外部方法:还有两个方法十分重要,JS部分最重要的内部方法: [b]_handleMessageFromObjC;OC部分重要的内部方法:[b]flushMessageQueue[/b][/b]
2、类结构图
WebViewJavascriptBridge目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用时可以二选其一;
WebViewjavascriptBridgeBase是通用类,用于处理从Native到JS的消息注入,消息队列处理和分发,JSON数据的序列化和反序列化,LOG输出;
3、源码分析
3.1 消息发送JS-》Native
[align=left][bridge send:(id)data][/align]
[align=left][bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback][/align]
这两个函数最后都是调用_doSend({ 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,由不断加1的唯一需要和时间戳构成,如果有responseCallback函数,使用callbackId作为索引,存入responseCallbacks对象,等到从OC侧返回的信息中对应的callbackId与当前responseCallbacks中callbackId相同时,调用回调函数responseCallback;sendMessageQueue是个消息数组,每次的新消息放入其中,messagingIframe是iframe对象,当设置src产生一次请求,在OC端的
(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType 会拦截请求内容
代码:
- (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 ([_baseisCorrectProcotocolScheme:url]) {
if ([_baseisCorrectHost:url]) {
NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)])
{
return [strongDelegate webView:webView shouldStartLoadWithRequest:requestnavigationType:navigationType];
} else {
return YES;
}
}
重点部分:执行注入_evaluateJavascript,
OC
-(NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
JS
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue)
sendMessageQueue = []
return messageQueueString
}
这个函数从JS的sendMessageQueue消息队列获取内容返回,这个sendMessageQueue是在之前的_doSend函数中传入的消息内容,也就是NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];这句代码获得从JS侧拿到的数据内容,然后调用[_baseflushMessageQueue:messageQueueString];对消息分发处理
- (void)flushMessageQueue:(NSString *)messageQueueString{
id messages = [self_deserializeMessageJSON:messageQueueString];
if (![messages isKindOfClass:[NSArray class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messagesclass], messages);
return;
}
for (WVJBMessage* messagein messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback =_responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacksremoveObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback =NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNullnull];
}
WVJBMessage* msg = @{@"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler;
if (message[@"handlerName"]) {
handler = self.messageHandlers[message[@"handlerName"]];
} else {
handler = self.messageHandler;
}
if (!handler) {
[NSException raise:@"WVJBNoHandlerException" format:@"No
handler for message from JS: %@", message];
}
handler(message[@"data"], responseCallback);
}
}
}
这个是整个框架中OC侧重要的函数,但是目前首先分析消息发送JS-》Native涉及到的部分内容,返回的消息包含callbackId,数据拼接后调用[self_queueMessage:msg];发送回JS侧的数据改为responseId为关键字key,具体如下:
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueueaddObject:message];
} else {
[self _dispatchMessage:message];
}
}
self.startupMessageQueue只有首次启动时有效,之后为nil,所以都是走[self_dispatchMessage:message];
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self_serializeMessage:message];
[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 = [NSStringstringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');",
messageJSON];
if ([[NSThreadcurrentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
此函数对message特殊字符进行转义处理,然后执行JS注入语句,WebViewJavascriptBridge._handleMessageFromObjC执行到JS侧
这个是整个框架中JS侧重要的函数,用于处理从OC侧返回的消息
function _dispatchMessageFromObjC(messageJSON) {
setTimeout(function _timeoutDispatchMessageFromObjC() {
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({ responseId:callbackResponseId, responseData:responseData })
}
}
var handler = WebViewJavascriptBridge._messageHandler
if (message.handlerName) {
handler = messageHandlers[message.handlerName]
}
try {
handler(message.data, responseCallback)
} catch(exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
}
}
}
})
}
执行JS侧本地回调函数
3.2 消息发送 OC--》JS
[align=left]bridge.send("Hi there!")[/align]
[align=left]bridge.send({ Foo:"Bar" })[/align]
[align=left]bridge.send(data, function responseCallback(responseData) { ... })[/align]
调用
[_base sendData:dataresponseCallback:responseCallback handlerName:nil];
执行 _queueMessage
3.3 OC注册事件和JS调用
OC侧注册
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handlercopy];
}
JS调用
function callHandler(handlerName, data, responseCallback) {
_doSend({ handlerName:handlerName, data:data }, responseCallback)
}
加入handlerName和data数据传给OC侧,JS侧记录responseCallback,最后也会走到- (void)flushMessageQueue:(NSString *)messageQueueString函数中,由于既没有callbackId也没有responseId,所以只处理handlerName及相关数据,最后走到 -
(void)flushMessageQueue:(NSString*)messageQueueString解析,OC侧执行之前注册的handler并传入data数据
3.4 JS注册事件和OC调用
JS注册
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler
}
本地记录
OC调用
- (void)callHandler:(NSString *)handlerName data:(id)data {
[self callHandler:handlerName data:dataresponseCallback:nil];
}
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback
{
[_base sendData:data responseCallback:responseCallbackhandlerName:handlerName];
}
handlerName和data 在 - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback
handlerName:(NSString*)handlerName中处理
4 、总结
1、与其它框架相比,此框架没有采用url拦截解析参数方式,而是多次JS注入参数获取,API接口暴露的操作在底层需要多次OC与JS之间交互完成
2、WKwebview部分没有使用新的delgate方法,而是沿用iframe方式,个人觉得采用新接口可能效率更高
1、对外接口
初始化OC | 初始化JS |
[align=left][WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview handler:(WVJBHandler)handler][/align] [align=left][/align] [align=left][WebViewJavascriptBridge bridgeForWebView:(UIWebView/WebView*)webview webViewDelegate:(UIWebViewDelegate*)webViewDelegate handler:(WVJBHandler)handler][/align] | [align=left]document.addEventListener('WebViewJavascriptBridgeReady', function onBridgeReady(event) { ... }, false)[/align] [align=left][/align] [align=left]bridge.init(function messageHandler(data, response) { ... })[/align] |
OC发送消息to JS | JS发送消息to OC |
[align=left][bridge send:(id)data][/align] [align=left][bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback][/align] | [align=left]bridge.send("Hi there!")[/align] [align=left]bridge.send({ Foo:"Bar" })[/align] [align=left]bridge.send(data, function responseCallback(responseData) { ... })[/align] |
OC注册事件(先) | JS调用事件(后) |
[align=left][bridge registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler][/align] | [align=left]WebViewJavascriptBridge.callHandler("handlerName")[/align] |
OC调用事件(后) | JS注册事件(先) |
[align=left][bridge callHandler:(NSString*)handlerName data:(id)data][/align] [align=left][bridge callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)callback][/align] | [align=left]bridge.registerHandler("handlerName", function(responseData) { ... })[/align] |
除了上述提到的外部方法:还有两个方法十分重要,JS部分最重要的内部方法: [b]_handleMessageFromObjC;OC部分重要的内部方法:[b]flushMessageQueue[/b][/b]
2、类结构图
WebViewJavascriptBridge目前既支持原有的UIWebView,也支持iOS8+之后新的WKWebView,使用时可以二选其一;
WebViewjavascriptBridgeBase是通用类,用于处理从Native到JS的消息注入,消息队列处理和分发,JSON数据的序列化和反序列化,LOG输出;
3、源码分析
3.1 消息发送JS-》Native
[align=left][bridge send:(id)data][/align]
[align=left][bridge send:(id)data responseCallback:(WVJBResponseCallback)responseCallback][/align]
这两个函数最后都是调用_doSend({ 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,由不断加1的唯一需要和时间戳构成,如果有responseCallback函数,使用callbackId作为索引,存入responseCallbacks对象,等到从OC侧返回的信息中对应的callbackId与当前responseCallbacks中callbackId相同时,调用回调函数responseCallback;sendMessageQueue是个消息数组,每次的新消息放入其中,messagingIframe是iframe对象,当设置src产生一次请求,在OC端的
(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType 会拦截请求内容
代码:
- (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 ([_baseisCorrectProcotocolScheme:url]) {
if ([_baseisCorrectHost:url]) {
NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)])
{
return [strongDelegate webView:webView shouldStartLoadWithRequest:requestnavigationType:navigationType];
} else {
return YES;
}
}
重点部分:执行注入_evaluateJavascript,
OC
-(NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
JS
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue)
sendMessageQueue = []
return messageQueueString
}
这个函数从JS的sendMessageQueue消息队列获取内容返回,这个sendMessageQueue是在之前的_doSend函数中传入的消息内容,也就是NSString *messageQueueString = [self_evaluateJavascript:[_basewebViewJavascriptFetchQueyCommand]];这句代码获得从JS侧拿到的数据内容,然后调用[_baseflushMessageQueue:messageQueueString];对消息分发处理
- (void)flushMessageQueue:(NSString *)messageQueueString{
id messages = [self_deserializeMessageJSON:messageQueueString];
if (![messages isKindOfClass:[NSArray class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messagesclass], messages);
return;
}
for (WVJBMessage* messagein messages) {
if (![message isKindOfClass:[WVJBMessage class]]) {
NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [messageclass], message);
continue;
}
[self _log:@"RCVD" json:message];
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback =_responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacksremoveObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback =NULL;
NSString* callbackId = message[@"callbackId"];
if (callbackId) {
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNullnull];
}
WVJBMessage* msg = @{@"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
} else {
responseCallback = ^(id ignoreResponseData) {
// Do nothing
};
}
WVJBHandler handler;
if (message[@"handlerName"]) {
handler = self.messageHandlers[message[@"handlerName"]];
} else {
handler = self.messageHandler;
}
if (!handler) {
[NSException raise:@"WVJBNoHandlerException" format:@"No
handler for message from JS: %@", message];
}
handler(message[@"data"], responseCallback);
}
}
}
这个是整个框架中OC侧重要的函数,但是目前首先分析消息发送JS-》Native涉及到的部分内容,返回的消息包含callbackId,数据拼接后调用[self_queueMessage:msg];发送回JS侧的数据改为responseId为关键字key,具体如下:
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueueaddObject:message];
} else {
[self _dispatchMessage:message];
}
}
self.startupMessageQueue只有首次启动时有效,之后为nil,所以都是走[self_dispatchMessage:message];
- (void)_dispatchMessage:(WVJBMessage*)message {
NSString *messageJSON = [self_serializeMessage:message];
[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 = [NSStringstringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');",
messageJSON];
if ([[NSThreadcurrentThread] isMainThread]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
此函数对message特殊字符进行转义处理,然后执行JS注入语句,WebViewJavascriptBridge._handleMessageFromObjC执行到JS侧
这个是整个框架中JS侧重要的函数,用于处理从OC侧返回的消息
function _dispatchMessageFromObjC(messageJSON) {
setTimeout(function _timeoutDispatchMessageFromObjC() {
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({ responseId:callbackResponseId, responseData:responseData })
}
}
var handler = WebViewJavascriptBridge._messageHandler
if (message.handlerName) {
handler = messageHandlers[message.handlerName]
}
try {
handler(message.data, responseCallback)
} catch(exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
}
}
}
})
}
执行JS侧本地回调函数
3.2 消息发送 OC--》JS
[align=left]bridge.send("Hi there!")[/align]
[align=left]bridge.send({ Foo:"Bar" })[/align]
[align=left]bridge.send(data, function responseCallback(responseData) { ... })[/align]
调用
[_base sendData:dataresponseCallback:responseCallback handlerName:nil];
执行 _queueMessage
3.3 OC注册事件和JS调用
OC侧注册
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handlercopy];
}
JS调用
function callHandler(handlerName, data, responseCallback) {
_doSend({ handlerName:handlerName, data:data }, responseCallback)
}
加入handlerName和data数据传给OC侧,JS侧记录responseCallback,最后也会走到- (void)flushMessageQueue:(NSString *)messageQueueString函数中,由于既没有callbackId也没有responseId,所以只处理handlerName及相关数据,最后走到 -
(void)flushMessageQueue:(NSString*)messageQueueString解析,OC侧执行之前注册的handler并传入data数据
3.4 JS注册事件和OC调用
JS注册
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler
}
本地记录
OC调用
- (void)callHandler:(NSString *)handlerName data:(id)data {
[self callHandler:handlerName data:dataresponseCallback:nil];
}
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback
{
[_base sendData:data responseCallback:responseCallbackhandlerName:handlerName];
}
handlerName和data 在 - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback
handlerName:(NSString*)handlerName中处理
4 、总结
1、与其它框架相比,此框架没有采用url拦截解析参数方式,而是多次JS注入参数获取,API接口暴露的操作在底层需要多次OC与JS之间交互完成
2、WKwebview部分没有使用新的delgate方法,而是沿用iframe方式,个人觉得采用新接口可能效率更高
相关文章推荐
- Android MVP Pattern
- Swift中的willSet与didSet zhuan
- 12步创建高性能Web APP
- Android实现再按一次退出程序
- 【Android】自定义ViewPager控制其页面切换速度
- Android M权限请求
- Android4.2锁屏流程【Android锁屏解析三】
- Android 系统属性SystemProperty分析
- JS和Native交互之 -WebViewJavascriptBridge
- Android 数据存储之文件存储
- 如何在Android studio中使用java8 的Lambda表达式
- Android打开或者关闭软键盘的解决方案
- Android 4.4 以上更改状态栏颜色
- iOS8 【xcode6中添加pch全局引用文件】
- Android和服务器通信,从服务器端获取图片
- 获取android软键盘高度
- 2016年3月8日Android实习日记
- iOS海哥开发笔记 (海哥原创,绘图Quartz-2d的简单使用 二)
- ios常见加密解密算法介绍与应用
- iOS海哥开发笔记 (海哥原创,Quartz-2d的简单使用 一)