原生和H5的交互,需要原生webview层面的支持:
-
原生UIWebView直接通过
stringByEvaluatingJavaScriptFromString
WKWebview对应evaluateJavaScript:completionHandler:
执行JS代码 -
webview中发出的所用网络请求都能被Native拦截到。通过拦截自定义URL Scheme调用Native方法。
UIWebView对应的拦截方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
WKWebview对应的拦截方法:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
WebViewJavascriptBridge简单介绍
包含的文件:
WebViewJavascriptBridge/WKWebViewJavascriptBridge
分别对应UIWebView/WKWebView的接口
WebViewJavascriptBridge_JS
JS 的实现
WebViewJavascriptBridgeBase
bridge的核心实现
集成方式:
iOS 通过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];
- 简单看一下
WebViewJavascriptBridge_JS
中的方法:
WebViewJavascriptBridge_js会在执行后创建一个WebViewJavascriptBridge对象,以及
var messagingIframe;
var sendMessageQueue = [];
var messageHandlers = {};
JS中注册和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调JS,需要JS先注册对应的方法;
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
native通过callHandler:data:responseCallback,内部是实现的sendData:responseCallback:handlerName:
- 封装一个message字典,用于传递给JS
- 判断是否有responseCallback,如果有就产生callbackId,并且保存responseCallback到responseCallbacks中
- 保存callbackId到message中,调用_queueMessage
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
//封装一个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:
会根据startupMessageQueue是否为nil判断,如果不空表示webview还未加在完成,进而保存到startupMessageQueue中,等到webview中
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
webview加载完会执行(WKWebview)decidePolicyForNavigationAction:decisionHandler,走isBridgeLoadedURL分支,
执行injectJavascriptFile,执行JS的初始化代码
// 相关宏定义
#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:
最终通过_evaluateJavascript
执行JS代码
- message转成json字符串
- 最终调用webview具体的执行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调用JS的核心方法。简单看下:
- 解析Native传来的字符串,字符串转对象
- 如果responseId存在,为js调用Native的回调,执行并且结束流程
- callbackId不为空,则说明Native有回调,创建responseCallback,保存callbackId到responseCallback中
- 根据handlerName从messageHandlers中取出对应的方法,然后执行
- responseCallback最后通过
_doSend
回传callbackId和参数
function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}
// 解析messageJSON,json字符串转对象
function _doDispatchMessageFromObjC() {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
//responseId 存在,js调用Native的回调,执行并且结束
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
// callbackId是Native带过来的,如果存在则创建responseCallback
if (message.callbackId) {
//responseCallback 用作回调,并回传callbackId到Native
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};
}
// 从messageHandlers中取出对应的方法,然后执行
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
} else {
handler(message.data, responseCallback);
}
}
}
}
_doSend
会通过iframe发送request到Native,Native根据callbackId取出最初保存在messageHandlers中的handle并执行,整个过程执行完成。
JS->native
js调用native,原生需要先注册相应的方法(注册实际上是保存起来)
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
js通过callHandler调用原生,进而执行_doSend:
- 判断是否有回调responseCallback,如果有产生callbackId
- 保存responseCallback到responseCallbacks中
- 保存callbackId到sendMessageQueue队列中
- 通过messagingIframe发起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会被原生的decidePolicyForNavigationAction拦截(WKWebview),这次会走isQueueMessageURL为true的情况。
然后执行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会执行一段JS代码:”WebViewJavascriptBridge._fetchQueue()“,看一眼js中的_fetchQueue方法:
该方法返回sendMessageQueue中的内容,并且清空队列;
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
原生这里,evaluateJavaScript获取到sendMessageQueue中的内容,紧接着执行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简化后如下:
- json转字典,遍历每个message对象
- 如果responseId存在,从_responseCallbacks中找出对应的responseCallback并执行,然后结束
- 如果responseId不存在,则message就是js callhandle原生方法。根据js传递来的callbackId来决定是否创建responseCallback,带上callbackId。
- 根据js传递的handlerName从原生messageHandlers中取出相应的方法handler
- 执行handler,handler的具体实现中会执行创建的responseCallback
最后,responseCallback会在JS中被执行,JS会根据最初创建的callbackId,在_dispatchMessageFromObjC中完成最后的处理,整个JS->Native过程结束
- (void)flushMessageQueue:(NSString *)messageQueueString{
// ... 此处省略判断的代码
// json字符串转数组
id messages = [self _deserializeMessageJSON:messageQueueString];
// 遍历每个message,从中判断responseId
for (WVJBMessage* message in messages) {
NSString* responseId = message[@"responseId"];
// 如果有responseId,为Native->JS 的回调,执行responseCallback后结束
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
// 如果无responseId,才是JS->Native的流程
//如果callbackId存在则需要回调JS,创建responseCallback,并在responseCallback中传递最初创建的callbackId,以及其他参数responseData
// 最后到_queueMessage中完成回调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
};
}
// 根据message中handlerName,从Native的messageHandlers取出方法并执行
WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
if (!handler) {
NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
continue;
}
// responseCallback会在具体handler中执行
handler(message[@"data"], responseCallback);
}
}
}
最后附上源码地址:WebViewJavascriptBridge
网友评论