
原生和H5的交互,需要原生webview層面的支持:
原生UIWebView直接通過(guò)
stringByEvaluatingJavaScriptFromString
WKWebview對(duì)應(yīng)evaluateJavaScript:completionHandler:執(zhí)行JS代碼webview中發(fā)出的所用網(wǎng)絡(luò)請(qǐng)求都能被Native攔截到。通過(guò)攔截自定義URL Scheme調(diào)用Native方法。
UIWebView對(duì)應(yīng)的攔截方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
WKWebview對(duì)應(yīng)的攔截方法:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
WebViewJavascriptBridge簡(jiǎn)單介紹
包含的文件:
WebViewJavascriptBridge/WKWebViewJavascriptBridge分別對(duì)應(yīng)UIWebView/WKWebView的接口
WebViewJavascriptBridge_JSJS 的實(shí)現(xiàn)
WebViewJavascriptBridgeBasebridge的核心實(shí)現(xiàn)
集成方式:
iOS 通過(guò)cocoapods集成
pod ‘WebViewJavascriptBridge’
H5中需要粘貼這段代碼:
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)
}
使用:
- iOS中如下方式初始化:
[WebViewJavascriptBridge bridgeForWebView:webView];
- 簡(jiǎn)單看一下
WebViewJavascriptBridge_JS中的方法:
WebViewJavascriptBridge_js會(huì)在執(zhí)行后創(chuàng)建一個(gè)WebViewJavascriptBridge對(duì)象,以及
var messagingIframe;
var sendMessageQueue = [];
var messageHandlers = {};
JS中注冊(cè)和call方法:
// register
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
// call handle
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
// _doSend
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;
}
Native->js:
native調(diào)JS,需要JS先注冊(cè)對(duì)應(yīng)的方法;
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
native通過(guò)callHandler:data:responseCallback,內(nèi)部是實(shí)現(xiàn)的sendData:responseCallback:handlerName:
- 封裝一個(gè)message字典,用于傳遞給JS
- 判斷是否有responseCallback,如果有就產(chǎn)生callbackId,并且保存responseCallback到responseCallbacks中
- 保存callbackId到message中,調(diào)用_queueMessage
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
//封裝一個(gè)message字典
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];
}
_queueMessage:會(huì)根據(jù)startupMessageQueue是否為nil判斷,如果不空表示webview還未加在完成,進(jìn)而保存到startupMessageQueue中,等到webview中
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
webview加載完會(huì)執(zhí)行(WKWebview)decidePolicyForNavigationAction:decisionHandler,走isBridgeLoadedURL分支,
執(zhí)行injectJavascriptFile,執(zhí)行JS的初始化代碼
// 相關(guān)宏定義
#define kOldProtocolScheme @"wvjbscheme"
#define kNewProtocolScheme @"https"
#define kQueueHasMessage @"__wvjb_queue_message__"
#define kBridgeLoaded @"__bridge_loaded__
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
// ...
}
_dispatchMessage:最終通過(guò)_evaluateJavascript執(zhí)行JS代碼
- message轉(zhuǎn)成json字符串
- 最終調(diào)用webview具體的執(zhí)行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];
});
}
}
webView中的WebViewJavascriptBridge._handleMessageFromObjC,Native調(diào)用JS的核心方法。簡(jiǎn)單看下:
- 解析Native傳來(lái)的字符串,字符串轉(zhuǎn)對(duì)象
- 如果responseId存在,為js調(diào)用Native的回調(diào),執(zhí)行并且結(jié)束流程
- callbackId不為空,則說(shuō)明Native有回調(diào),創(chuàng)建responseCallback,保存callbackId到responseCallback中
- 根據(jù)handlerName從messageHandlers中取出對(duì)應(yīng)的方法,然后執(zhí)行
- responseCallback最后通過(guò)
_doSend回傳callbackId和參數(shù)
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
// 解析messageJSON,json字符串轉(zhuǎn)對(duì)象
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
//responseId 存在,js調(diào)用Native的回調(diào),執(zhí)行并且結(jié)束
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
// callbackId是Native帶過(guò)來(lái)的,如果存在則創(chuàng)建responseCallback
if (message.callbackId) {
//responseCallback 用作回調(diào),并回傳callbackId到Native
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
// 從messageHandlers中取出對(duì)應(yīng)的方法,然后執(zhí)行
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
_doSend會(huì)通過(guò)iframe發(fā)送request到Native,Native根據(jù)callbackId取出最初保存在messageHandlers中的handle并執(zhí)行,整個(gè)過(guò)程執(zhí)行完成。
JS->native
js調(diào)用native,原生需要先注冊(cè)相應(yīng)的方法(注冊(cè)實(shí)際上是保存起來(lái))
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
js通過(guò)callHandler調(diào)用原生,進(jìn)而執(zhí)行_doSend:
- 判斷是否有回調(diào)responseCallback,如果有產(chǎn)生callbackId
- 保存responseCallback到responseCallbacks中
- 保存callbackId到sendMessageQueue隊(duì)列中
- 通過(guò)messagingIframe發(fā)起request,scheme包含wvjb_queue_message
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, 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;
}
request會(huì)被原生的decidePolicyForNavigationAction攔截(WKWebview),這次會(huì)走isQueueMessageURL為true的情況。
然后執(zhí)行WKFlushMessageQueue
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
if (webView != _webView) { return; }
NSURL *url = navigationAction.request.URL;
__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
if ([_base isWebViewJavascriptBridgeURL:url]) {
if ([_base isBridgeLoadedURL:url]) {
[_base injectJavascriptFile];
} else if ([_base isQueueMessageURL:url]) {
[self WKFlushMessageQueue];
} else {
[_base logUnkownMessage:url];
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
// ...
}
WKFlushMessageQueue會(huì)執(zhí)行一段JS代碼:”WebViewJavascriptBridge._fetchQueue()“,看一眼js中的_fetchQueue方法:
該方法返回sendMessageQueue中的內(nèi)容,并且清空隊(duì)列;
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
原生這里,evaluateJavaScript獲取到sendMessageQueue中的內(nèi)容,緊接著執(zhí)行flushMessageQueue,這是最核心的方法了
- (void)WKFlushMessageQueue {
[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
if (error != nil) {
NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
}
[_base flushMessageQueue:result];
}];
}
flushMessageQueue簡(jiǎn)化后如下:
- json轉(zhuǎn)字典,遍歷每個(gè)message對(duì)象
- 如果responseId存在,從_responseCallbacks中找出對(duì)應(yīng)的responseCallback并執(zhí)行,然后結(jié)束
- 如果responseId不存在,則message就是js callhandle原生方法。根據(jù)js傳遞來(lái)的callbackId來(lái)決定是否創(chuàng)建responseCallback,帶上callbackId。
- 根據(jù)js傳遞的handlerName從原生messageHandlers中取出相應(yīng)的方法handler
- 執(zhí)行handler,handler的具體實(shí)現(xiàn)中會(huì)執(zhí)行創(chuàng)建的responseCallback
最后,responseCallback會(huì)在JS中被執(zhí)行,JS會(huì)根據(jù)最初創(chuàng)建的callbackId,在_dispatchMessageFromObjC中完成最后的處理,整個(gè)JS->Native過(guò)程結(jié)束
- (void)flushMessageQueue:(NSString *)messageQueueString{
// ... 此處省略判斷的代碼
// json字符串轉(zhuǎn)數(shù)組
id messages = [self _deserializeMessageJSON:messageQueueString];
// 遍歷每個(gè)message,從中判斷responseId
for (WVJBMessage* message in messages) {
NSString* responseId = message[@"responseId"];
// 如果有responseId,為Native->JS 的回調(diào),執(zhí)行responseCallback后結(jié)束
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
// 如果無(wú)responseId,才是JS->Native的流程
//如果callbackId存在則需要回調(diào)JS,創(chuàng)建responseCallback,并在responseCallback中傳遞最初創(chuàng)建的callbackId,以及其他參數(shù)responseData
// 最后到_queueMessage中完成回調(diào)JS
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
};
}
// 根據(jù)message中handlerName,從Native的messageHandlers取出方法并執(zhí)行
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
// responseCallback會(huì)在具體handler中執(zhí)行
handler(message[@"data"], responseCallback);
}
}
}
最后附上源碼地址:WebViewJavascriptBridge