上一篇讲了加载html文件,html中的图片用OC进行下载并缓存传给html中进行显示的过程.
这一篇讲一下Native和JS之间如何进行相互调用method.
1: js调用Native并取得回调
在news.html中可以看到图片的点击方法
function onImageClick(picUrl){
connectWebViewJavascriptBridge(function(bridge) {
var allImage = document.querySelectorAll("img[esrc]");
allImage = Array.prototype.slice.call(allImage, 0);
var urls = new Array();
var index = -1;
var x = 0;
var y = 0;
var width = 0;
var height = 0;
allImage.forEach(function(image) {
var imgUrl = image.getAttribute("esrc");
var newLength = urls.push(imgUrl);
if(imgUrl == picUrl || imgUrl == decodeURIComponent(picUrl)){
index = newLength-1;
x = image.getBoundingClientRect().left;
y = image.getBoundingClientRect().top;
x = x + document.documentElement.scrollLeft;
y = y + document.documentElement.scrollTop;
width = image.width;
height = image.height;
console.log("x:"+x +";y:" + y+";width:"+image.width +";height:"+image.height);
}
});
console.log("检测到点击");
//前面的都是拿取到一些参数进行包装
//callHandler这个函数的实现是怎么样的的?
bridge.callHandler('imageDidClicked', {'index':index,'x':x,'y':y,'width':width,'height':height}, function(response) {
console.log("JS已经发出imgurl和index,同时收到回调,说明OC已经收到数据");
});
});
}
function callHandler(handlerName, data, responseCallback) {
_doSend({ handlerName:handlerName, data:data }, responseCallback)
}
function _doSend(message, responseCallback) {
//这里因为responseCallback是有值的所以
if (responseCallback) {
//这里是将时间变成ID用于保持唯一性
var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
responseCallbacks[callbackId] = responseCallback
message['callbackId'] = callbackId
//在这里将数据包装有 注: 记住这里传过去的包装数据
//handlerName:handlerName
//data:data
//callbackId:callbackId
}
//这里数组保存message这个对象
sendMessageQueue.push(message)
//发送网络一遍OC中webview的代理拦截
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
}
接下来看OC中如何进行拦截
//这个方法在上一篇已经讲过
- (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 ([_base isCorrectProcotocolScheme:url]) {
if ([_base isCorrectHost:url]) {
//这句代码将会执行WebViewJavascriptBridge._fetchQueue();js代码
//这一段js代码返回的是什么呢?
//function _fetchQueue() {
// var messageQueueString = JSON.stringify(sendMessageQueue)
//sendMessageQueue = []
//return messageQueueString
//}
//可以看到拿到的是message包装的数据
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
} else {
[_base logUnkownMessage:url];
}
return NO;
} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
} else {
return YES;
}
}
//这一段代码要着重讲解下
[_base flushMessageQueue:messageQueueString];
//这就是_base执行的方法具体实现
- (void)flushMessageQueue:(NSString *)messageQueueString{
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];
//大家还记得我们刚才传过来的数据中都有什么呢? 不记得往上面翻翻看下其中并没有responseId
NSString* responseId = message[@"responseId"];
if (responseId) {
WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
responseCallback(message[@"responseData"]);
[self.responseCallbacks removeObjectForKey:responseId];
} else {
WVJBResponseCallback responseCallback = NULL;
NSString* callbackId = message[@"callbackId"];
//是拥有callbackId 的
if (callbackId) {
//在这里定义了一个block等待被调用.
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;
//也是拥有handleName的
if (message[@"handlerName"]) {
handler = self.messageHandlers[message[@"handlerName"]];
} else {
handler = self.messageHandler;
}
//那么这里拿出来的handler是什么时候保存进去的呢? 到底是什么呢? 带着这个问题我们来看一下OC代码.
if (!handler) {
[NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];
}
handler(message[@"data"], responseCallback);
}
}
}
看一下OC代码中的
//这段代码是干什么的呢?
//通过方法名我们知道这是在注册一个方法.
[_bridge registerHandler:@"imageDidClicked" handler:^(id data, WVJBResponseCallback responseCallback) {
NSInteger index = [[data objectForKey:@"index"] integerValue];
NSInteger originX = [[data objectForKey:@"x"] floatValue];
NSInteger originY = [[data objectForKey:@"y"] floatValue] + 64;
NSInteger width = [[data objectForKey:@"width"] floatValue];
NSInteger height = [[data objectForKey:@"height"] floatValue];
UIImageView *tappedImageView = [[UIImageView alloc]init];
[tappedImageView sd_setImageWithURL:[NSURL URLWithString:imgUrls[index]]];
[tappedImageView setFrame:CGRectMake(originX, originY, width, height)];
/**** 用一个白块遮住底层****/
UIView *bgWhite = [[UIView alloc]initWithFrame:tappedImageView.frame];
[bgWhite setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:bgWhite];
/**** ****/
[self.view addSubview:tappedImageView];
KYPhotoGallery *mainPhotoGallery = [[KYPhotoGallery alloc]initWithTappedImageView:tappedImageView andImageUrls:[[NSMutableArray alloc] initWithArray:@[imgUrls[index]]] andInitialIndex:1 dismissBlock:^{
//完成后移除
[tappedImageView removeFromSuperview];
[bgWhite removeFromSuperview];
}];
mainPhotoGallery.imageViewArray = [[NSMutableArray alloc]initWithArray:@[tappedImageView]];
[self presentViewController:mainPhotoGallery animated:NO completion:nil];
}];
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
_base.messageHandlers[handlerName] = [handler copy];
}
这是实现, 我们看到以handleName为key,handler为value,保存在字典中.
那我们就知道刚才取出来的handler是什么了.
那刚才注册的回调什么时候调用呢, 我们发现作者的这个方法是点击图片放大,我们发现并不需要JS那边做任何回应,所以只是JS通知OC这边需要大图浏览时,便会调用当前注册的这个回调.
那我们想看到如何回调的.我们就自己测试一下
我们在图片点击方法中JS代码最后加入
bridge.callHandler('testObjcCallback',{'data':'my name is js'},function(response){
console.log(response);
});
这一段代码.
跟前面一样,一直走到OC代码的注册代码被调用.
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"testObjcCallback called: %@", data);
responseCallback(@"Response from testObjcCallback");
}];
我们看到这里的传参responseCallback 这个block被调用了.
那么这个block定义我们是知道的.我们看一下上面.刚才在flushMessageQueue方法中被定义的blcok会被调用.
responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}
WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
[self _queueMessage:msg];
};
这个block中将OC处理完的数据包装起来调用_queueMessage:这个方法.
这个方法我们上一章讲过,会将数据传给JS端代码.JS端会根据ID值找到回调的匿名函数,会直接调用.
总结: 这就完成了JS调用原生并将处理完成之后的数据返回回去的操作.
同时Native调用JS的原理和JS调用Native的原理差不多, 也不是直接调用,而是通过JSBridge去调用.