美文网首页工具库
iOS Native和H5交互《WebViewJavascrip

iOS Native和H5交互《WebViewJavascrip

作者: 你duck不必呀 | 来源:发表于2023-02-07 15:08 被阅读0次
    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交互《WebViewJavascrip

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