WebViewJavascriptBridge 原理分析
網(wǎng)上好多都是在介紹?WebViewJavascriptBridge如何使用,這篇文章就來說說?WebViewJavascriptBridge 設(shè)計原理。
主要從兩個過程來講一下:js調(diào)用UIViewController中的代碼(Native),Native調(diào)用js
1.概述
? ?首先有兩個問題:
? ? a.Native(中的UIWebView)是否可以直接調(diào)用js method(方法)? ?可以。
? ? b.js 是否可以直接調(diào)用Native的mthod?不行。
? ?明確上述兩個問題,那么上圖就不難明白了,webpage中的js method和webview本地的method之間關(guān)系。那WebViewJavascriptBridge出現(xiàn)是否解決這個問題(這個問題就是讓js可以直接調(diào)用native的method)呢?答案是否定的?沒有本質(zhì)還是用uiwebview的代理方法進行字段攔截(判斷url的scheme),實現(xiàn)js間接調(diào)用native的method。
? ?我們來看WebViewJavascriptBridge提供的demo:
? ?
? ?主要的核心是下面兩個,接下來我們就來討論一下其設(shè)計原理。
2.js調(diào)用Native method
? 在概述中說過,js是不能直接調(diào)用native的method所以,需要借助- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType,這個方法大家不陌生,每次在重新定向URL的時候,這個方法就會被觸發(fā),通常情況,我們會在這里做一些攔截完成js和本地的間接交互什么的。那么WebViewJavascriptBridge也不另外,也是這么做。
我們先來看看在ExampleApp.html文件中點擊一個按鈕發(fā)起請求的代碼:
[html]?view plain?copy
?print?
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)??
????????????})??
????????}??
估計大家大體都能看懂,唯獨有疑問的地方是:
[html]?view plain?copy
?print?
bridge.callHandler('testObjcCallback',?{'foo':?'cccccccccccc'},?function(response)?{??
????????????????log('JS?got?response',?response)??
????????????})??
????????}??
這段代碼先不說,上面代碼就是一個按鈕的普通單擊事件方法。我們一起想一下,如果這個按鈕需要被點擊之后調(diào)用native中的funtion函數(shù),之后需要把這個(native的)funtion函數(shù)處理結(jié)果返回給js中的方法繼續(xù)處理。這個是我們需求,帶著這個需求我們看一下這個方法,testObjcCallBack這個我們猜測一下應(yīng)該native中的方法或者一個能夠調(diào)用到方法的name/id,后面這個是個json{‘foo’:‘ccccccccccccc’},應(yīng)該是個參數(shù),那么后面這個方法一看log應(yīng)該知道,是對native返回的result進行處理的方法。拿具體是不是呢?只要找到callHandler方法就知道了。
在文件WebViewJavascriptBridge.js.txt里面我們找找這個方法:
[html]?view plain?copy
?print?
function?callHandler(handlerName,?data,?responseCallback)?{??
????????_doSend({?handlerName:handlerName,?data:data?},?responseCallback)??
????}??
這里又多了一個方法叫_doSend連個參數(shù) 第1個是字典key-value定義,第二個是一個方法的指針(看看上面的方法你就知道了),那我們必須在同一個文件里面看看能不能找到這個_doSend方法:
[html]?view plain?copy
?print?
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)應(yīng)該是唯一的,通過計數(shù)和時間應(yīng)該知道這個字符串應(yīng)該是唯一的,message也是一個字典,這是給message添加了一個新的key-value。干嘛呢?我也不知道,我們來看看sendMessageQueue是什么,大家一個push就知道應(yīng)該是個數(shù)組。他吧一個字典放到一個消息隊列中(數(shù)組隊列),讓后產(chǎn)生一個src(url scheme)。
有兩個變量我們看看:
[html]?view plain?copy
?print?
var?CUSTOM_PROTOCOL_SCHEME?=?'wvjbscheme'??
var?QUEUE_HAS_MESSAGE?=?'__WVJB_QUEUE_MESSAGE__'??
干嘛用,肯定是給webview 的 delegate判斷用的,你感覺呢?(肯定是)
下面是在文件:WebViewJavascriptBridge.m
好了到了這里大家猜猜這個要干嘛?肯定是要發(fā)url讓web截取對吧?那還用問啊,肯定是啊,已經(jīng)說過了js能不能調(diào)用native的funtion函數(shù)?不能。我們來看看這個messagingIframe是:
[html]?view plain?copy
?print?
function?_createQueueReadyIframe(doc)?{??
????messagingIframe?=?doc.createElement('iframe')??
????messagingIframe.style.display?=?'none'??
????messagingIframe.src?=?CUSTOM_PROTOCOL_SCHEME?+?'://'?+?QUEUE_HAS_MESSAGE??
????doc.documentElement.appendChild(messagingIframe)??
}??
原來就是iframe,這個就不同給大家解釋了。好了src一產(chǎn)生就會出現(xiàn)什么,uiwebview代理回掉截獲,此時我們把目光回到UIWebview的Native下面:
[html]?view plain?copy
?print?
-?(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;??
????}??
}??
一看就頭大,哈哈,是,我也頭大。看看上面的注釋說 會走這里,我們看看為什么會走那里,最外圈的if([url scheme])判斷是
#define kCustomProtocolScheme @"wvjbscheme"
這個定義是什么意思,我們先不做解釋,剛才我們說過js不能直接調(diào)用native的function,大家只要記住這點,接著往下走就是了。至于為什么走這里,自己看代碼(上文有提到),我們看看_flushMessageQueue:
[html]?view plain?copy
?print?
-?(void)_flushMessageQueue?{??
????NSString?*messageQueueString?=?[_webView?stringByEvaluatingJavaScriptFromString:@"WebViewJavascriptBridge._fetchQueue();"];??
????//json轉(zhuǎn)成數(shù)組??
????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();"];
我們必須回去到j(luò)s文件中去,這里是webview直接調(diào)用js中的方法:
[html]?view plain?copy
?print?
function?_fetchQueue()?{??
????var?messageQueueString?=?JSON.stringify(sendMessageQueue)??
????sendMessageQueue?=?[]??
????return?messageQueueString??
}??
謝天謝地這個方法代碼不多,這個消息很眼熟,SendMessageQueue,剛才我們說什么來?他是一個字典,那里面有哪些東西,我么來看看
handlerName:handlerName,
data:data,
callbackId:callbackId?
這個消息字典此時被取出來準備做什么,這里提示下我們已經(jīng)走到webview 的delegate里面了,所以拿到這些信息肯定是調(diào)用native的method對吧?肯定是的。接著往下走,接著會把json字符串轉(zhuǎn)成數(shù)組,然后進行判斷,
[html]?view plain?copy
?print?
NSString*?responseId?=?message[@"responseId"];??
有沒有responseid,你說又沒,肯定沒有啊(你不行看看上面),所以就這這里了
[html]?view plain?copy
?print?
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);??
這部分是重點,到底他是怎么要調(diào)用本地function的,callbackId大家熟悉吧,判斷是否為空,不為空給他指定一個block,這個不說了,block指定,此時不調(diào)用(手動調(diào)用才會執(zhí)行),這個剛才說了用來處理native的function處理的result用于把處理后的值返回給js的,接著往下去,看到handler這個方法會從message找到handlerName,這里我們看一下多了一個_messageHandlers字典,從這個字典獲取一個block(WVJBHandler是一個block),直接執(zhí)行了。那我們看看_messageHandlers是怎么被添加block的:
[html]?view plain?copy
?print?
-?(void)registerHandler:(NSString?*)handlerName?handler:(WVJBHandler)handler?{??
????_messageHandlers[handlerName]?=?[handler?copy];??
}??
那又是誰調(diào)用了這個方法:
找到了(在文件 ExampleAppViewController.m的viewdidload中),這里有方法testObjecCallback
[html]?view plain?copy
?print?
[_bridge?registerHandler:@"testObjcCallback"?handler:^(id?data,?WVJBResponseCallback?responseCallback)?{??
????NSLog(@"testObjcCallback?called:?%@",?data);??
????responseCallback(@"Response?from?testObjcCallback");??
}];??
有點亂了。剛才我們的思路都是倒推的,如果我們整過來,首先肯定是viewdidload初始化,初始化之后會把這個block加入到_messageHandlers中,之后因為js調(diào)用動態(tài)讀取這個block調(diào)用,在調(diào)用之前,我們又把定一個block付給回掉處理的responseCallback的block,這個block在handler中調(diào)用而調(diào)用,有點繞,自己可以多想想。
我們接著來看看:
[html]?view plain?copy
?print?
responseCallback?=?^(id?responseData)?{??
????????????????????if?(responseData?==?nil)?{??
????????????????????????responseData?=?[NSNull?null];??
????????????????????}??
??????????????????????
????????????????????WVJBMessage*?msg?=?@{?@"responseId":callbackId,?@"responseData":responseData?};??
????????????????????[self?_queueMessage:msg];??
????????????????};??
這個就是你繞的地方,他是后被定義的,所以一開不執(zhí)行,只有在處理數(shù)據(jù)后回調(diào)才會被調(diào)用,這里有個方法_queueMessage:
[html]?view plain?copy
?print?
-?(void)_queueMessage:(WVJBMessage*)message?{??
????if?(_startupMessageQueue)?{??
????????[_startupMessageQueue?addObject:message];??
????}?else?{??
????????[self?_dispatchMessage:message];??
????}??
}??
這里面還有個方法:
[html]?view plain?copy
?print?
-?(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文件中看到
[html]?view plain?copy
?print?
function?_handleMessageFromObjC(messageJSON)?{??
????if?(receiveMessageQueue)?{??
????????receiveMessageQueue.push(messageJSON)??
????}?else?{//肯定走這個??為什么呢???
????????_dispatchMessageFromObjC(messageJSON)??
????}??
}??
再來看看:
[html]?view plain?copy
?print?
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)??
????????????????????}??
????????????????}??
??
????????????}??
????????})??
????}??
大家還記得我們返回的對象是:
[html]?view plain?copy
?print?
@{?@"responseId":callbackId,?@"responseData":responseData?}??
所以這里messageHandlers剛才也說過了用來存方法的,callbackId被換了個名字叫responseId意思一樣,只要值沒變就行,所以就會執(zhí)行:
[html]?view plain?copy
?print?
bridge.callHandler('testObjcCallback',?{'foo':?'cccccccccccc'},?function(response)?{??
????????????????log('JS?got?response',?response)??
????????????})??
中的方法,好了,完了。
總結(jié)一下:js這邊 先把方法名字、參數(shù)、處理方法保存成一個字典在轉(zhuǎn)成json字符串,在通過UIWebview調(diào)用js中某個方法把這個json字符串傳到Native中去(不是通過url傳的,這樣太low了),同時把這個處理的方法以key-value形式放到一個js的字典中。
? ? ? ? ? ? ? ? ?UIWebView在收到這個json之后,進行數(shù)據(jù)處理、還有js的回掉的處理方法(就是那個callbackId)處理完成后也會拼成一個key-value字典通過調(diào)用js傳回去(可以直接調(diào)用js)。
? ? ? ? ? ? ? ? ?js在接到這個json后,根據(jù)responseId讀取responseCallbacks中處理方法進行處理Native code返回的數(shù)據(jù)。
3.Native調(diào)用js method
? ? 過程不是直接調(diào)用js,也是通過js調(diào)用Native過程一樣的處理方式。
? ? 大體來看一下,先看一個按鈕的單擊事件:
? ??
[html]?view plain?copy
?print?
-?(void)callHandler:(id)sender?{??
????id?data?=?@{?@"greetingFromObjC":?@"Hi?there,?JS!"?};??
????[_bridge?callHandler:@"testJavascriptHandler"?data:data?responseCallback:^(id?response)?{??
????????NSLog(@"testJavascriptHandler?responded:?%@",?response);??
????}];??
}??
看看callHandler:
[html]?view plain?copy
?print?
-?(void)callHandler:(NSString?*)handlerName?data:(id)data?responseCallback:(WVJBResponseCallback)responseCallback?{??
????[self?_sendData:data?responseCallback:responseCallback?handlerName:handlerName];??
}??
看看_sendData:
[html]?view plain?copy
?print?
-?(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];??
}??
到_queueMessage:之后流程就和上面一樣了,這里面native也有個:
[html]?view plain?copy
?print?
NSString*?responseId?=?message[@"responseId"];??
???????if?(responseId)?{??
???????????WVJBResponseCallback?responseCallback?=?_responseCallbacks[responseId];??
???????????responseCallback(message[@"responseData"]);??
???????????[_responseCallbacks?removeObjectForKey:responseId];??
???????}??
這個和js中的處理思想是一樣的。
總結(jié):native將方法名、參數(shù)、回到的id放到一個對象中傳給js。
? ? ? ? ? ?js根據(jù)方法名字調(diào)用相應(yīng)方法,之后將返回數(shù)據(jù)和responseId拼裝,最后通過src 重定向到UIWebview 的delegate。
? ? ? ? ? ?native得到數(shù)據(jù)后根據(jù)responseId調(diào)用事先裝入_responseCallbacks的block,動態(tài)讀取調(diào)用,從而完成交互。
本文轉(zhuǎn)自ljianbing51CTO博客,原文鏈接:?http://blog.51cto.com/ljianbing/1857876,如需轉(zhuǎn)載請自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的WebViewJavascriptBridge 原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 防火墙如可禁止tracert但允许pin
- 下一篇: JavaEE 资源注入