JSBridge 从源代码入手<一>

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的过程.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值