iOS Native和H5交互《WebViewJavascriptBridge》原理

image.png

原生和H5的交互,需要原生webview层面的支持:

  1. 原生UIWebView直接通过stringByEvaluatingJavaScriptFromString
    WKWebview对应evaluateJavaScript:completionHandler:执行JS代码

  2. webview中发出的所用网络请求都能被Native拦截到。通过拦截自定义URL Scheme调用Native方法。
    UIWebView对应的拦截方法:

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

WKWebview对应的拦截方法:

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

WebViewJavascriptBridge简单介绍

包含的文件:

WebViewJavascriptBridge/WKWebViewJavascriptBridge分别对应UIWebView/WKWebView的接口
WebViewJavascriptBridge_JSJS 的实现
WebViewJavascriptBridgeBasebridge的核心实现

集成方式:

iOS 通过cocoapods集成

pod ‘WebViewJavascriptBridge’

H5中需要粘贴这段代码:

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

使用:

  1. iOS中如下方式初始化:
[WebViewJavascriptBridge bridgeForWebView:webView];
  1. 简单看一下WebViewJavascriptBridge_JS中的方法:
    WebViewJavascriptBridge_js会在执行后创建一个WebViewJavascriptBridge对象,以及
    var messagingIframe;
    var sendMessageQueue = [];
    var messageHandlers = {};

JS中注册和call方法:

    // register
    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler;
    }
    // call handle
    function callHandler(handlerName, data, responseCallback) {
        if (arguments.length == 2 && typeof data == 'function') {
            responseCallback = data;
            data = null;
        }
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }
    // _doSend
    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;
    }
    

Native->js:

native调JS,需要JS先注册对应的方法;

  function registerHandler(handlerName, handler) {
      messageHandlers[handlerName] = handler;
  }

native通过callHandler:data:responseCallback,内部是实现的sendData:responseCallback:handlerName:

  • 封装一个message字典,用于传递给JS
  • 判断是否有responseCallback,如果有就产生callbackId,并且保存responseCallback到responseCallbacks中
  • 保存callbackId到message中,调用_queueMessage
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {

  //封装一个message字典
  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];
}

_queueMessage:会根据startupMessageQueue是否为nil判断,如果不空表示webview还未加在完成,进而保存到startupMessageQueue中,等到webview中

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

webview加载完会执行(WKWebview)decidePolicyForNavigationAction:decisionHandler,走isBridgeLoadedURL分支,
执行injectJavascriptFile,执行JS的初始化代码

// 相关宏定义
#define kOldProtocolScheme @"wvjbscheme"
#define kNewProtocolScheme @"https"
#define kQueueHasMessage   @"__wvjb_queue_message__"
#define kBridgeLoaded      @"__bridge_loaded__

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

_dispatchMessage:最终通过_evaluateJavascript执行JS代码

  • message转成json字符串
  • 最终调用webview具体的执行JS方法
- (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中的WebViewJavascriptBridge._handleMessageFromObjC,Native调用JS的核心方法。简单看下:

  • 解析Native传来的字符串,字符串转对象
  • 如果responseId存在,为js调用Native的回调,执行并且结束流程
  • callbackId不为空,则说明Native有回调,创建responseCallback,保存callbackId到responseCallback中
  • 根据handlerName从messageHandlers中取出对应的方法,然后执行
  • responseCallback最后通过_doSend回传callbackId和参数
    function _dispatchMessageFromObjC(messageJSON) {
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
             _doDispatchMessageFromObjC();
        }
        
        // 解析messageJSON,json字符串转对象
        function _doDispatchMessageFromObjC() {
            var message = JSON.parse(messageJSON);
            var messageHandler;
            var responseCallback;
            //responseId 存在,js调用Native的回调,执行并且结束
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
                }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
            } else {
            // callbackId是Native带过来的,如果存在则创建responseCallback
                if (message.callbackId) {
                
                //responseCallback 用作回调,并回传callbackId到Native
                    var callbackResponseId = message.callbackId;
                    responseCallback = function(responseData) {
                        _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                    };
                }
                
                // 从messageHandlers中取出对应的方法,然后执行
                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                    console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                    handler(message.data, responseCallback);
                }
            }
        }
    }
    

_doSend会通过iframe发送request到Native,Native根据callbackId取出最初保存在messageHandlers中的handle并执行,整个过程执行完成。

JS->native

js调用native,原生需要先注册相应的方法(注册实际上是保存起来)

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

js通过callHandler调用原生,进而执行_doSend:

  • 判断是否有回调responseCallback,如果有产生callbackId
  • 保存responseCallback到responseCallbacks中
  • 保存callbackId到sendMessageQueue队列中
  • 通过messagingIframe发起request,scheme包含wvjb_queue_message
    function callHandler(handlerName, data, responseCallback) {
        if (arguments.length == 2 && typeof data == 'function') {
            responseCallback = data;
            data = null;
        }
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
    }
    
    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;
    }
    

request会被原生的decidePolicyForNavigationAction拦截(WKWebview),这次会走isQueueMessageURL为true的情况。
然后执行WKFlushMessageQueue

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

WKFlushMessageQueue会执行一段JS代码:”WebViewJavascriptBridge._fetchQueue()“,看一眼js中的_fetchQueue方法:
该方法返回sendMessageQueue中的内容,并且清空队列;

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

原生这里,evaluateJavaScript获取到sendMessageQueue中的内容,紧接着执行flushMessageQueue,这是最核心的方法了

- (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简化后如下:

  • json转字典,遍历每个message对象
  • 如果responseId存在,从_responseCallbacks中找出对应的responseCallback并执行,然后结束
  • 如果responseId不存在,则message就是js callhandle原生方法。根据js传递来的callbackId来决定是否创建responseCallback,带上callbackId。
  • 根据js传递的handlerName从原生messageHandlers中取出相应的方法handler
  • 执行handler,handler的具体实现中会执行创建的responseCallback
    最后,responseCallback会在JS中被执行,JS会根据最初创建的callbackId,在_dispatchMessageFromObjC中完成最后的处理,整个JS->Native过程结束
- (void)flushMessageQueue:(NSString *)messageQueueString{
    // ... 此处省略判断的代码
    // json字符串转数组
    id messages = [self _deserializeMessageJSON:messageQueueString];
    // 遍历每个message,从中判断responseId
    for (WVJBMessage* message in messages) {
        NSString* responseId = message[@"responseId"];
        // 如果有responseId,为Native->JS 的回调,执行responseCallback后结束
        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            // 如果无responseId,才是JS->Native的流程
            //如果callbackId存在则需要回调JS,创建responseCallback,并在responseCallback中传递最初创建的callbackId,以及其他参数responseData
            // 最后到_queueMessage中完成回调JS
            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
                };
            }
            
            // 根据message中handlerName,从Native的messageHandlers取出方法并执行
            
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            // responseCallback会在具体handler中执行
            handler(message[@"data"], responseCallback);
        }
    }
}

最后附上源码地址:WebViewJavascriptBridge

你可能感兴趣的:(iOS Native和H5交互《WebViewJavascriptBridge》原理)