iOS开发技术收集 -- 完整应用及开发技巧篇iOS 开发每天分享优质文章iOS开发技术收集 -- 理论知识及学习资料篇

WebViewJavascriptBridge 源码学习--了解

2019-12-11  本文已影响0人  忆辰念家

WebViewJavascriptBridge 应该很多开发的同事都有接触过,是一个挺好的原生与H5交互实现方案的三方开源库。其实现的原理其实挺简单的:
H5调用原生:是通过拦截加载的Url实现的。
原生调用H5:是通过执行Javascript字符串来实现的。
解决了上面两个问题,就能实现H5与原生之间的方法调用,及可实现两端的交互。
接下来就具体看一下代码是怎么实现的。

先来看一下这个库的文件


WebViewJavascriptBridge代码结构.png

就这几个问题,大致的功能已经在截图标明,接下来就是具体的分析。

H5调用原生

H5调用原生方法的话,原生这边的代码实现就是下面几行代码

        //先搞个webview
        self.uiWebView = [[UIWebView alloc] initWithFrame:self.view.bounds];
        self.uiWebView.delegate = self;
        [self.view addSubview:self.uiWebView];

        //*1、初始化 WebViewJavascriptBridge 对象
        self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.uiWebView];

        //*2、设置代理 (应为webview的delegate在 WebViewJavascriptBridge 内部会被重新赋值,因为需要 其他代理来把webview的代理给转发出去)
        [self.bridge setWebViewDelegate:self];

        //*3、注册给H5调用的方法
        //方法名:daJiangYou,将来给H5调用的方法
        //入参:data,H5传递过来的参数
        //回调:responseCallback,原生方法执行完毕之后,通知H5,同时可传递相关数据给到H5,作为返回参数。
        [self.bridge registerHandler:@"daJiangYou" handler:^(id data, WVJBResponseCallback responseCallback) {
            //打印入参
            NSLog(@"%@", data);
            
            //搞事情
            //。。。。
            
            //结束,告诉H5
            responseCallback(@"OK");
        }];

        //*4、加载H5地址
        [self.uiWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://192.168.2.92:8081/index.html"]]];

主要就是分为上面的四个步骤,接下来就分析每个步骤干了什么玩意儿。

注意点:这里一定要注意,加载H5必须在用户注册方法之后,如果先加载H5,在H5中调用原生方法时,可能因为原生还未注册而导致失败。

1、初始化 WebViewJavascriptBridge 对象。

追踪过去:会发现有三个方法会被调用。

//第一个方法
+ (instancetype)bridgeForWebView:(id)webView {
    return [self bridge:webView];
}

//第二个方法
+ (instancetype)bridge:(id)webView {
    //判断WKWebView是否可用
#if defined supportsWKWebView
    //判断是否为 WKWebView
    if ([webView isKindOfClass:[WKWebView class]]) {
        //执行 WKWebView 的初始化方法
        return (WebViewJavascriptBridge*) [WKWebViewJavascriptBridge bridgeForWebView:webView];
    }
#endif
    if ([webView isKindOfClass:[WVJB_WEBVIEW_TYPE class]]) {
         //执行 WebView 的初始化方法
        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;
}

同时存在对 WebViewJavascriptBridgeBase 的初始化:

- (id)init {
    if (self = [super init]) {
        self.messageHandlers = [NSMutableDictionary dictionary];  //原生注册的提供给H5调用的方法的存储
        self.startupMessageQueue = [NSMutableArray array]; //原生调用H5方法的消息队列的存储
        self.responseCallbacks = [NSMutableDictionary dictionary];  //原生调用H5方法的回调处理的存储
        _uniqueId = 0;  //回调处理的唯一编号
    }
    return self;
}

简单画了个示意图,希望大家可以看懂:


示意图1.png
2、设置 WebViewJavascriptBridge 代理对象。

追踪过去: 就下面这点代码

- (void)setWebViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate {
    //负责对webview代理的转发
    _webViewDelegate = webViewDelegate;
}
3、注册给H5调用的方法

追踪过去:将注册的方法进行存储。

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    //将H5提供的方法存储在字典中
    //方法名:回调(具体的事件)
    //daJiangYou:打酱油方法的实现
    _base.messageHandlers[handlerName] = [handler copy];
}
示意图2.png
4、加载H5地址

这就是加载一个普通的H5,没啥好说的,直接跳过去。

5、H5调用原生方法的准备工作(为window注入用于完成交互的对象)

h5这边需要一个JS的方法,通过这个方法来给 window 注入一个用于实现交互的对象。
代码的具体实现如下:

export function setupWebViewJavascriptBridge(callback) {
    //如果window下有 WebViewJavascriptBridge 对象,就 回调这个对象,并返回
    if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge);}
    //如果window下有 WVJBCallbacks 这个数组,就将 callback 存储起来
    if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
    //没有数组就初始化一个
    window.WVJBCallbacks = [callback];

    //弄一个看不见的元素,加载 https://__bridge_loaded__ 这个链接,用于为window注入 WebViewJavascriptBridge 对象
    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)
}

在这里可以看到H5页面加载了一个地址(https://_bridge_loaded_),这时我们就应该回到原始的页面,看看在webview的代理里干了什么。

- (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;
    //判断是否为 WebViewJavascriptBridge 加载的用于交互的url
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) { //判断是否为Bridge加载的url
            [_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:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

可以发现,这个库对URL的加载进行啦拦截处理。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
如果为其定义的特殊的地址,就进行相关操作。刚刚H5主动加载的H5地址就会满足 [_base isBridgeLoadedURL:url] 这个条件,具体代码就不看了,大家可以自己点进去瞅瞅。

因此就会执行下面的方法:

- (void)injectJavascriptFile {
    //获取要注入的JS字符串
    NSString *js = WebViewJavascriptBridge_js();
    //执行JS代码
    [self _evaluateJavascript:js];
    
    //这里为啥要这样处理呢
    //因为存在这种情况:在原生调用H5方法的时候,H5还未初始化JavascriptBridge,也就是说H5那边还没有注册相关方法,因此需要在初始化成功的时候将所有存储的消息,发送给H5(但其实初始化成功 JavascriptBridge 时,方法同样可能未注册哦,写这个有啥用呢)
    //如果 startupMessageQueue 数组里不为 nil
    if (self.startupMessageQueue) {
        //取出消息
        NSArray* queue = self.startupMessageQueue;
        //数组置空
        self.startupMessageQueue = nil;
        //所有存储的消息要发给H5(这里属于原生调用H5先跳过去)
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}
- (void) _evaluateJavascript:(NSString *)javascriptCommand {
    //由代理对象来实现(uiwebview和wkwebview 各自执行自己的相关方法)
    [self.delegate _evaluateJavascript:javascriptCommand];
}

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand {
    return [_webView stringByEvaluatingJavaScriptFromString:javascriptCommand];
}

上面的代码中有个执行JS代码的方法,这个就是JavascriptBridge这个库的核心所在了,我们来看看这个JS都干了什么。
首先我们可以发现它就是一个JS方法的字符串:

NSString * WebViewJavascriptBridge_js() {
    #define __wvjb_js_func__(x) #x
    
    // BEGIN preprocessorJSCode
    static NSString * preprocessorJSCode = @__wvjb_js_func__(
                                                             ;(function() {})();
    ); // END preprocessorJSCode

    #undef __wvjb_js_func__
    return preprocessorJSCode;
};

具体看看这个JS方法干了什么玩意儿,注释写的很清楚了,大家可以看看。

    //如果存在 WebViewJavascriptBridge 对象就直接返回
    if (window.WebViewJavascriptBridge) {
        return;
    }

    //错误处理
    if (!window.onerror) {
        window.onerror = function(msg, url, line) {
            console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
        }
    }
        
    //为window添加 WebViewJavascriptBridge 对象
    window.WebViewJavascriptBridge = {
        //对象包含的方法及属性
        registerHandler: registerHandler,  //注册方法给原生
        callHandler: callHandler,  //调用原生的方法
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,  //超时处理
        _fetchQueue: _fetchQueue,  //获取待发送消息
        _handleMessageFromObjC: _handleMessageFromObjC  //处理原生发送的消息
    };

    var messagingIframe;  //发送消息的隐藏元素
    var sendMessageQueue = [];  //H5调用原生方法的存储
    var messageHandlers = {};  //H5注册的提供给原生调用的方法的存储
    
    //规定的用于消息传递的特殊url
    var CUSTOM_PROTOCOL_SCHEME = 'https';
    var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
    
    var responseCallbacks = {};  //H5调用原生方法回调处理的存储
    var uniqueId = 1; //回调处理的唯一编号
    var dispatchMessagesWithTimeoutSafety = true;  //是否开启超时安全处理

   //先忽略各种各样的方法

    //弄一个看不见的元素,加载特定url,用于调用原生提供的方法
    messagingIframe = document.createElement('iframe');
    messagingIframe.style.display = 'none';
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    document.documentElement.appendChild(messagingIframe);
        
    //为原生注册 _disableJavascriptAlertBoxSafetyTimeout 方法,用来设置 dispatchMessagesWithTimeoutSafety 的值
    registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
    
    //初始化完成执行所有回调,将 WebViewJavascriptBridge 给到H5
    setTimeout(_callWVJBCallbacks, 0);
    function _callWVJBCallbacks() {
        var callbacks = window.WVJBCallbacks;
        delete window.WVJBCallbacks;
        for (var i=0; i<callbacks.length; i++) {
            callbacks[i](WebViewJavascriptBridge);
        }
    }

到这里所有的前期准备工作都完成啦,接下来看看H5怎么调用原生方法的吧。


示意图3.png
6、H5调用原生方法

这时候只需要在适当的时机调用下面的方法就好啦:

        //点击按钮调用这个段代码就可以喽
        //初始化 JavascriptBridge,
        setupWebViewJavascriptBridge((bridge) => {
          //调用原生方法 daJiangYou
          bridge.callHandler('daJiangYou',{"message":"我要2斤酱油"}, function responseCallback(responseData) {
            //这边原生回调信息(responseData),
            //处理自己的逻辑
          })
        });

WebViewJavascriptBridge 对象调用 callHandler 方法,我们去看看 callHandler 的都干嘛了,这里大家应该知道去哪里找 callHandler 方法的实现吧,就是原生的 WebViewJavascriptBridge_JS 文件中,因为H5获取到的 bridge 对象就是通过这个文件产生的呢。

    function callHandler(handlerName, data, responseCallback) {
        //判断参数个数
        //如果是两个参数 且 第二个参数是一个方法
        if (arguments.length == 2 && typeof data == 'function') {
            //那么 data 就是回调的方法
            responseCallback = data;
            //第二个参数置空
            data = null;
        }
        //调用发送消息的方法
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }
        
    // message:消息内容
    // responseCallback:回调处理
    function _doSend(message, responseCallback) {
        //如果存在处理回调的方法
        if (responseCallback) {
            //生成一个唯一的key
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
            //将回调方法存储起来
            responseCallbacks[callbackId] = responseCallback;
            //消息中也要带上这个key,以便原生方法完成后,通知H5
            message['callbackId'] = callbackId;
        }
        //待发送的消息存入数组
        sendMessageQueue.push(message);
        //加载一个H5地址
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    }

加载H5就会执行webview相关代理,并使 [_base isQueueMessageURL:url] 条件成立。

- (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;
    //判断是否为 WebViewJavascriptBridge 加载的用于交互的url
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) { //判断是否为Bridge加载的url
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) { //判断是否为发送消息的url
            //把H5中存储的待发送消息取出来
            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;
    }
}

通过执行一个JS的方法,将H5那边存储的数据传递到原生这边。

//获取H5消息队列,js方法
- (NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}

    //获取待发送的消息队列
    function _fetchQueue() {
        //把存储待发送的消息数组,转换为json串
        var messageQueueString = JSON.stringify(sendMessageQueue);
        //清空数组
        sendMessageQueue = [];
        //返回结果
        return messageQueueString;
    }

这个方法就是处理H5传过来的所有消息。

/// 处理H5传过来的消息
/// @param messageQueueString 消息数组对应的json串
- (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;
    }
    
    //json反序列化,获取到待处理消息的数组
    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];
        
        //原生调用H5的相关逻辑这里可以先不管,直接看 else 语句
        //判断是否为响应消息,(原生调用H5方法,H5处理完成时通知原生)
        NSString* responseId = message[@"responseId"];
        if (responseId) {
            //根据key找到对应的回调函数
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            //执行回调函数
            responseCallback(message[@"responseData"]);
            //将这个回调移除
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            //如果没有上面的东西,则说明是H5调用原生的方法
            WVJBResponseCallback responseCallback = NULL;
            //取出当前消息的唯一标识的ID
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                //原生处理完相关逻辑,会调用 responseCallback ,来告诉H5
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    //通过原生调用H5的方法,给H5发送一个响应消息
                    // responseId: 响应消息的唯一标识就是当前处理的消息的唯一标识
                    // responseData: 传过去的参数
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            //根据方法名,找到对应的 handler(方法对应的实现)
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            
            //执行当前方法
            //message[@"data"]: 参数
            //responseCallback: 回调
            handler(message[@"data"], responseCallback);
        }
    }
}

最终就会执行到我们最开始为H5注册的方法的回调中。

        [self.bridge registerHandler:@"daJiangYou" handler:^(id data, WVJBResponseCallback responseCallback) {
            //打印入参
            NSLog(@"%@", data);
            
            //搞事情
            //。。。。
            
            //结束,告诉H5
            responseCallback(@"OK");
        }];

到此为止,H5就调用到了原生的方法喽。


示意图4.png
7、H5调用原生方法回调处理

H5调用原生方法之后,在原生方法处理完相关逻辑,我们需要告诉H5或者传递相关参数过去。

通过上面的方法我们可以看到,在block的最后,会调用 responseCallback(@"OK") 这个玩意儿,这时候就会执行上面我们看到的那个block。

responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    //通过原生调用H5的方法,给H5发送一个响应消息
                    // responseId: 响应消息的唯一标识就是当前处理的消息的唯一标识
                    // responseData: 传过去的参数
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };

这里就是调用H5的方法了,给H5发送了一个响应消息,看一下方法的具体实现喽。

//给H5发送消息
- (void)_queueMessage:(WVJBMessage*)message {
    //H5为初始化 WebViewJavascriptBridge 就先存起来
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        //发送消息
        [self _dispatchMessage:message];
    }
}

- (void)_dispatchMessage:(WVJBMessage*)message {
    //对象的序列化
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    //json串的格式化
    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"];
    //调用JS方法 _handleMessageFromObjC,给H5传递数据
    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的方法里

    //处理原生传过来的消息
    function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
    }

处理原生发过来的消息。

function _dispatchMessageFromObjC(messageJSON) {
        // 如果 dispatchMessagesWithTimeoutSafety 为true,则延迟一定时间处理(这边库的实现并没有做延迟,也就是说没啥用)
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
             _doDispatchMessageFromObjC();
        }
        
        function _doDispatchMessageFromObjC() {
            //字符串转为对象
            var message = JSON.parse(messageJSON);
            
            var messageHandler;
            var responseCallback;
            
            //如果为响应消息
            if (message.responseId) {
                //取出 responseCallbacks 存储的相关方法
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                //执行 responseCallback
                responseCallback(message.responseData);
                //并将这个东西从存储的字典中移除
                delete responseCallbacks[message.responseId];
            } else {
                //不满足上面条件则,为事件消息
                
                //事件消息唯一标识不为空
                if (message.callbackId) {
                    //H5处理完相关逻辑,会调用 responseCallback ,来告诉原生
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                    };
                }
                
                //根据方法名,找到对应的 handler(方法对应的实现)
                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    //执行handler
                    handler(message.data, responseCallback);
                }
            }
        }
    }

最终就会执行到我们调用原生方法的回调中

        setupWebViewJavascriptBridge((bridge) => {
          //调用原生方法 daJiangYou
          bridge.callHandler('daJiangYou',{"message":"我要2斤酱油"}, function responseCallback(responseData) {
            //这边原生回调信息(responseData),
            //处理自己的逻辑
          })
        });
示意图5.png

到这里一个完整的H5调用原生方法的实现就完全分析完了,希望对看到文章的小伙伴有所帮助。原生调用H5方法的原理和上面分析的过程一样一样的,这里就不多写了,小伙伴们可以自己去分析一下哦。

最后上个大图:


完整示意图.png

总结一下

这个库的实现H5与原生JS交互的原理就是两段发送消息的过程。
其中有调用方法的消息,我们可以称为
事件消息:
handlerName:事件名称(方法名称)
data:事件数据(方法参数)
callbackId:事件编号(每个消息对应事件的唯一编号)

还有对方调用方法,对方方法执行完毕后,对方发过来的执行结果的消息,我们可以称为
响应消息
responseId:响应编号(响应的那个事件,这个就是对应事件的唯一编号)
responseData:响应数据(响应时传过去的相关数据)

H5给原生发消息主要的过程:
1、将消息存储在消息数组中。

function callHandler(handlerName, data, responseCallback);
function _doSend(message, responseCallback);

2、加载特定的URL。

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

3、原生拦截特定URL。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

4、调用相关JS方法,将消息数组转化为JSON串返回给原生。

- (NSString *)webViewJavascriptFetchQueyCommand;
function _fetchQueue();

5、处理发送过来的消息。

- (void)flushMessageQueue:(NSString *)messageQueueString

原生给H5发消息主要的过程:
1、将消息转换成格式化的字符串。

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName;
- (void)_queueMessage:(WVJBMessage*)message;
- (void)_dispatchMessage:(WVJBMessage*)message;

2、通过webview执行JS方法,将格式化的字符串传递过去。

- (NSString*) _evaluateJavascript:(NSString*)javascriptCommand;

3、处理发送过来的数据。

function _handleMessageFromObjC(messageJSON);
function _dispatchMessageFromObjC(messageJSON);

原生主要的存储对象
原生注册的提供给H5调用的方法的存储
messageHandlers = [NSMutableDictionary dictionary];

原生调用H5方法的消息的存储(主要用于在H5相关对象未初始化时,原生调用H5方法的存储)
startupMessageQueue = [NSMutableArray array];

原生调用H5方法的回调处理的存储
responseCallbacks = [NSMutableDictionary dictionary];

H5主要的存储对象
H5调用原生方法消息的存储
sendMessageQueue = [];

H5注册的提供给原生调用的方法的存储
messageHandlers = {};

H5调用原生方法回调处理的存储
responseCallbacks = {};

OK,就写这么多吧,完结!!!!

JS 交互的方法还有很多,有兴趣的同学可以看看我这篇文章哦。
UIWebview、WKWebView中JS交互方法总结

上一篇下一篇

猜你喜欢

热点阅读