美文网首页
WebViewJavascriptBridge框架阅读

WebViewJavascriptBridge框架阅读

作者: huainanzi | 来源:发表于2018-11-03 15:33 被阅读6次

    Architecture


    WKWebViewJavascriptBridgeUML(class)
    WKWebViewJavascriptBridgeUML(Timing).png

    What


    WebViewJavascriptBridge是iOS上简化H5 Hybrid方案中Native端与JS端交互通信的框架,适用于UIWebView与WKWebView。

    1.OC Call JS

    • Native端

        /**
         OC调用JS
         
         @param callHandler 方法名
         @param data 参数
         @param responseCallback 调用方法后的返回值
         */
        [_bridge callHandler:@"OCCallJS" data:@{} responseCallback:^(id responseData) {
            
        }];
    
    • JS端

        setupWebViewJavascriptBridge(function(bridge) {
            bridge.registerHandler('oc_js', function(data, responseCallback) {
                var responseData = { 'Javascript Says':'js callBack' }
                responseCallback(responseData)
            })  
    

    通过上面在js端进行方法的注册,oc端就可以通过进行直接的调用以及得到回调的结果。

    2.JS Call OC

    • OC端

       /**
        JS call OC
        
        @param animated 方法名
        @param animated js传递的参数
        @param animated OC回调
        */
    
       [_bridge registerHandler:@"js_oc" handler:^(id data, WVJBResponseCallback responseCallback) {
           responseCallback(@{@"OC says":@"OC Response"});
       }];
    
    • JS端

        setupWebViewJavascriptBridge(function(bridge) {
            var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
            callbackButton.innerHTML = 'Fire testObjcCallback'
            callbackButton.onclick = function(e) {
                bridge.callHandler('js_oc', {'foo': 'bar'}, 
            }
        })
    

    同样的通过在OC端注册相应的方法,然后在js端通过在事件方法中进行oc端方法的直接调用

    How


    WebViewJavascriptBridge是如何做到灵活的实现两端相互通信的呢,我们知道目前OC与JS交互有两种方式:

    //1.通过webView提供的原生的api可以直接调用js
    //UIWebView
     - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
    //WKWebView
    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler
    //2.通过iOS 7以后javaScript Core来进行交互
    、、、、、、、
    

    通过javaScript Core的局限性在于版本的要求是ios7及以上,还有一个就是JsCore的所有的操作都依赖与JSContext这个东西,通过webView能否获取到成为了关键。UIWebView我们可以通过在网页加载完毕之后再mianFrame上是可以获取到JSContext的,对于WKWebView由于存在于一个独立的进程中,获取其JSContext成为困难。而作者在这里采用的是第一种来实现交互。

    OC调用JS的方法有了。那么JS调用OC是怎么获取到调用时机的呢,这里作者采用了拦截URL的方式进行通信,作者这里将加载的URL分为三种:

    //1.注入js的bridge
    https://__bridge_loaded__
    //2.消息事件的查询
    https://__wvjb_queue_message__
    //3.正常的网页请求加载
    

    通过webView相应的代理的方法进行请求的拦截判断进行不同的处理。

    //UIWebView
    - (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;
    //判断是否为js注入URL或消息查询URL
        if ([_base isWebViewJavascriptBridgeURL:url]) {
            if ([_base isBridgeLoadedURL:url]) {
               //js注入URL ,注入jsBridge
                [_base injectJavascriptFile];
            } else if ([_base isQueueMessageURL:url]) {
              //消息查询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:)]) {
    //正常的网页加载URL
            return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
        } else {
            return YES;
        }
    }
    
    //WKWebView
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
        if (webView != _webView) { return; }
        NSURL *url = navigationAction.request.URL;
        __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
    
        if ([_base isWebViewJavascriptBridgeURL:url]) {
            if ([_base isBridgeLoadedURL:url]) {
                [_base injectJavascriptFile];
            } else if ([_base isQueueMessageURL:url]) {
                [self WKFlushMessageQueue];
            } else {
                [_base logUnkownMessage:url];
            }
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
        
        if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
            [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
        } else {
            decisionHandler(WKNavigationActionPolicyAllow);
        }
    }
    

    这些URL的拦截是如何合适进行启动加载拦截的,这个框架的使用是需要前端加载一些固定的生成Bridge的代码的:

    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) {
    //这里进行相应的js方法的注册和调用
        })
    

    当我们加载网页的时候就会执行上面的setupWebViewJavascriptBridge方法,在这个方法中如果有全局的WebViewJavascriptBridge实例,则将WebViewJavascriptBridge传入callback作为参数直接调用callBack,接着讲callBack存入一个全局的WVJBCallbacks数组中。如果没有WebViewJavascriptBridge这个实例,则会创建一个临时的iframe,并加载https://bridge_loaded然后立刻删除iframe。

    这个时候就会触发webView的上面加载代理方法,并判断出是js注入URL,进行jsBridge的注入。

    (function() {
        if (window.WebViewJavascriptBridge) {
            return;
        }
    
        if (!window.onerror) {
            window.onerror = function(msg, url, line) {
                console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
            }
        }
    //挂载一个全局的WebViewJavascriptBridge
        window.WebViewJavascriptBridge = {
            registerHandler: registerHandler,
            callHandler: callHandler,
            disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
            _fetchQueue: _fetchQueue,
            _handleMessageFromObjC: _handleMessageFromObjC
        };
    
        var messagingIframe;
    // 记录正在进行中的消息,便于oc端获取消息
        var sendMessageQueue = [];
    //注册的消息对象
        var messageHandlers = {};
        var CUSTOM_PROTOCOL_SCHEME = 'https';
        var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
    //记录保存要回调的函数
        var responseCallbacks = {};
        var uniqueId = 1;
        var dispatchMessagesWithTimeoutSafety = true;
    
        function registerHandler(handlerName, handler) {
            messageHandlers[handlerName] = handler;
        }
        
        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;
        }
    //消息进行发送,触发iframe的改变(核心方法)
        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;
        }
    
        function _fetchQueue() {
            var messageQueueString = JSON.stringify(sendMessageQueue);
            sendMessageQueue = [];
            return messageQueueString;
        }
    //处理来自oc端调用的消息
        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);
        }
    
        messagingIframe = document.createElement('iframe');
        messagingIframe.style.display = 'none';
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
        document.documentElement.appendChild(messagingIframe);
    
        registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
        
        setTimeout(_callWVJBCallbacks, 0);
        function _callWVJBCallbacks() {
            var callbacks = window.WVJBCallbacks;
            delete window.WVJBCallbacks;
            for (var i=0; i<callbacks.length; i++) {
                callbacks[i](WebViewJavascriptBridge);
            }
        }
    })();
    

    上面大致就是js端的bridge的过程,两端是如何实现的相互的调用的尼,先看下两端用于数据传送的格式定义:

    调用方发送的数据格式:
    var  message = {
      handleName:"xxx"
      data:object
      callbackId:"xxx"
    }
    
    接受方进行回调发送的数据格式:
    var message = {
      responseId:"xxx"(等于上面的callbackId)
      responseData:"xxx"
    }
    
    • JS call OC 的过程:
    1.封装消息,调用_doSend进行发送
    function callHandler(handlerName, data, responseCallback) {
            if (arguments.length == 2 && typeof data == 'function') {
                responseCallback = data;
                data = null;
            }
            _doSend({ handlerName:handlerName, data:data }, responseCallback);
        }
    2.存在回调则在消息中加入回调的id,并保存回调的函数,触发iframe变化,在OC端进行url拦截
        function _doSend(message, responseCallback) {
            if (responseCallback) {
                var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
                responseCallbacks[callbackId] = responseCallback;
                message['callbackId'] = callbackId;
            }
            //记录已经发送的message
            sendMessageQueue.push(message);
            messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
        }
    3.
    - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
     3.1.调用js中的WebViewJavascriptBridge._fetchQueue()获取到消息json字符串
                NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
     3.2.在OC端进行消息的处理
                [_base flushMessageQueue:messageQueueString];
    
    }
    4.
    - (void)flushMessageQueue:(NSString *)messageQueueString{
    4.1将消息转化为对象
        id messages = [self _deserializeMessageJSON:messageQueueString];
        for (WVJBMessage* message in messages) { 
            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];
                        }
     4.2.对回调的message数据格式进行包装
                        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;
                }
    4.3.对注册的方法进行相应的方法的调用
                handler(message[@"data"], responseCallback);
            }
        }
    }
    5.
    - (void)_queueMessage:(WVJBMessage*)message {
    5.1.对回调的message进行发送
            [self _dispatchMessage:message];
    }
    - (void)_dispatchMessage:(WVJBMessage*)message {
    5.2.对回调的消息进行序列化
        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]) {
    5.3.调用js的WebViewJavascriptBridge._handleMessageFromObjC方法将消息传递到js端
            [self _evaluateJavascript:javascriptCommand];
    
        } else {
            dispatch_sync(dispatch_get_main_queue(), ^{
                [self _evaluateJavascript:javascriptCommand];
            });
        }
    }
    6.
    function _dispatchMessageFromObjC(messageJSON) {
                   。。。。。。
            function _doDispatchMessageFromObjC() {
                var message = JSON.parse(messageJSON);
                var messageHandler;
                var responseCallback;
    
                if (message.responseId) {
    6.获取到回调的函数,进行回调并删除保存的回调函数,调用结束
                    responseCallback = responseCallbacks[message.responseId];
                    if (!responseCallback) {
                        return;
                    }
                    responseCallback(message.responseData);
                    delete responseCallbacks[message.responseId];
                } else {
                    。。。。。。。。
                }
            }
        }
    
    • OC Call JS的过程
    1.
    - (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 {
    1.对消息进行封装,并保存记录下回调Block
        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];
    }
    2.
    - (void)_dispatchMessage:(WVJBMessage*)message {
    2.1.将消息进行序列化
        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];
    2.2.调用js的WebViewJavascriptBridge._handleMessageFromObjC方法将消息传递到js端
        if ([[NSThread currentThread] isMainThread]) {
            [self _evaluateJavascript:javascriptCommand];
    
        } else {
            dispatch_sync(dispatch_get_main_queue(), ^{
                [self _evaluateJavascript:javascriptCommand];
            });
        }
    }
    3.
        function _dispatchMessageFromObjC(messageJSON) {
            function _doDispatchMessageFromObjC() {
                var message = JSON.parse(messageJSON);
                var messageHandler;
                var responseCallback;
    
                if (message.responseId) {
    。。。。。。
                } else {
    3.1.设置回调
                    if (message.callbackId) {
                        var callbackResponseId = message.callbackId;
                        responseCallback = function(responseData) {
    3.3.发起对回调的调用
                            _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                        };
                    }
    3.2通过函数名称进行函数调用
                    var handler = messageHandlers[message.handlerName];
                    if (!handler) {
                        console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                    } else {
                        handler(message.data, responseCallback);
                    }
                }
            }
        }
    4.
    function _doSend(message, responseCallback) {
    4.1.触发iframe的url,OC端进行拦截
            sendMessageQueue.push(message);
            messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
        }
    5.
    - (void)flushMessageQueue:(NSString *)messageQueueString{
    5.1将message的字符串转化为对象
        id messages = [self _deserializeMessageJSON:messageQueueString];
        for (WVJBMessage* message in messages) {
            NSString* responseId = message[@"responseId"];
    5.2.通过responeId获取回调block进行回调
            if (responseId) {
                WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
                responseCallback(message[@"responseData"]);
                [self.responseCallbacks removeObjectForKey:responseId];
            } else {
              
        }
    }
    

    相关文章

      网友评论

          本文标题:WebViewJavascriptBridge框架阅读

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