WebViewJavascriptBridge 6.0源码解析

移动开发 来源:FlyElephant 1218℃ 0评论

WebViewJavascriptBridge现在star快9000了,最早的一篇分析属于CSDN万花筒的WebViewJavascriptBridge 原理分析,但是百度搜索的头条是博客园的一篇抄袭文章,只字未改,也不给原文链接,真是人心不古.

WebViewJavaScriptBirdge 最新版本是6.0.2,GitHub介绍说明:

An iOS/OSX bridge for sending messages between Obj-C and JavaScript in UIWebViews/WebViews

WebViewJavaScriptBirdge调用起来非常简单,JavaScript和Objective-C之间通过register和callBack的形式通信,WebViewJavaScriptBirdge作为中间层负责消息的传递.

WebViewJavaScriptBirdge.png
WebViewJavaScriptBirdge.png

WebViewJavaScriptBirdge的核心是由WebViewJavascriptBridge_JS,WebViewJavascriptBridge,WKWebViewJavascriptBridge和WebViewJavascriptBridgeBase组成.

JavaScript 调用 Objective-C 实现

JavaScript通过特定的url来注入JavaScript文件:
<pre><code>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) }</code></pre>

关于自定义协议常用宏定义:
<pre><code>`#define kOldProtocolScheme @"wvjbscheme"

define kNewProtocolScheme @"https"

define kQueueHasMessage @"wvjb_queue_message"

define kBridgeLoaded @"bridge_loaded"`</code></pre>

Load文件判断:
<pre><code>- (BOOL)isBridgeLoadedURL:(NSURL*)url { NSString* host = url.host.lowercaseString; return [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded]; }</code></pre>

WebView中代理中判断:
<pre><code>`- (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 isWebViewJavascriptBridgeURL:url]) {
    if ([_base isBridgeLoadedURL:url]) {
        [_base injectJavascriptFile];
    } else if ([_base isQueueMessageURL: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;
}

}`</code></pre>

注入文件,加载这个WebViewJavascriptBridge_js中定义的JavaScript方法:

<pre><code>- (void)injectJavascriptFile { NSString *js = WebViewJavascriptBridge_js(); [self _evaluateJavascript:js]; if (self.startupMessageQueue) { NSArray* queue = self.startupMessageQueue; self.startupMessageQueue = nil; for (id queuedMessage in queue) { [self _dispatchMessage:queuedMessage]; } } }</code></pre>

Objective-C端注册一个函数,JavaScript调用的流程:
注册代码:
<pre><code>[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) { NSLog(@"testObjcCallback called: %@", data); responseCallback(@"Response from testObjcCallback"); }];</code></pre>

JavaScript调用:
<pre><code>var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button')) callbackButton.innerHTML = 'Fire testObjcCallback' callbackButton.onclick = function(e) { e.preventDefault() log('JS calling handler "testObjcCallback"') bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) { log('JS got response', response) }) }</code></pre>

进入JS回调:
<pre><code>function callHandler(handlerName, data, responseCallback) { if (arguments.length == 2 && typeof data == 'function') { responseCallback = data; data = null; } _doSend({ handlerName:handlerName, data:data }, responseCallback); }</code></pre>

发送消息,同时将消息推入消息队列:
<pre><code>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; }</code></pre>

WebView的代理中判断是否存在新消息:
<pre><code>- (BOOL)isQueueMessageURL:(NSURL*)url { NSString* host = url.host.lowercaseString; return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage]; }</code></pre>

整个通信的核心代码,发送和转发消息:

<pre><code>`- (void)flushMessageQueue:(NSString *)messageQueueString{
if (messageQueueString == nil || messageQueueString.length == 0) {
NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
return;
}

id messages = [self _deserializeMessageJSON:messageQueueString];
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 = self.messageHandlers[message[@"handlerName"]];
        
        if (!handler) {
            NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
            continue;
        }
        
        handler(message[@"data"], responseCallback);
    }
}

}`</code></pre>

Objective-C执行注册的时候,会将注册名称,回调统一放在messageHandlers字典中,JavaScript传递数据的实际执行代码只有几行:

<pre><code>` WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];

        if (!handler) {
            NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
            continue;
        }
        
        handler(message[@"data"], responseCallback);`</code></pre>

Objective-C中调用JavaScript的回调:

<pre><code>` responseCallback = ^(id responseData) {
if (responseData == nil) {
responseData = [NSNull null];
}

                WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                [self _queueMessage:msg];
            };`</code></pre>

Objective-C 调用 JavaScript实现

Objective-C通过webView初始化WebViewJavaScriptBridge.

<pre><code>`+ (instancetype)bridgeForWebView:(id)webView {
return [self bridge:webView];
}

  • (instancetype)bridge:(id)webView {

if defined supportsWKWebView

if ([webView isKindOfClass:[WKWebView class]]) {
    return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
}

endif

if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
    WebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _platformSpecificSetup:webView];
    return bridge;
}
[NSException raise:@"BadWebViewType" format:@"Unknown web view type."];
return nil;

}`</code></pre>

JavaScript注册回调的代码:
<pre><code>bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) { log('ObjC called testJavascriptHandler with', data) var responseData = { 'Javascript Says':'Right back atcha!' } log('JS responding with', responseData) responseCallback(responseData) })</code></pre>

注册之后会在WebviewJavaScriptBridge进行绑定操作:
<pre><code>function registerHandler(handlerName, handler) { messageHandlers[handlerName] = handler; }</code></pre>

Objective-C进行回调的代码:
<pre><code>[_bridge callHandler:@"testJavascriptHandler" data:@{ @"foo":@"before ready" }];</code></pre>

WebViewJavaScriptBridge的callHandler代码:
<pre><code>- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback { [_base sendData:data responseCallback:responseCallback handlerName:handlerName]; }</code></pre>

发送消息的代码:
<pre><code>`- (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];

}`</code></pre>

消息分发:
<pre><code>`- (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];
    });
}

}`</code></pre>

注意字符串中_handleMessageFromObjC方法,OC中是可以直接JavaScript代码的.

WebViewJavascriptBridge_JS实际执行代码:
<pre><code>` function _dispatchMessageFromObjC(messageJSON) {
if (dispatchMessagesWithTimeoutSafety) {
setTimeout(_doDispatchMessageFromObjC);
} else {
_doDispatchMessageFromObjC();
}

    function _doDispatchMessageFromObjC() {
        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({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                };
            }
            
            var handler = messageHandlers[message.handlerName];
            if (!handler) {
                console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
            } else {
                handler(message.data, responseCallback);
            }
        }
    }
}

function _handleMessageFromObjC(messageJSON) {
    _dispatchMessageFromObjC(messageJSON);
}`</code></pre>

关于整体WebViewJavascriptBridge 6.0版本相互之间的通信分析结束,当前有些细节知识点没有完全发掘,有兴趣的可以自己研究一下.

参考资料:
[WebViewJavascriptBridge 原理分析]

关闭

IT问道推荐

银行贷款频频被拒?
“Dr信用牛牛”让你远离信用污点 国内首家信用健康管理平台免费为你提供信用修复方案