Hybrid之JSBridge的实现原理(WebViewJavascriptBridge源码分析)

前言

小编之前写的 iOS WebView和JS的交互 这篇文章介绍了iOSjs交互的几种方式。其中现在最常用的是JSBridge的方式,我们在上一篇也介绍了具体的使用,本文详细介绍JSBridge(WebViewJavascriptBridge)的实现原理。android版本的JSBridge实现原理由于能力有限,不作介绍。不过可以脑补一下,其实现思路应该和iOS版本的JSBridge是一样的。下边我们正式介绍了

交互详细流程图梳理

Hybrid之JSBridge的实现原理(WebViewJavascriptBridge源码分析)_第1张图片
jsBridge交互.png

WebViewJavascriptBridge源码解读

目录和相关文件功能介绍

首先先看一下WebViewJavascriptBridge插件的目录

Hybrid之JSBridge的实现原理(WebViewJavascriptBridge源码分析)_第2张图片
QQ截图20200724150849.png
  • WebViewJavascriptBridge 在使用的时候,初始化对象做了对UIWebViewWKWebView的兼容。对外暴露外界使用的方法,以及拦截UIWebView的代理方法
  • WebViewJavascriptBridge_JS 注入js代码,主要是js和native之间相互调用的js代码
  • WebViewJavascriptBridgeBase 主要是管理js和native交互的数据,以及native和js之间相互调用的工具方法
  • WKWebViewJavascriptBridge 为WKWebView的定制的对象,主要实现拦截WKWebView的代理方法,以及暴露外界使用的方法

WebViewJavascriptBridge的初始化流程

要想了解源码,必定要先会使用,那么我们先从使用的最开始,初始化一步步了解

首先初始化native端的WebViewJavascriptBridge初始化,会执行WebViewJavascriptBridge对象的bridge方法

WebViewJavascriptBridge的初始化
+ (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;
}

这里有两个变量:

  • supportsWKWebView 判断系统是否支持WKWebView
  • WVJB_WEBVIEW_TYPE 在ios端是UIWebView

1、如果支持WKWebView,且传入的webViewWKWebView类型,使用WKWebViewJavascriptBridge对象初始化,代码如下所示

+ (instancetype)bridgeForWebView:(WKWebView*)webView {
    WKWebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _setupInstance:webView];
    [bridge reset];
    return bridge;
}
- (void) _setupInstance:(WKWebView*)webView {
    _webView = webView;
    _webView.navigationDelegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    _base.delegate = self;
}

其实WKWebViewJavascriptBridgeWebViewJavascriptBridge对象做的事情基本一样,只是两者在内部分别处理了UIWebViewWKWebView本身api的调用和实现的代理方法,暴露给外界的方法一模一样。所以这里我们就只介绍WebViewJavascriptBridge这个对象的实现,WKWebViewJavascriptBridge这个对象的实现可以自己看一下。这里就省略了。

2、 不满足1的条件,就使用WebViewJavascriptBridge对象初始化

3、设置UIWebView/WKWebView的代理,在各自对象里边实现UIWebView/WKWebView的代理方法,最后初始化WebViewJavascriptBridgeBase对象。

nativeWebViewJavascriptBridgeBase的初始化

这里初始化了messageHandlersstartupMessageQueueresponseCallbacks_uniqueId四个属性,代码如下

- (id)init {
    if (self = [super init]) {
        self.messageHandlers = [NSMutableDictionary dictionary];
        self.startupMessageQueue = [NSMutableArray array];
        self.responseCallbacks = [NSMutableDictionary dictionary];
        _uniqueId = 0;
    }
    return self;
}

messageHandlers:存放native注册的事件交互的字典对象
startupMessageQueue:存放在js注入之前,native端调用js的事件交互的队列。因为js端需要手动触发通知native端注入js代码,在这时候注入完成需要立即执行队列里的任务
responseCallbacks:存在native端调用js传入的回调函数字典对象
_uniqueId:这是一个记录自增数字,主要用来生成不同的`callBackId

这时候,native端的初始化已经完成。但是有的小伙伴存在一个疑问,js全局Bridege初始化呢?js端和native端是怎么相互调用的呢?这一切都是疑问,下边我们一步步解开她的面纱

js端初始化和js代码的注入

js端触发初始化

官方文档提示js端需要执行下边代码。那我们就从这里开始分析js代码是如何注入的。js端执行代码如下:

function iosSetupWebViewJavaScriptBridge(callback) {
  if (window.WebViewJavascriptBridge) {
    return callback(window.WebViewJavascriptBridge)
  }
  if (window.WVJBCallbacks) {
    return window.WVJBCallbacks.push(callback)
  }
  window.WVJBCallbacks = [callback]
  const WVJBIframe = document.createElement('iframe')

  WVJBIframe.style.display = 'none'
  WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
  document.documentElement.appendChild(WVJBIframe)
  setTimeout(function timeCB() {
    document.documentElement.removeChild(WVJBIframe)
  }, 0)
}
iosSetupWebViewJavaScriptBridge(function(JSBridge){
    console.log(JSBridge); // js端获取到的WebViewJavascriptBridge对象
})

我们可以看到这里js先创建了iframe,挂在到document文档。然后设置了src=wvjbscheme://__BRIDGE_LOADED__,最后把iframe移除。

native的webView拦截初始化url

在上边的创建iframe,挂载iframe,设置iframesrc属性的过程结束后,native端webView会发生重新请求,会触发nativeWebViewJavascriptBridge对象实现的UIWebView代理方法,在这一步对请求的url做拦截,判断是初始化动作还是执行事件交互的动作。如下代码

- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id)listener {
    if (webView != _webView) { return; }
    
    NSURL *url = [request URL];
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) { // 初始化会执行
            [_base injectJavascriptFile];  
        } else if ([_base isQueueMessageURL:url]) { // js端调用native端会执行
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        [listener ignore];
    } else if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:request:frame:decisionListener:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:actionInformation request:request frame:frame decisionListener:listener];
    } else {
        [listener use];
    }
}

因为这里webview获取到的urliframesrc='wvjbscheme://__BRIDGE_LOADED__',这里native端执行[_base isBridgeLoadedURL:url]返回true,然后会执行WebViewJavascriptBridgeBase对象injectJavascriptFile的实例方法,这个方法其实就是注入js代码的方法,代码如下

- (void)injectJavascriptFile {
    NSString *js = WebViewJavascriptBridge_js(); // 获取将要注入的js代码
    [self _evaluateJavascript:js];  // webview注入js代码 UIWebView和WKWebView注入的方法不一样
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}

这里主要是获取WebViewJavascriptBridge_js文件中的js代码,然后webview注入这一段js代码。那么我们来看一下注入的js代码究竟是什么东西,代码里边我做了一些注释,可以看一下,方法具体的执行内容这里先不讲解,后边会详细介绍讲解

native端WebViewJavascriptBridge_js对象(这个对象维护的是注入的js代码)
;(function() {
    if (window.WebViewJavascriptBridge) {
        return;
    }

    if (!window.onerror) {
        window.onerror = function(msg, url, line) {
            console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line);
        }
    }
    // 注入的全局对象和方法
    window.WebViewJavascriptBridge = {
        registerHandler: registerHandler,
        callHandler: callHandler,
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    };
    // iframe
    var messagingIframe;  
    // 存放js调用native的事件交互的队列,因为js端调用native的事件交互是异步的过程,
    // js可能会同时调用多个native端注册的事件交互,所以会把js端调用的事件交互放到这里,native端一起执行这些任务
    var sendMessageQueue = [];  
    // 存放js注册的事件交互对象,数据结构:{name:function(){}}
    var messageHandlers = {}; 
    
    var CUSTOM_PROTOCOL_SCHEME = 'https';
    var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__';
    
    // 存放js调用native的回调responseCallback数据,数据结构:{callBackId:responseCallback}
    var responseCallbacks = {};  
    var uniqueId = 1; // 用来生成自增的callBackId
    var dispatchMessagesWithTimeoutSafety = true;

    // js注册native端调用的事件交互的全局方法
    function registerHandler(handlerName, handler) {
        ....省略
    }
     
    // js 调用  native 端事件交互的全局方法
    function callHandler(handlerName, data, responseCallback) {
        ....省略
    }
    function disableJavscriptAlertBoxSafetyTimeout() {
        ....省略
    }
    
    function _doSend(message, responseCallback) {
        ....省略
    }
    // 提供给native端调用,目的是获取js端调用native的所有待执行事件
    function _fetchQueue() {
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        return messageQueueString;
    }

    function _dispatchMessageFromObjC(messageJSON) {
        ....省略
    }
    
    function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
    }

        // 创建iframe全局对象,设置src,隐藏iframe
    messagingIframe = document.createElement('iframe');
    messagingIframe.style.display = 'none';
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
    document.documentElement.appendChild(messagingIframe);

    registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout);
    
    // js代码注入后,执行到这里,执行js传过来的callBack,把WebViewJavascriptBridge返回出去,
    //这样外界就可以获取到全局的WebViewJavascriptBridge对象
    setTimeout(_callWVJBCallbacks, 0);
    function _callWVJBCallbacks() {
        var callbacks = window.WVJBCallbacks;
        delete window.WVJBCallbacks;
        for (var i=0; i

熟悉js的同学应该一眼就能看懂这里的逻辑

1、初始化js全局WebViewJavascriptBridge对象,给WebViewJavascriptBridge对象暴露一些方法和window下的全局对象和私有方法,
2、最后创建iframe对象,插入到document文档中
3、获取WVJBCallbacks全局对象(其实是一个回调的数组),最后把WebViewJavascriptBridge全局对象通过回调的方式传递出去。也就是在执行iosSetupWebViewJavaScriptBridge方法时候传入的回调来接收

主要的全局对象

messageHandlers 存放js端注册的事件交互messageHandlers[name] = function(data){};
sendMessageQueue 存放未执行的js端调用native的事件交互
responseCallbacks 存放js调用native传入的responseCallback回调,responseCallbacks[callBackId] = responseCallback

暴露的方法

registerHandler(handlerName, handler): 注册事件交互
callHandler(handlerName, data, responseCallback):调用native的事件交互,
_fetchQueue(): 获取js端调用native端即将执行的事件交互队列
_handleMessageFromObjC(messageJSON)native端调用js的事件交互执行的方法

到这里,native端和js端都已经完成了初始化的工作。已经弄清楚js端代码是如何注入以及注入的js代码做了什么事情。

下边我们开始讲解jsnative互相调用的流程

js调用native事件交互

js调用native事件交互,前提需要native端注册这个事件交互

native端注册事件交互流程

我们找到nativeWebViewJavascriptBridge对象的registerHandler的实例方法

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

这个方法最终是为native端的WebViewJavascriptBridgeBase对象的messageHandlers(这个值下边后边会用到比较重要)字典添加键是handlerName,值是handlerblcok(这里可以理解是回调方法)

js端调用流程

首先我们找到WebViewJavascriptBridge_js文件js代码的callHandler方法

function callHandler(handlerName, data, responseCallback) {
    if (arguments.length == 2 && typeof data == 'function') {
        responseCallback = data;
        data = null;
    }
    _doSend({ handlerName:handlerName, data:data }, responseCallback);
}

这个方法传入name,data,responseCallback三个参数,对dataresponseCallback进行了调和,然后调用私有方法_doSend,这里传入的第一个参数message都有哪些成员属性,后边会用到

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

这个方法执行(js端):
1、判断是否有responseCallback回调,如果有,生成一个js端唯一的callBackId,放入到js端responseCallbacks对象中,在message对象中添加callBackId的属性和callBackId值。这里的message对象是{handlerName,data,callBackId},callBackId可能不存在
2、然后放入js端sendMessageQueue数组中,这个数组存放的是js端调用native待执行的事件交互
3、设置iframesrc=https://__wvjb_queue_message__,这时候js端的任务已经结束。下边把执行交给native
4、nativewebView会触发请求,这里又会执行到webView拦截url的方法,判断到请求的urlhttps://__wvjb_queue_message__这个值,会执行下边`native端代码

NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];

[_base webViewJavascriptFetchQueyCommand]返回的字符串是@"WebViewJavascriptBridge._fetchQueue();",那么其实就是webview执行js端的WebViewJavascriptBridge对象的_fetchQueue方法

_fetchQueue方法如下

function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}

这个方法作用是把待执行的js端调用native事件交互的数组转化成字符串,接下来回到native端拿到这个字符串后执行WebViewJavascriptBridgeBase对象的flushMessageQueue:messageQueueString实例方法

flushMessageQueue方法

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

这个方法执行流程:
1、把需要执行的任务字符串转成OC字典
2、循环遍历每一个任务
3、执行每一个任务(每一个任务这里的数据结构{handlerName,data,callBackId}callBackId可能不存在,上边已经强调过)
4、取出每一个任务的callBackId,构造一个responseCallBack回调。如果callBackId是空,那么这回调什么也不执行;callBackId有值,构造一个@{ @"responseId":callbackId, @"responseData":responseData } 字典,然后执行这个任务_queueMessage这个方法。这一步只是构造一个回调。(这里不会走到有reponseId的逻辑)
5、从messageHandlers字典取出js端传入的handleName值对应的回调。如果存在这个回调(这个回调是native端注册),执行这个方法传入js端传入的data值,和native端构造的responseCallBack,这样就实现了js端调用native的方法。

那么这就算是一个完整的js调用native的流程了吗?显然不是,还差最后一步,就是怎么执行js在调用事件传入的第三个回调参数呢?(也就是callHandler(name,data,responseCallBack)方法第三个参数responseCallBack回调方法)

接着上边第五步,handler(message[@"data"], responseCallback);在执行这一步的时候如果传入的responseCallback回调得到执行,最终会执行[self _queueMessage:msg];(这里传入的参数数据结构@{ @"responseId":callbackId, @"responseData":responseData }

- (void)_queueMessage:(WVJBMessage*)message {
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        [self _dispatchMessage:message];
    }
}

这个时候startupMessageQueue是空,就会执行_dispatchMessage方法

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

这里把传入的字典转成字符串,然后在主线程,webview执行jsWebViewJavascriptBridge对象的_handleMessageFromObjC(message)方法,这时候执行交给js端

function _handleMessageFromObjC(messageJSON) {
        _dispatchMessageFromObjC(messageJSON);
}

最终执行_dispatchMessageFromObjC私有方法

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

注意这里的messageJSON数据结构是{responseId,responseData},这一步的reponseId就是在js在执行callHandler(name,data,responseCallBack)这一步,js端构造的唯一的callBackId
1、把messageJSON字符串转化成对象
2、取出reponseId,这里一定存在
3、从responseCallbacks中取出responseId值对应的回调函数,上边已经提到responseCallbacks这个对象就是存放callBackId对应的回调函数。
4、执行第三步取出回调,传入native端传入reponseData
5、执行结束,从responseCallbacks移除这个callBackId

到这里一个完整的js执行native事件的链路执行结束。

native调用js的事件交互

native调用jsjs需要提前注册事件

js端注册事件交互流程
function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }

js端执行上边代码,在jsmessageHandlers对象中存入键时handlerName的值,值为handler的方法

native端调用流程

native端提供的有三个方法

- (void)callHandler:(NSString*)handlerName;
- (void)callHandler:(NSString*)handlerName data:(id)data;
- (void)callHandler:(NSString*)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback;

这三个方法最后都会WebViewJavascriptBridgeBase对象的- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)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];
}

这个方法其实就是js_doSend方法的翻译版本
1、创建一个字典
2、判断responseCallback是否有值,如果有生成一个native端唯一的callBackId,放入到nativeresponseCallbacks字典中,这里的message字典数据结构是{handlerName,data,callBackId}callBackId可能不存在。
3、调用_queueMessage方法,执行message这个任务。上边也提到了,调用这个方法,会执行到_dispatchMessage方法,_dispatchMessage方法如下

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

这个方法的执行流程,最终会执行到jsWebViewJavascriptBridge对象_handleMessageFromObjC方法,然后调用_dispatchMessageFromObjC方法,但是这里执行的流程和上边有区别

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

这里首先把messageJSON转成js对象

这里的message的数据结构时{handlerName,data,callBackId},和上边讲到调用这个方法的数据结构不一样,不要混淆。

1、message.responseId这里一定是空,不会执行这个判断分支逻辑
2、执行else分支逻辑,如果message.callBackId存在,那么会构造一个js的回调函数responseCallback;在执行responseCallback方法时,会执行_doSend这个方法,传入构造的{ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData } 对象,来执行这个任务。这一步只是构造一个回调
3、从jsmessageHandlers对象取出message.handlerName值对应的方法,如果存在这个方法(这个方法是js端注册),执行这个方法,传入native传过来的data值,和js端构造的responseCallBack,这样就实现了native端调用`js的方法。

那么这就算是一个完整的native调用js的流程了吗?显然不是,还差最后一步,就是怎么执行native在调用js方法时传入的回调函数呢?(也就是- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName方法的第二个参数responseCallBack回调函数)

接着上边第三步,handler(message.data, responseCallback)在执行这一步的时候如果传入的responseCallback回调得到执行,最终会执行_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });

那么其实我们回到了js调用native的流程,这里只是执行_doSend传入的参数不一样,(_doSend方法这里不粘贴了...)

这个执行上下文是在js端:
1、把传入的参数对象{ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData }放入jssendMessageQueue数组
2、设置iframesrc=https://__wvjb_queue_message__,下边就会把执行交给native端了
3、nativewebView会触发请求,这里又会执行到webView拦截url的方法,判断到请求的urlhttps://__wvjb_queue_message__这个值,会经过native方法之间调用(上边已经指出)最后执行下边代码

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

这里会取出每一个任务messageresponseId,不存在responseId流程上边已经讲过。如果存在responseId值会从nativeresponseCallbacks取出responseId值对应的回调函数,上边已经提到responseCallbacks这个对象就是存放callBackId对应的回调函数。
1、如果存在这个回调,执行这个回调,传入js端传递过来的reponseData
2、执行结束,native端从responseCallbacks移除这个callBackId,也就是把这个回调移除

主流程这里已经完结,可以边看源码边看上边的详细交互流程图,这样更清晰易懂!有些细致末叶这里不再赘述,有需要的小伙伴可以再仔细的看源码

谢谢

你可能感兴趣的