美文网首页
WebViewJavascriptBridge原理(个人理解)

WebViewJavascriptBridge原理(个人理解)

作者: xiaoliang1 | 来源:发表于2017-09-04 01:34 被阅读368次

    之前的工作很少跟h5交互打交道。最近公司新项目要和h5交互。oc调js 很简单。就是js调OC比较麻烦。安卓的一句话搞定。我们如果用jscontent也很简单。最后还是选择了WebViewJavascriptBridge。这个框架。这个框架github上9.3k的星星。说明还是很厉害的。

    下午,的时候请教了一下领导。帮我解释了WebViewJavascriptBridge demo 里面的html里面的js代码的意思。了解了一下js,貌似js入门还是很简单的。于是很快的理解了这个框架。好了切入正题。

    ExampleApp.html里面的js代码:

    window.onerror = function(err) {
            log('window.onerror: ' + err)
        }
    
        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)
        }
    
        setupWebViewJavascriptBridge(function(bridge) {
            var uniqueId = 1
            function log(message, data) {
                var log = document.getElementById('log')
                var el = document.createElement('div')
                el.className = 'logLine'
                el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
                if (log.children.length) { log.insertBefore(el, log.children[0]) }
                else { log.appendChild(el) }
            }
    
            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)
            })
    
            document.body.appendChild(document.createElement('br'))
    
            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)
                })
            }
        })
    
    

    function setupWebViewJavascriptBridge(callback) {.....}是定义一个函数。
    而下面直接调了这个函数:setupWebViewJavascriptBridge(.....),里面参数是一个函数,setupWebViewJavascriptBridge函数会先判断window.WebViewJavascriptBridge这个对象是否存在,有的话就直接返回了。貌似下面使用来创建window.WebViewJavascriptBridge对象的。第一次肯定是没有的。那么久往下看:

    window.WVJBCallbacks = [callback];
    

    在window对象创建一个WVJBCallbacks属性,其实后面[callback]告诉我们WVJBCallbacks是一个数组,callback就是执行setupWebViewJavascriptBridge(.....)传进来的 函数.接着创建了一个frame: WVJBIframe,又把WVJBIframe .src设置一个url:

    WVJBIframe.src = 'https://__bridge_loaded__
    

    这样操作后果就是网页会请求https://bridge_loaded这个链接。到此 HTML里面的js解释完毕。去看看网页拿这个url干什么了。

    我们注意到 这个要执行这个:

    + (instancetype)bridgeForWebView:(id)webView
    

    我们看看这个里面干什么了。

    + (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;
    }
    
    //地方代码
    - (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView {
        _webView = webView;
        _webView.policyDelegate = self;
        _base = [[WebViewJavascriptBridgeBase alloc] init];
        _base.delegate = self;
    }
    
    

    看完了解到其实吧UIwebView delegate设置为WebViewJavascriptBridge对象。也就是说刚刚https://bridge_loaded URL加载情况会被WebViewJavascriptBridge拦截。我们去看看。

    - (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;
        }
    }
    

    最后发现:

    if ([_base isBridgeLoadedURL:url]) {
                [_base injectJavascriptFile];
            }
    //其他地方代码
    #define kBridgeLoaded      @"__bridge_loaded__"
    
    - (BOOL)isBridgeLoadedURL:(NSURL*)url {
        NSString* host = url.host.lowercaseString;
        return [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];
    }
    

    这地地方会拦截刚才那个Url。
    看了injectJavascriptFile:

    - (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];
            }
        }
    }
    

    其实最最重要的是注入一个js文件;

    Snip20170904_1.png

    稍微懂点js语法得人都知道。这里面创建了window.WebViewJavascriptBridge这个对象。以及这个对象的属性等等。稍微细心的同学护法在126行他执行了一个函数_callWVJBCallbacks,紧接着线面就是这个函数的实现:

    function _callWVJBCallbacks() {
            var callbacks = window.WVJBCallbacks;
            delete window.WVJBCallbacks;
            for (var i=0; i<callbacks.length; i++) {
                callbacks[i](WebViewJavascriptBridge);
            }
        }
    

    看到了callbacks = window.WVJBCallbacks;然后下面循环执行。我们上面window.WVJBCallbacks 里面放的是setupWebViewJavascriptBridge(....)执行里面参数,这个参数是个函数。那么这个函数参数就会被这个循环执行,这个参数函数的参数就是WebViewJavascriptBridge(等同window.WebViewJavascriptBridge)。看看这个这个参数函数干了什么:

    setupWebViewJavascriptBridge(function(bridge) {
            var uniqueId = 1
            function log(message, data) {
                var log = document.getElementById('log')
                var el = document.createElement('div')
                el.className = 'logLine'
                el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
                if (log.children.length) { log.insertBefore(el, log.children[0]) }
                else { log.appendChild(el) }
            }
    
            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)
            })
    
            document.body.appendChild(document.createElement('br'))
    
            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)
                })
    

    这个函数里面我们就叫他WebViewJavascriptBridge了,不在用bridge了。因为我已经知道这个参数就是WebViewJavascriptBridge。
    WebViewJavascriptBridge调用了registerHandler,其实就是一个方法名对应一个js函数。是oc调js用的。不在具体分析的。
    重点是下面的:

    E83A996A-EAF6-4683-9B1F-F91955BEEC95.png

    我们发现阿牛单击就会让WebViewJavascriptBridge调用callHandler;

    function callHandler(handlerName, data, responseCallback) {
            if (arguments.length == 2 && typeof data == 'function') {
                responseCallback = data;
                data = null;
            }
            _doSend({ handlerName:handlerName, data:data }, responseCallback);
        }
        function disableJavscriptAlertBoxSafetyTimeout() {
            dispatchMessagesWithTimeoutSafety = false;
        }
        
        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;
        }
    

    _doSend里面又用了一个frame,并设置了一个url.这个URL就是https://wvjb_queue_message.这样又出发请求Url。又回到WebViewJavascriptBridge拦截请求了。
    最后触发这个判断:

    WX20170904-012603@2x.png

    messageQueueString里面包含了handerName和对应的blcok回调参数的值。具体看flushMessageQueue这个方法。

    相关文章

      网友评论

          本文标题:WebViewJavascriptBridge原理(个人理解)

          本文链接:https://www.haomeiwen.com/subject/skjsjxtx.html