标签:
网上好多都是在介绍 WebViewJavascriptBridge如何使用,这篇文章就来说说 WebViewJavascriptBridge 设计原理。
主要从两个过程来讲一下:js调用UIViewController中的代码(Native),Native调用js
首先有两个问题:
a.Native(中的UIWebView)是否可以直接调用js method(方法)? 可以。
b.js 是否可以直接调用Native的mthod?不行。
明确上述两个问题,那么上图就不难明白了,webpage中的js method和webview本地的method之间关系。那WebViewJavascriptBridge出现是否解决这个问题(这个问题就是让js可以直接调用native的method)呢?答案是否定的?没有本质还是用uiwebview的代理方法进行字段拦截(判断url的scheme),实现js间接调用native的method。
我们来看WebViewJavascriptBridge提供的demo:
主要的核心是下面两个,接下来我们就来讨论一下其设计原理。
在概述中说过,js是不能直接调用native的method所以,需要借助- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType,这个方法大家不陌生,每次在重新定向URL的时候,这个方法就会被触发,通常情况,我们会在这里做一些拦截完成js和本地的间接交互什么的。那么WebViewJavascriptBridge也不另外,也是这么做。
我们先来看看在ExampleApp.html文件中点击一个按钮发起请求的代码:
1
2
3
4
5
6
7
8
9
10
|
var callbackButton = document.getElementById( ‘buttons‘ ).appendChild(document.createElement( ‘button‘ )) callbackButton.innerHTML = ‘Fire testObjcCallback‘ callbackButton.onclick = function(e) { e.preventDefault() log( ‘JS calling handler "testObjcCallback"‘ ) //1 bridge.callHandler( ‘testObjcCallback‘ , { ‘foo‘ : ‘cccccccccccc‘ }, function(response) { log( ‘JS got response‘ , response) }) } |
1
2
3
4
|
bridge.callHandler( ‘testObjcCallback‘ , { ‘foo‘ : ‘cccccccccccc‘ }, function(response) { log( ‘JS got response‘ , response) }) } |
在文件WebViewJavascriptBridge.js.txt里面我们找找这个方法:
1
2
3
|
function callHandler(handlerName, data, responseCallback) { _doSend({ handlerName:handlerName, data:data }, responseCallback) } |
1
2
3
4
5
6
7
8
9
|
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是个字符串,responseCallBacks[] 一看就知道是个字典 ,这个字典把回掉(我们猜测)的方法responseCallback给保存起来,这Key(也就是callbackId)应该是唯一的,通过计数和时间应该知道这个字符串应该是唯一的,message也是一个字典,这是给message添加了一个新的key-value。干嘛呢?我也不知道,我们来看看sendMessageQueue是什么,大家一个push就知道应该是个数组。他吧一个字典放到一个消息队列中(数组队列),让后产生一个src(url scheme)。
有两个变量我们看看:
1
2
|
var CUSTOM_PROTOCOL_SCHEME = ‘wvjbscheme‘ var QUEUE_HAS_MESSAGE = ‘__WVJB_QUEUE_MESSAGE__‘ |
干嘛用,肯定是给webview 的 delegate判断用的,你感觉呢?(肯定是)
下面是在文件:WebViewJavascriptBridge.m
好了到了这里大家猜猜这个要干嘛?肯定是要发url让web截取对吧?那还用问啊,肯定是啊,已经说过了js能不能调用native的funtion函数?不能。我们来看看这个messagingIframe是:
1
2
3
4
5
6
|
function _createQueueReadyIframe(doc) { messagingIframe = doc.createElement( ‘iframe‘ ) messagingIframe.style.display = ‘none‘ messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ‘://‘ + QUEUE_HAS_MESSAGE doc.documentElement.appendChild(messagingIframe) } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
- (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 ([[url scheme] isEqualToString:kCustomProtocolScheme]) { if ([[url host] isEqualToString:kQueueHasMessage]) { //会走这里 [self _flushMessageQueue]; } else { NSLog(@ "WebViewJavascriptBridge: WARNING: Received unknown WebViewJavascriptBridge command %@://%@" , kCustomProtocolScheme, [url path]); } return NO; } else if (strongDelegate && [strongDelegate respondsToSelector: @selector (webView:shouldStartLoadWithRequest:navigationType:)]) { return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType]; } else { return YES; } } |
#define kCustomProtocolScheme @"wvjbscheme"
这个定义是什么意思,我们先不做解释,刚才我们说过js不能直接调用native的function,大家只要记住这点,接着往下走就是了。至于为什么走这里,自己看代码(上文有提到),我们看看_flushMessageQueue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
- ( void )_flushMessageQueue { NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@ "WebViewJavascriptBridge._fetchQueue();" ]; //json转成数组 id messages = [self _deserializeMessageJSON:messageQueueString]; if (![messages isKindOfClass:[NSArray class ]]) { NSLog(@ "WebViewJavascriptBridge: WARNING: Invalid %@ received: %@" , [messages class ], messages); return ; } for (WVJBMessage* message in messages) { if (![message isKindOfClass:[WVJBMessage class ]]) { NSLog(@ "WebViewJavascriptBridge: WARNING: Invalid %@ received: %@" , [message class ], message); continue ; } [self _log:@ "RCVD" json:message]; //用于js回掉 NSString* responseId = message[@ "responseId" ]; if (responseId) { WVJBResponseCallback responseCallback = _responseCallbacks[responseId]; responseCallback(message[@ "responseData" ]); [_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; if (message[@ "handlerName" ]) { handler = _messageHandlers[message[@ "handlerName" ]]; } else { handler = _messageHandler; } if (!handler) { [NSException raise:@ "WVJBNoHandlerException" format:@ "No handler for message from JS: %@" , message]; } handler(message[@ "data" ], responseCallback); } } } |
我们逐行来看:
NSString *messageQueueString = [_webView stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];
我们必须回去到js文件中去,这里是webview直接调用js中的方法:
1
2
3
4
5
|
function _fetchQueue() { var messageQueueString = JSON.stringify(sendMessageQueue) sendMessageQueue = [] return messageQueueString } |
handlerName:handlerName,
data:data,
callbackId:callbackId
这个消息字典此时被取出来准备做什么,这里提示下我们已经走到webview 的delegate里面了,所以拿到这些信息肯定是调用native的method对吧?肯定是的。接着往下走,接着会把json字符串转成数组,然后进行判断,
1
|
NSString* responseId = message[@ "responseId" ]; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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; if (message[@ "handlerName" ]) { handler = _messageHandlers[message[@ "handlerName" ]]; } else { handler = _messageHandler; } if (!handler) { [NSException raise:@ "WVJBNoHandlerException" format:@ "No handler for message from JS: %@" , message]; } handler(message[@ "data" ], responseCallback); |
1
2
3
|
- ( void )registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler { _messageHandlers[handlerName] = [handler copy]; } |
找到了(在文件 ExampleAppViewController.m的viewdidload中),这里有方法testObjecCallback
1
2
3
4
|
[_bridge registerHandler:@ "testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) { NSLog(@ "testObjcCallback called: %@" , data); responseCallback(@ "Response from testObjcCallback" ); }]; |
我们接着来看看:
1
2
3
4
5
6
7
8
|
responseCallback = ^(id responseData) { if (responseData == nil) { responseData = [NSNull null ]; } WVJBMessage* msg = @{ @ "responseId" :callbackId, @ "responseData" :responseData }; [self _queueMessage:msg]; }; |
1
2
3
4
5
6
7
|
- ( void )_queueMessage:(WVJBMessage*)message { if (_startupMessageQueue) { [_startupMessageQueue addObject:message]; } else { [self _dispatchMessage:message]; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
- ( 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 = [NSString stringWithFormat:@ "WebViewJavascriptBridge._handleMessageFromObjC(‘%@‘);" , messageJSON]; if ([[NSThread currentThread] isMainThread]) { [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand]; } else { __strong WVJB_WEBVIEW_TYPE* strongWebView = _webView; dispatch_sync(dispatch_get_main_queue(), ^{ [strongWebView stringByEvaluatingJavaScriptFromString:javascriptCommand]; }); } } |
我们在回到WebViewJavascriptBridge.js.txt文件中看到
1
2
3
4
5
6
7
|
function _handleMessageFromObjC(messageJSON) { if (receiveMessageQueue) { receiveMessageQueue.push(messageJSON) } else { //肯定走这个 为什么呢? _dispatchMessageFromObjC(messageJSON) } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
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) } } } }) } |
1
|
@{ @ "responseId" :callbackId, @ "responseData" :responseData } |
所以这里messageHandlers刚才也说过了用来存方法的,callbackId被换了个名字叫responseId意思一样,只要值没变就行,所以就会执行:
1
2
3
|
bridge.callHandler( ‘testObjcCallback‘ , { ‘foo‘ : ‘cccccccccccc‘ }, function(response) { log( ‘JS got response‘ , response) }) |
总结一下:js这边 先把方法名字、参数、处理方法保存成一个字典在转成json字符串,在通过UIWebview调用js中某个方法把这个json字符串传到Native中去(不是通过url传的,这样太low了),同时把这个处理的方法以key-value形式放到一个js的字典中。
UIWebView在收到这个json之后,进行数据处理、还有js的回掉的处理方法(就是那个callbackId)处理完成后也会拼成一个key-value字典通过调用js传回去(可以直接调用js)。
js在接到这个json后,根据responseId读取responseCallbacks中处理方法进行处理Native code返回的数据。
过程不是直接调用js,也是通过js调用Native过程一样的处理方式。
大体来看一下,先看一个按钮的单击事件:
1
2
3
4
5
6
|
- ( void )callHandler:(id)sender { id data = @{ @ "greetingFromObjC" : @ "Hi there, JS!" }; [_bridge callHandler:@ "testJavascriptHandler" data:data responseCallback:^(id response) { NSLog(@ "testJavascriptHandler responded: %@" , response); }]; } |
看看callHandler:
1
2
3
|
- ( void )callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback { [self _sendData:data responseCallback:responseCallback handlerName:handlerName]; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
- ( 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]; _responseCallbacks[callbackId] = [responseCallback copy]; message[@ "callbackId" ] = callbackId; } if (handlerName) { message[@ "handlerName" ] = handlerName; } [self _queueMessage:message]; } |
1
2
3
4
5
6
|
NSString* responseId = message[@ "responseId" ]; if (responseId) { WVJBResponseCallback responseCallback = _responseCallbacks[responseId]; responseCallback(message[@ "responseData" ]); [_responseCallbacks removeObjectForKey:responseId]; } |
总结:native将方法名、参数、回到的id放到一个对象中传给js。
js根据方法名字调用相应方法,之后将返回数据和responseId拼装,最后通过src 重定向到UIWebview 的delegate。
native得到数据后根据responseId调用事先装入_responseCallbacks的block,动态读取调用,从而完成交互。
标签:
原文地址:http://www.cnblogs.com/LiLihongqiang/p/5781802.html