JSBridge 源代码
github地址: https://github.com/HotWordland/WebNewsJSBridgeOC
需求
- Native调用JS
- JS调用Native
问题
- JS是否可以直接调用Native (不能)
- Native是否可以直接调用JS (可以)
那WebViewJavascriptBridge出现是否解决这个问题(这个问题就是让js可以直接调用native的method)呢?答案是否定的。没有本质解决还是用UIWebview的代理方法进行字段拦截(判断url的scheme),实现js间接调用native的method。
..
打开JSBridge
在OC中ViewController中viewDidLoad中首先会看到加载的HTML中的img标签的src属性被更改为esrc故意让图片不显示出来
### 代码块
/****** 加载html文件 ******/
NSString* htmlPath = [[NSBundle mainBundle] pathForResource:@"news" ofType:@"html"];
NSString* appHtml = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
NSURL *baseURL = [NSURL fileURLWithPath:htmlPath];
NSString *content = [appHtml stringByReplacingOccurrencesOfString:@"img src" withString:@"img esrc"];
/****** ******/
/****** 正则替换img src 成 img esrc 让网页的图片不加载出来******/
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(<img[^>]+esrc=\")(\\S+)\"" options:0 error:nil];
NSString *result = [regex stringByReplacingMatchesInString:content options:0 range:NSMakeRange(0, content.length) withTemplate:@"<img esrc=\"$2\" onClick=\"javascript:onImageClick('$2')\""];
/****** ******/
[self.webView loadHTMLString:result baseURL:baseURL];
接下来
/****** 初始化 ******/
_bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView webViewDelegate:self handler:^(id data, WVJBResponseCallback responseCallback) {
NSLog(@"ObjC received message from JS: %@", data);
/****** js端逻辑 页面加载完成捕获完src里的链接 就通知OC进行图片下载******/
[self downloadAllImagesInNative:[NSMutableArray arrayWithArray:data]];
/****** ******/
responseCallback(@"Response for message from ObjC");
}];
/****** ******/
这段代码就一个方法,初始化bridge
初始化的时候会设置webView 并将webView的delegate设置进去,
- (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(id<UIWebViewDelegate>)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle{
_webView = webView;
_webView.delegate = self;
_webViewDelegate = webViewDelegate;
_base = [[WebViewJavascriptBridgeBase alloc] initWithHandler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle];
_base.delegate = self;
}
在这个WebViewJavascriptBridge.m中设置完代理在代理方法里面
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if (webView != _webView) { return; }
_numRequestsLoading--;
if (_numRequestsLoading == 0 && ![[webView stringByEvaluatingJavaScriptFromString:[_base webViewJavascriptCheckCommand]] isEqualToString:@"true"]) {
//加载WebViewJavaScriptBridge.js.txt文件
[_base injectJavascriptFile:YES];
}
[_base dispatchStartUpMessageQueue];
__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
if (strongDelegate && [strongDelegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
[strongDelegate webViewDidFinishLoad:webView];
}
}
解析
/*
//这个判断确定了在当前加载的html中WebViewJavascriptBridge是否构成了对象
-(NSString *)webViewJavascriptCheckCommand {
return @"typeof WebViewJavascriptBridge == \'object\';";
}
在js中
function connectWebViewJavascriptBridge(callback) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else { document.addEventListener('WebViewJavascriptBridgeReady', function() {
callback(WebViewJavascriptBridge)}, false)
}
}
来注册方法监听事件
如果说没有构成对象那么就加载WebViewJavaScriptBridge.js.txt文件
在WebViewJavaScriptBridge.js.txt中因为是自运行方法
在文件中这两个方法就是创建了WebViewJavascriptBridge这个对象并发送window监听的WebViewJavascriptBridgeReady事件
在html中的js代码中window监听的事件回调
window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_fetchQueue: _fetchQueue,
_handleMessageFromObjC: _handleMessageFromObjC
}
var doc = document
_createQueueReadyIframe(doc)
var readyEvent = doc.createEvent('Events')
readyEvent.initEvent('WebViewJavascriptBridgeReady')
readyEvent.bridge = WebViewJavascriptBridge
doc.dispatchEvent(readyEvent)
解析初始化方法中的这个block回调
在html中有这么一段js代码
function onLoaded() {
$('img').addClass('img-responsive')
connectWebViewJavascriptBridge(function(bridge) {
var allImage = document.querySelectorAll("img");
allImage = Array.prototype.slice.call(allImage, 0);
var imageUrlsArray = new Array();
allImage.forEach(function(image) {
var esrc = image.getAttribute("esrc");
if(esrc){
var newLength = imageUrlsArray.push(esrc);
}
});
bridge.send(imageUrlsArray);
});
}
其中bridge.send(imageUrlArray);
我们看一下send这个函数的实现
function send(data, responseCallback) {
_doSend({ 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
}
逐行分析一下,变量callbackId是个字符串,responseCallBacks[] 一看就知道是个字典 ,这个字典把回掉(我们猜测)的方法responseCallback给保存起来,这Key(也就是callbackId)应该是唯一的,通过计数和时间应该知道这个字符串应该是唯一的,message也是一个字典,这是给message添加了一个新的key-value。干嘛呢?我也不知道,我们来看看sendMessageQueue是什么,大家一个push就知道应该是个数组。他吧一个字典放到一个消息队列中(数组队列),让后产生一个src(url scheme)。
有两个变量我们看看:
var CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme'
var QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__'
干嘛用,肯定是给webview 的 delegate判断用的,你感觉呢?(肯定是)
messagingIframe这个东西是什么? 看看WebViewJavaScriptBridge.js.txt这个文件的上面有这么一段代码:
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一产生就会出现什么,uiwebview代理回掉截获,此时我们把目光回到UIWebview的Native下面:
- (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]) {
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 isCorrectProcotocolScheme:url]和[_base isCorrectHost:url]
#define kCustomProtocolScheme @"wvjbscheme"
#define kQueueHasMessage @"__WVJB_QUEUE_MESSAGE__"
-(BOOL)isCorrectProcotocolScheme:(NSURL*)url {
if([[url scheme] isEqualToString:kCustomProtocolScheme]){
return YES;
} else {
return NO;
}
}
-(BOOL)isCorrectHost:(NSURL*)url {
if([[url host] isEqualToString:kQueueHasMessage]){
return YES;
} else {
return NO;
}
}
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
这一句代码里面执行了
-(NSString *)webViewJavascriptFetchQueyCommand {
return @"WebViewJavascriptBridge._fetchQueue();";
}
这么一段js代码,然后扭过头来看_fetchQueue()函数
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue)
sendMessageQueue = []
return messageQueueString
}
其实就是取出发送的消息以字符串的形式发送给Native
拿到消息之后执行了
[_base flushMessageQueue:messageQueueString];
- (void)flushMessageQueue:(NSString *)messageQueueString{
//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];
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"];
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 = self.messageHandlers[message[@"handlerName"]];
} else {
handler = self.messageHandler;
}
if (!handler) {
[NSException raise:@"WVJBNoHandlerException" format:@"No handler for message from JS: %@", message];
}
handler(message[@"data"], responseCallback);
}
}
}
在上面的_doSend函数中可以看到包装的数据中是不存在callbackId这个东西,所以这边的回调什么也没有做.
这个时候定义responseCallback回调,但不运行.
执行的handler到底是哪里添加进self.messageHandlers的或者self.messageHandler是否存在?
那我们看一下
还记得当时初始化bridge的时候执行过程吗?
-(id)initWithHandler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle
{
self = [super init];
_resourceBundle = bundle;
self.messageHandler = messageHandler;
self.messageHandlers = [NSMutableDictionary dictionary];
self.startupMessageQueue = [NSMutableArray array];
self.responseCallbacks = [NSMutableDictionary dictionary];
_uniqueId = 0;
return(self);
}
这段代码中将传入的messageHandler 复制给self.messageHandler,self.messageHandlers = [NSMutableDictionary dictionary];初始化为空字典,
那么我们在viewdidLoad初始化的时候定义的Block会在会在这里调用,传入的responseCallback这个回调会在初始化定义的Block中进行调用.因为什么也没有做所以也没什么好说的.
但是可以看出这一段是在让js告诉OC图片加载的网络URL和让Native这边去下载图片.
那图片是怎么回去的呢?
我们来看一下初始化中定义的Block中下载图片的这个method
-(void)downloadAllImagesInNative:(NSMutableArray *)imageUrls{
imgUrls = imageUrls;
SDWebImageManager *manager = [SDWebImageManager sharedManager];
//初始化一个置空元素数组
if (!allImagesOfThisArticle) {
allImagesOfThisArticle = [[NSMutableArray alloc]init];//本地的一个用于保存所有图片的数组
}
for (NSUInteger i = 0; i < imageUrls.count; i++) {
NSString *_url = imageUrls[i];
[manager downloadImageWithURL:[NSURL URLWithString:_url] options:SDWebImageHighPriority progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
[allImagesOfThisArticle addObject:image];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *imgB64 = [UIImageJPEGRepresentation(image, 1.0) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
//把图片在磁盘中的地址传回给JS
NSString *key = [manager cacheKeyForURL:imageURL];
NSString *source = [NSString stringWithFormat:@"data:image/png;base64,%@", imgB64];
[_bridge callHandler:@"imagesDownloadComplete" data:@[key,source]];
});
}
}];
}
}
其中这个method
[_bridge callHandler:@"imagesDownloadComplete" data:@[key,source]];
会调运method到
- (void)callHandler:(NSString *)handlerName {
[self callHandler:handlerName data:nil responseCallback:nil];
}
- (void)callHandler:(NSString *)handlerName data:(id)data {
[self callHandler:handlerName data:data responseCallback:nil];
}
- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
[_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}
- (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];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
}
if (handlerName) {
message[@"handlerName"] = handlerName;
}
[self _queueMessage:message];
}
可以看到message字典内容为
message[@"handlerName"] = imagesDownloadComplete
message[@"data"] = @[key,source];//这里面保存着图片磁盘存储地址
这句代码的调用实质是执行
[self _queueMessage:message];
- (void)_queueMessage:(WVJBMessage*)message {
if (self.startupMessageQueue) {
[self.startupMessageQueue addObject:message];
} else {
[self _dispatchMessage:message];
}
}
- (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]) {
[self _evaluateJavascript:javascriptCommand];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self _evaluateJavascript:javascriptCommand];
});
}
}
这段代码将图片地址将被包装执行js语句
[NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
回到WebViewJavascriptBridge这个js文件
function _handleMessageFromObjC(messageJSON) {
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON)
} else {
_dispatchMessageFromObjC(messageJSON)
}
}
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)
}
}
}
})
}
可以看出最后的回调是handler = messageHandlers[message.handlerName]
那么这个messageHandlers字典中取出的是什么回调呢?
我们看看html中的一段代码:
bridge.registerHandler('imagesDownloadComplete', function(data, responseCallback) {
console.log(data);
var responseData = { 'Javascript Says':'imagesDownloadComplete' }
var allImage = document.querySelectorAll("img");
allImage = Array.prototype.slice.call(allImage, 0);
var pOldUrl = data[0];
var pNewUrl = data[1];
allImage.forEach(function(image) {
if (image.getAttribute("esrc") == pOldUrl || image.getAttribute("esrc") == decodeURIComponent(pOldUrl)) {
console.log("寻找到了esrc")
image.src = pNewUrl;
}else console.log("没有寻找到esrc");
});
responseCallback(responseData)
})
bridge.registerHandler这个函数的实现:
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler
}
那么这个回调就不言而喻了
这就完成了从js调用Native 到Native调用js的过程.