美文网首页
WebViewJavascriptBridge浅析

WebViewJavascriptBridge浅析

作者: 新_1740 | 来源:发表于2018-03-27 10:07 被阅读0次

    WebViewJavascriptBridge是一个Objective-C与JavaScript进行消息互通的三方库。通过WebViewJavascriptBridge,我们可以很方便的实现OC和Javascript互调的功能。WebViewJavascriptBridge实现互调的过程也容易理解,就是在OC环境和Javascript环境各自保存一个相互调用的bridge对象,每一个调用之间都有id和callbackid来找到两个环境对应的处理。从Github上下载源码之后,可以看到核心类主要包含如下几个:

    WebViewJavascriptBridge_JS:Javascript环境的Bridge初始化和处理。负责接收OC发给Javascript的消息,并且把Javascript环境的消息发送给OC。

    WKWebViewJavascriptBridge/WebViewJavascriptBridge:主要负责OC环境的消息处理,并且把OC环境的消息发送给Javascript环境。

    WebViewJavascriptBridgeBase:主要实现了OC环境的Bridge初始化和处理。

    下面我们来详细看一下具体的实现(这里的介绍以WKWebView环境为例)。

    1.初始化

    1.1OC环境的初始化

    【结论】:所有与Javascript之间交互的信息都存储在messageHandlers和responseCallbacks中。这两个属性记录了OC环境与Javascript交互的信息。

    1.2OC环境注册方法

    注册一个OC方法给Javascript调用,并且把它的回调实现保存在messageHandlers中。具体代码如下:

    1.3Javascript初始化&注册方法

    这个我们先来看一下WebViewJavascriptBridge上的示例ExampleAPP.html:

    【说明】:调用setupWebViewJavascriptBridge函数,并且这个函数传入的参数也是一个函数。参数函数中有在Javascript环境中注册的setupWebViewJavascriptBridge的实现过程中,我们可以发现,如果不是第一次初始化,会通过window.WVJBCallbacks两个判断返回。iframe可以理解为webview中的窗口,当我们改变iframe的src属性的时候,相当于我们浏览器实现了链接的跳转。比如从www.google.com。下面这段代码的目的就是实现一个到https://__bridge_loaded__的跳转。从而达到初始化Javascript环境的bridge的作用。

    我们知道,只要webview有跳转,就会调用webview的代理方法,我们重点看下面这个代理方法。

    上面的方法中,首先通过isWebViewJavascriptBridgeURL 来判断是普通的跳转还是webViewJavascriptBridge跳转。如果是__bridge_loaded__ 表示是初始化Javascript环境的消息;如果是__WVJB_QUEUE_MESSAGE__ 表示是发送Javascript消息。我们继续看几个核心方法的实现:

    接下来调用injectJavascriptFile方法,将WebViewJavascriptBridge_JS中的方法注入到webview中并且执行,从而达到初始化Javascript环境的brige的作用。

    那么WebViewJavascriptBridge_JS到底是怎么实现的呢?

    NSString * WebViewJavascriptBridge_js() {

    #define __wvjb_js_func__(x) #x

        // BEGIN preprocessorJSCode

        static NSString * preprocessorJSCode = @__wvjb_js_func__(

        ;(function() {

            //如果已经初始化了,则返回

            if (window.WebViewJavascriptBridge) {

                return;

            }

            if (!window.onerror) {

                window.onerror = function(msg, url, line) {

                    console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);

                }

            }

            //初始化Bridge对象,OC可以通过WebViewJavascriptBridge来调用JS里面的各种方法

            window.WebViewJavascriptBridge = {

                registerHandler: registerHandler,  //JS中注册方法

                callHandler: callHandler,  //JS中调用OC中的方法

                disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,

                _fetchQueue: _fetchQueue,  //把消息转换成JSON串

                _handleMessageFromObjC: _handleMessageFromObjC  //OC调用JS的入口方法

            };

            var messagingIframe;

            //用于存储消息列表

            var sendMessageQueue = [];

            //用于存储消息

            var messageHandlers = {};

            //通过下面两个协议组合来确定是否是特定的消息,然后拦击

            var CUSTOM_PROTOCOL_SCHEME = 'https';

            var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';

            //oc调用js的回调

            var responseCallbacks = {};

            //消息对应的id

            var uniqueId = 1;

            //是否设置消息超时

            var dispatchMessagesWithTimeoutSafety = true;

            //web端注册一个消息方法

            function registerHandler(handlerName, handler) {

                messageHandlers[handlerName] = handler;

            }

            //web端调用一个OC注册的消息

            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;

            }

            //把消息从JS发送到OC,执行具体的发送操作

            function _doSend(message, responseCallback) {

                if (responseCallback) {

                    var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();

                    //存储消息的回调ID

                    responseCallbacks[callbackId] = responseCallback;

                    //把消息对应的回调ID和消息一起发送,以供消息返回以后使用

                    message['callbackId'] = callbackId;

                }

                //把消息放入消息列表

                sendMessageQueue.push(message);

                //下面这句话会出发JS对OC的调用,让webview执行跳转操作,从而可以在

                //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

                //中拦截到JS发给OC的消息

                messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;

            }

            //把消息转换成JSON字符串返回

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

                            };

                        }

                        //获取JS注册的函数

                        var handler = messageHandlers[message.handlerName];

                        if (!handler) {

                            console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);

                        } else {  //调用JS中的对应函数处理

                            handler(message.data, responseCallback);

                        }

                    }

                }

            }

            //OC调用JS的入口方法

            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);

            //注册_disableJavascriptAlertBoxSafetyTimeout方法,让OC可以关闭回调超时,默认是开启的

            registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);

            //执行_callWVJBCallbacks方法

            setTimeout(_callWVJBCallbacks, 0);

    从上面可以看到,整个类就是一个立即执行的Javascript方法。

    首先会初始化一个WebViewJavascriptBridge对象,并且这个对象是赋值给window对象,这里的window对象可以理解为webview。所以如果在OC环境中要调用js方法,就可以通过在加上具体方法来调用。

    WebViewJavascriptBridge对象中有Javascript环境注入的提供给OC调用的方法registerHandler,javascript调用OC环境方法的callHandler。

    _fetchQueue这个方法的作用就是把Javascript环境的方法序列化成JSON字符串,然后传入OC环境再转换。

    _handleMessageFromObjC就是处理OC发给Javascript环境的方法。

    在这个文件中也初始化了一个iframe实现webview的url跳转功能,从而激发webview代理方法的调用。

    上面的src就是https://__wvjb_queue_message__/。这个是javascript发送的OC的第一条消息,目的和上面OC环境的startupMessageQueue一样,就是在javascript环境初始化完成以后,把javascript要发送给OC的消息立即发送出去。

    1.4OC发消息给Javascript

    - (void)p_callJSHandler:(id)sender {

        iddata = @{@"OC调用JS方法":@"OC调用JS方法的参数" };

        [self.bridge callHandler:@"testJavascriptHandler"data:data responseCallback:^(id response) {

            NSLog(@"JS 响应数据: %@", response);

        }];

    }/**

    OC调用JS方法

    @param handlerName JS中提供的方法名称

    @param data 参数

    @param responseCallback 回调block

    */- (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];

    }//把OC消息序列化、并且转化为JS环境的格式,然后在主线程中调用_evaluateJavascript- (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];

            });

        }

    }

    打印javascriptCommand,结果如下:

    WebViewJavascriptBridge._handleMessageFromObjC('{\"callbackId\":\"objc_cb_1\",\"data\":{\"OC调用JS方法\":\"OC调用JS方法的参数\"},\"handlerName\":\"testJavascriptHandler\"}');

    实际上就是执行JS环境中的_handleMessageFromObjC方法:

    //处理从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 });

                            };

                        }

                        //获取JS注册的函数var handler = messageHandlers[message.handlerName];

                        if(!handler) {

                            console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);

                        } else{//调用JS中的对应函数处理                        handler(message.data, responseCallback);

                        }

                    }

                }

            }

            //把消息从JS发送到OC,执行具体的发送操作        function _doSend(message, responseCallback) {

                if (responseCallback) {

                    var callbackId ='cb_'+(uniqueId++)+'_'+new Date().getTime();

                    //存储消息的回调IDresponseCallbacks[callbackId] = responseCallback;

                    //把消息对应的回调ID和消息一起发送,以供消息返回以后使用message['callbackId'] = callbackId;

                }

                //把消息放入消息列表            sendMessageQueue.push(message);

                //下面这句话会出发JS对OC的调用,让webview执行跳转操作,从而可以在

                //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

                //中拦截到JS发给OC的消息messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;}

    最重要的是最后面的通过改变iframe的messagingIframe.src,只有这样才能触发webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler从而在OC中处理javascript环境触发过来的回调。

    //第一次注入JS代码if ([_base isBridgeLoadedURL:url]) {

                [_base injectJavascriptFile];

            } elseif([_base isQueueMessageURL:url]) {//处理WEB发过来的消息[selfWKFlushMessageQueue];        }else {

                [_base logUnkownMessage:url];

            }

            decisionHandler(WKNavigationActionPolicyCancel);

    这里会走[self WKFlushMessageQueue];方法:

    //把消息或者WEB回调发送到OC- (void)WKFlushMessageQueue {

        [_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {

            if(error != nil) {

                NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);

            }

            [_base flushMessageQueue:result];

        }];

    }

    最后执行flushMessageQueue,通过Javascript传过来的responseId获取对应的WVJBResponseCallback,执行这个block。到这里从OC发送消息到Javascript并且Javascript返回消息给OC的流程就完成了。

    1.5JS发消息给OC

    bridge.callHandler('testObjcHandler', {'foo':'bar'}, function(response) {

        log('JS got response', response)-->})//web端调用一个OC注册的消息function callHandler(handlerName, data, responseCallback) {

        if(arguments.length ==2&&typeofdata =='function') {

            responseCallback = data;

            data =null;

        }

        _doSend({ handlerName: handlerName, data: data }, responseCallback);

    }//JS调用OC方法,执行具体的发送操作function _doSend(message, responseCallback) {

        if (responseCallback) {

            var callbackId ='cb_'+(uniqueId++)+'_'+new Date().getTime();

            //存储消息的回调IDresponseCallbacks[callbackId] = responseCallback;

            //把消息对应的回调ID和消息一起发送,以供消息返回以后使用message['callbackId'] = callbackId;

        }

        //把消息放入消息列表    sendMessageQueue.push(message);

        //下面这句话会发起JS对OC的调用,让webview执行跳转操作,从而可以在

        //webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

        //中拦截到JS发给OC的消息messagingIframe.src = CUSTOM_PROTOCOL_SCHEME +'://'+ QUEUE_HAS_MESSAGE;

    }       

    具体执行和OC调用javascript过程一样。

    2.总结

    分别在OC环境和Javascript环境都保存一个bridge对象,里面维持着requestId、callbackId,以及每个id对应的具体实现。

    OC通过Javascript环境的window.WebViewJavascriptBridge对象来找到具体的方法,然后执行。

    Javascript通过改变iframe的src来唤起webview的代理方法webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler从而实现把Javascript消息发送给OC的功能。

    相关文章

      网友评论

          本文标题:WebViewJavascriptBridge浅析

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