阅读 142

WebViewJavascriptBridge源码解析

最近项目中,把 APP 和 小程序 里 80% 的页面都换成了 H5,目的是快速开发,方便热更新。前端除了要保证代码高度复用并且降低耦合,还要主动和负责 Native 开发的同学沟通,定义好协议,留下文档,方便后期维护。

对于混合开发来说,最核心的是数据交互:

  • 小程序端:在不影响用户体验的情况下,直接或间接的使用现有规则进行开发。
  • Native端:使用了目前市面上成熟的 WebViewJavaScriptBridge开源库。在Android 端踩了一些坑,iOS 端没出现太大的问题。
    项目完成了,抽时间学习下 WebViewJavaScriptBridge 源码,顺便对这次 Hybrid开发做下总结。

一、 JavaScript 与 Native 交互的方式(以iOS为例)

1、JS 调用 Native 方法:

  • Native 拦截 URL 跳转
window.location.href = 'Native自定义协议';
  • 使用 WebKit,苹果官方推荐使用这种方式
window.webkit.messagehandlers.<name>.postMessage('xxx');

2、Native 调用 JS 方法

前端将JS function 暴露到全局window对象上, NativewebView 中注入 JS代码执行。

以上方法如果单独使用,都比较麻烦,而且代码难以组织,Native 拦截 URL 跳转或使用 WebKit 更多时候是用在前端单向调用 Native 方法的场景,不支持 returncallback,只能做 send 操作,做不了get 操作。

3、使用 WebViewJavaScriptBridge 开源库
iOSAndroid 对应的代码开源地址:

二、WebViewJavaScriptBridge 实现机制

WebViewJavaScriptBridge 很好的解决了 JSNative通信的问题,并且使我们能更好的组织代码,其原理也是根据以上两种方法做了进一步封装。

JSNative 需要互相调用,那么各自都需要做到两点:
1、注册好方法,供对方调用
2、调用对方已注册的方法

image.png

iOS(WKWebView)对外暴露的API:

- (void)registerHandler:(NSString*)handlerName handler:(WVJBHandler)handler;
- (void)removeHandler:(NSString*)handlerName;

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

- (void)reset;
- (void)setWebViewDelegate:(id)webViewDelegate;
- (void)disableJavscriptAlertBoxSafetyTimeout;

JavaScript 对外暴露的 API:

window.WebViewJavascriptBridge = {
  registerHandler: registerHandler,
  callHandler: callHandler,
  disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
  _fetchQueue: _fetchQueue,
  _handleMessageFromObjC: _handleMessageFromObjC
};

我们通常使用的也就是它们各自的 registerHandlercallHandler方法。

三、WebViewJavaScriptBridge 目录结构

1、WebViewJavascriptBridgeBase

用来进行 bridge 初始化和消息处理的核心类,其保存了三个很重要的属性:
responseCallbacks:用于保存 Objective-Cjavascript环境相互调用的回调模块。通过 _uniqueId加上时间戳来确定每个调用的回调。

messageHandlers:用于保存 Objective-C 环境注册的方法,key 是方法名,value是这个方法对应的回调 block

startupMessageQueue:保存类实例化过程中需要发送给 JavaScirpt 环境的消息。

2、WebViewJavascriptBridge

bridge 入口类,判断当前 WebView 的类型是 UIWebViewWKWebView,执行相应的逻辑。

3、WKWebViewJavascriptBridge

针对 WKWebView 做的一层封装,主要用来执行JS 代码,以及实现 WKWebView 的代理方法,并通过拦截 URL来通知 WebViewJavascriptBridgeBase 做相应操作。本次源码学习,也是以 WKWebViewJavascriptBridge 为主,忽略 UIWebView

4、WebViewJavascriptBridge_JS

其代码会被注入到 WebView 中,用于 JavaScript 端的 register 和 call 操作。

加下来主要针对

  • iOS 初始化 WebViewJavascriptBridge
  • JavaScript初始化 Bridge
  • JavaScript 主动调用 iOS 方法
  • iOS 主动调用 JavaScript方法
    这四个过程进行深入讲解

四、iOS 初始化 WebViewJavascriptBridge

ExampleWKWebViewController.m文件中,进行brige的初始化

self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webview];

来到 WebViewJavascriptBridge 源码目录,打开 WebViewJavascriptBridge.m 文件,找到 bridgeForWebView方法,相关代码如下:

// 类方法,用来初始化 bridge
+ (instancetype)bridgeForWebView:(id)webView {
    return [self bridge:webView];
}

+ (instancetype)bridge:(id)webView {
 // 通过 supportsWKWebView 判断是否支持 WKWebView
 // 如果支持的话,执行 WKWebViewJavascriptBridge 类方法 bridgeForWebView
#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;
}

打开 WKWebViewJavascriptBridge.m文件,找到:

+ (instancetype)bridgeForWebView:(WKWebView*)webView {
    WKWebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _setupInstance:webView];
    [bridge reset];
    return bridge;
}

实例化bridge之后,依次执行 _setupInstancereset方法:

- (void) _setupInstance:(WKWebView*)webView {
    // 使用成员变量 _webView 保存 webView
    _webView = webView;
    // 设置 navigationDelegate,很重要
    // navigationDelegate 的类型是 WKNavigationDelegate,定义了很多 WKWebView 运行过程中的代理方法
    // 后面要使用其拦截 URL 的功能
    _webView.navigationDelegate = self;
    // 实例化 WebViewJavascriptBridgeBase
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    // 设置 WebViewJavascriptBridgeBase 的 delegate,方便调用 _evaluateJavascript
    _base.delegate = self;
}
- (void)reset {
    // 调用 WebViewJavascriptBridgeBase 的 reset 方法
    [_base reset];
}
- (void)reset {
// 重置三个很重要的成员变量:startupMessageQueue 、responseCallbacks 和 _uniqueId
// 记住它们的数据类型
// responseCallbacks:用于保存 Objective-C 与javascript环境相互调用的回调模块。通过 _uniqueId加上时间戳来确定每个调用的回调。
// startupMessageQueue:保存类实例化过程中需要发送给 JavaScirpt 环境的消息。
    self.startupMessageQueue = [NSMutableArray array]; // 数组
    self.responseCallbacks = [NSMutableDictionary dictionary]; // 字典
    _uniqueId = 0;
}

因为 webviewnavigationDelegate属性指向了 WKWebViewJavascriptBridge 实例,那么 webview 就有权执行 WKWebViewJavascriptBridge实例的某些已实现的方法,如:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    // 如果不是指定的 webView 则不做任何处理
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

    // 判断是否是前端和Native协商好的协议格式
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        // 是否是 webView loaded 的url
        if ([_base isBridgeLoadedURL:url]) {
            // 注入 WebViewJavascriptBridge_JS.m 中的 JS 字符串
            // JS 就能通过全局 window 对象访问 WebViewJavascriptBridge 了
            [_base injectJavascriptFile];
          // 是否是JS主动发送的 call 消息
        } else if ([_base isQueueMessageURL:url]) {
            // 刷新 MessageQueue 队列
            [self WKFlushMessageQueue];
        } else {
            // 未知消息
            [_base logUnkownMessage:url];
        }
        // 取消执行正常的http请求流程,Native 自己处理剩余逻辑
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    // 如果不是前端和Native协商好的URL格式,说明是正常的http请求
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

从以上代码可知,Native获取JS发送的消息的方式,就是拦截URL请求。

image.png

五、JavaScript 初始化 Bridge

前端使用全局 window对象下的 WebViewJavascriptBridge,来源于 Native注入 的WebViewJavascriptBridge_JS.m中的 JS 字符串。

前端需要使用 WebViewJavascriptBridge 对象做一些初始化的工作,每次 call Native端的方法,都需要主动改变 iframesrc 属性,达到使用 url 请求的效果,然后 Native 端便可以拦截到这个请求。具体示例代码如下,可作为参考。

const u = window.navigator.userAgent;
const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
const isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);

if (isAndroid) {
  initAndroidBridge(bridge => {
    bridge.init((message, responseCallback) => {
      responseCallback();
    });
    // 如果是页面初始化完成后需要立即调用原生的方法
    // 那么需要在对应的页面先订阅『页面初始化完成的消息』
    // 然后在这里主动触发
    // 例如 Vue 的 EventBus 等。
  });
}

function initAndroidBridge(callback) {
  if (window.WebViewJavascriptBridge) {
    callback(window.WebViewJavascriptBridge);
  } else {
    document.addEventListener('WebViewJavascriptBridgeReady', () => {
      callback(window.WebViewJavascriptBridge);
    }, false);
  }
}

function initIOSBridge(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 = 'https://__bridge_loaded__';
  document.documentElement.appendChild(WVJBIframe);
  setTimeout(() => {
    document.documentElement.removeChild(WVJBIframe);
  }, 0);
}

const call = (function () {
  if (isiOS) {
    return function (name, data, callback) {
      initIOSBridge(bridge => bridge.callHandler(name, data, callback));
    }
  }
  return function (name, data, callback) {
    window.WebViewJavascriptBridge.callHandler(name, data, callback);
  }
})();

const register = (function () {
  if (isiOS) {
    return function (name, callback) {
      initIOSBridge(bridge => bridge.registerHandler(name, callback));
    }
  }
  return function (name, callback) {
    window.WebViewJavascriptBridge.registerHandler(name, callback);
  }
})();

export default {
  call,
  register
}
image.png

六、JavaScript调用iOS

JS主动调用 Native,首先Native 要注册好对应的方法,所以从 Native 使用register 注册方法开始,然后 JS再使用call调用 Native 注册好的方法。

   [_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"testObjcCallback called: %@", data);
        responseCallback(@"Response from testObjcCallback");
    }];

registerHandler 方法在 WebViewJavascriptBridge.m文件中

- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    // 给 messageHandlers 字典赋值
    // handlerName 作为键名
    // handler 拷贝作为键值
    _base.messageHandlers[handlerName] = [handler copy];
}

注册完成,前端可以调用了:

window.WebViewJavascriptBridge.callHandler(handlerName, data, callback)

在前面提到过,Native 执行[_base injectJavascriptFile]方法将 WebViewJavascriptBridge_JS 代码注入到webview 中,所以window 全局对象上就有了 WebViewJavascriptBridge,即:

window.WebViewJavascriptBridge = {
  callHandler,
  registerHandler,
  // ... 其他方法
};

所以看下 WebViewJavascriptBridge_JS.m 文件,找到 callHandler方法:

function callHandler(handlerName, data, responseCallback) {
  // 参数重载,data可传可不传
  if (arguments.length == 2 && typeof data == 'function') {
    responseCallback = data;
    data = null;
  }
  //  执行 _doSend 方法
  _doSend({ handlerName:handlerName, data:data }, responseCallback);
}

看下 _doSend 方法做了哪些事情:

function _doSend(message, responseCallback) {
  // responseCallback 也是可选的,有这样的场景:
  // 前端 call Native 方法之后,并不需要 Native 回应,也就是单向通信
  if (responseCallback) {
    // 前端 call 时,callbackId 的组成:cb前缀、uniqueId、时间戳
    var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
    // responseCallbacks 对象保存 callbackId 对应的responseCallback
    responseCallbacks[callbackId] = responseCallback;
    // 给 message 添加 callbackId属性,值为 calbackId
    message['callbackId'] = callbackId;
  }
  // 将此次 message 放到 sendMessageQueue 队列中
  sendMessageQueue.push(message);
  // Native 接收前端消息的核心:改变 iframe 的 src 属性,Native 会拦截到
  // Native 判断 scheme 是否是与前端约定好的,做出具体处理
  messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

好了,Native注册好了方法,前端也调用了,这时候,Native 会拦截到 URL 请求,看代码:

- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener {
    if (webView != _webView) { return; }
    
    NSURL *url = [request URL];
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
          // Native 拦截到了前端 call 来的消息
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        [listener ignore];
    } else if (_webViewDelegate && [_webViewDelegate respondsToSelector:@selector
      // ... 省略
    } else {
      // ... 省略
    }
}

上面的代码,之前笔记有学习过,现在主要关注Native 拦截到了前端 call来的消息,即:

// 执行 _evaluateJavascript 方法
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];

首先获取到 "WebViewJavascriptBridge._fetchQueue();"这个字符串, 通过调用
[_base webViewJavascriptFetchQueyCommand]:方法

- (NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}

然后交给 _evaluateJavascript 去处理,其实就是执行 JS 字符串代码

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

接着看下那段 JS字符串代码,在 WebViewJavascriptBridge_JS.m 文件中找到:

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

sendMessageQueue是个数组字符串,其元素格式如下:

var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
[
  {
    handlerName,
    data,
    callbackId
  }
]
// 别忘了 responseCallbacks 对象保存 callbackId 对应的responseCallback
responseCallbacks[callbackId] = responseCallback;

下面将 _fetchQueue 的执行结果 messageQueueString 作为参数传入并执行 [_base flushMessageQueue]

- (void)flushMessageQueue:(NSString *)messageQueueString{
  // 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) {
      // 数组元素的每一项 message 必须是合格的字典结构
        if (![message isKindOfClass:[WVJBMessage class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }
        [self _log:@"RCVD" json:message];
        
        NSString* responseId = message[@"responseId"];
        // 前端主动 call Native 方法,暂时没有 responseId,直接走 else 分支
        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            // responseCallback 是一个 block
            WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                // responseCallback
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }
                    
                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }
            
            // 还记得吗?Native 注册方法时,执行了:
            // _base.messageHandlers[handlerName] = [handler copy];
            // 现在要根据与前端约定好的 handlerName 取出对应的 handler
            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];
            
            if (!handler) {
              // 走到这里,说明前端调用了Native未注册的方法
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }
            // 传入对应的参数,执行 handler
            // 也就是说,Native 收到了前端 call 来的消息,要执行自己的逻辑了
            handler(message[@"data"], responseCallback);
        }
    }
}

上面代码的 handler 就是Native注册的方法:

self.bridge registerHandler:@"login" handler:^(id data, WVJBResponseCallback responseCallback) {
  NSString *response = @"this is a responsive message from native";
  // 这里的 responseCallback 就是 上面代码判断 responseId 不存在时定义的 block
  // 将需要返回给前端的参数 response 传入 
  responseCallback(response);
}];

那么 responseCallback 做了什么呢?再 copy 下代码,方便查看:

responseCallback = ^(id responseData) {
    if (responseData == nil) {
      // responseData 不存在的时候返回空
        responseData = [NSNull null];
    }
    
    // 重新组装下 msg,执行 self(base) 的 _queueMessage 方法
    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
    [self _queueMessage:msg];
};
- (void)_queueMessage:(WVJBMessage*)message {
  // WKWebview 初始化完成,执行 injectJavascriptFile 时已将startupMessageQueue 置为 nil
  // self.startupMessageQueue = nil;
    if (self.startupMessageQueue) {
        [self.startupMessageQueue addObject:message];
    } else {
        [self _dispatchMessage:message];
    }
}

下面具体看_dispatchMessage 的逻辑,别忘了 msg的结构:

WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
- (void)_dispatchMessage:(WVJBMessage*)message {
    // 将字段转为字符串
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];

    // 省略一系列的转义代码..
   
    // 使用 stringWithFormat 方法拼接字符串
    // 最后执行的js代码相当于:WebViewJavascriptBridge._handleMessageFromObjC(message)
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

WebViewJavascriptBridge_JS.m 文件中找到 _handleMessageFromObjC方法:

function _handleMessageFromObjC(messageJSON) {
  // _handleMessageFromObjC 做了个中转,实际是执行 _dispatchMessageFromObjC 方法
    _dispatchMessageFromObjC(messageJSON);
}

到这里,说明Native 正式把控制权交给 JS 了,执行 _dispatchMessageFromObjC 方法:

function _dispatchMessageFromObjC(messageJSON) {
        if (dispatchMessagesWithTimeoutSafety) {
            setTimeout(_doDispatchMessageFromObjC);
        } else {
             _doDispatchMessageFromObjC();
        }
        
        function _doDispatchMessageFromObjC() {
      // 转成 JS 熟悉的 JSON
            var message = JSON.parse(messageJSON);
            var messageHandler;
            var responseCallback;

      // 当前 messageJSON 中存在 responseId
      // 再把 msg 结构 copy 过来:
      // WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
            if (message.responseId) {
        // 还记得吗?前端 call Native 方法的时候,执行了 _doSend 方法
        // 使用responseCallbacks 对象保存 callbackId 对应的responseCallback
        // responseCallbacks[callbackId] = responseCallback;
        // 现在根据 responId ,也就是当时的 callbackId 取出 callback 执行
        // 即:Native 的逻辑走完了,要开始自己的回调了
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                    return;
        }
        // 这个 responseCallback 就是我们 call 时写的 callback,现在传入 message.responseData执行
        responseCallback(message.responseData);
        // 调用完成后,删除 callback 引用
                delete responseCallbacks[message.responseId];
            } else {
                // ... 省略当前用不到的代码...
            }
        }
    }
image.png

七、iOS主动调用JavaScript方法

Native 主动调用 JS,首先 JS 要注册好对应的方法,所以本节笔记从 JS 使用 register 注册方法开始,然后 Native 再使用 call 调用 JS 注册好的方法。

window.WebViewJavascriptBridge.registerHandler(handlerName, callback)

registerHandler 方法在 WebViewJavascriptBridge_JS.m 文件中:

function registerHandler(handlerName, handler) {
  // 与 Native 注册方法一模一样
  // 给 messageHandlers 对象赋值
  // handlerName 作为键名
  // handler 拷贝作为键值
  messageHandlers[handlerName] = handler;
}

注册完成,Native 可以调用了:

[self.bridge callHandler:@"handlerName" data:@"event from native" responseCallback:^(id responseData) {
    NSLog(@"message from JavaScript: %@", responseData);
}];

在 WKWebViewJavascriptBridge.m 中找到 callHandler 方法:

- (void)callHandler:(NSString *)handlerName {
    [self callHandler:handlerName data:nil responseCallback:nil];
}

- (void)callHandler:(NSString *)handlerName data:(id)data {
    [self callHandler:handlerName data:data responseCallback:nil];
}

- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
    [_base sendData:data responseCallback:responseCallback handlerName:handlerName];
}

作为示例,我们传入全部的三个参数:handlerName、data 和 responseCallback,也就是执行上面第三个方法:

 [_base sendData:data responseCallback:responseCallback handlerName:handlerName];

在 WebViewJavascriptBridgeBase.m 文件中找到对应的方法:

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    // 定义一个 message 字典
    NSMutableDictionary* message = [NSMutableDictionary dictionary];
    
    // 给 message 字典赋值 data
    if (data) {
        message[@"data"] = data;
    }
    
    if (responseCallback) {
        // 这个与 JS call Native 方法一样了
        // 拼接好唯一的 callbackId
        // 将 responseCallback 放入 responseCallbacks 字典中
        NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
        self.responseCallbacks[callbackId] = [responseCallback copy];
        message[@"callbackId"] = callbackId;
    }
    
    if (handlerName) {
        // message 字典添加 handlerName
        message[@"handlerName"] = handlerName;
    }

    // 将组装好的 message 传入 _queueMessage 方法并执行
    [self _queueMessage:message];
}

先记住 message 字典的结构:

NSMutableDictionary* message = {
  @"data": data, // 如果有 data 的话
  @"callbackId": @"objc_cb_唯一的uniqueId",
  @"handlerName": handlerName
};

另外在 responseCallbacks 字典中也添加了 callbackId,其值为 responseCallback 的拷贝。

self.responseCallbacks[callbackId] = [responseCallback copy];

下面具体看下 _queueMessage 方法,

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

_dispatchMessage 方法:

- (void)_dispatchMessage:(WVJBMessage*)message {
    // 将 message 字典转成对象字符串
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    // ... 省略一系列转义代码
    
    // 和 JS 调用 Native 一样,获取到可执行的 javascriptCommand
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

WebViewJavascriptBridge._handleMessageFromObjC 方法:还记得 message的结构吗?

NSMutableDictionary* message = {
  @"data": data, // 如果有 data 的话
  @"callbackId": @"objc_cb_唯一的uniqueId",
  @"handlerName": handlerName
};

message 转为字符串后传入 _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 { // 看到 message 的结构,此时依然走这个 else 分支
      if (message.callbackId) {
        var callbackResponseId = message.callbackId;
        responseCallback = function(responseData) {
          _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
        };
      }
      
      // JS 注册方法时,messageHandlers[handlerName] = handler;
      // 这里根据 handlerName 取出来用
      var handler = messageHandlers[message.handlerName];
      if (!handler) {
        console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
      } else {
        // 执行 handler
        handler(message.data, responseCallback);
      }
    }
  }
}

handler 就是 JS注册的方法的回调:

window.WebViewJavascriptBridge.registerHandler(handlerName, function handler (data, responseCallback) {
  // ... 走完 JS 逻辑之后,要回调 Native call JS 方法的第二个或第三个参数,也就是 Native 的 callback,即:
  var dataFromJs = {
    name: 'zhaoyiming',
    age: 18
  };
  responseCallback(dataFromJs);

  // - (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback {
  //  [_base sendData:data responseCallback:responseCallback handlerName:handlerName];
  // }
})

但是这里有个问题, Native能直接执行 JS 方法,JS 不能直接执行 Native 的方法。
那么这里的 responseCallback 其实就是上文 else 分支中自定义的 responseCallback

responseCallback = function(responseData) {
  // 在自定义的 responseCallback 中执行 _doSend 方法,看看做了哪些操作,来达到 JS 『间接』调用 Native 方法的
  _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
};

_doSend 方法:

根据上文执行,这里的 message 结构为:
const message = {
  handlerName,
  responseId: callbackResponseId, // 重点在这个地方,这次 responseId 有值了
  responseData
};
function _doSend(message, responseCallback) {
  // 这次 responseCallback 没有传,是 undefined,所以不走 if 语句块内的逻辑
  if (responseCallback) {
    var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
    responseCallbacks[callbackId] = responseCallback;
    message['callbackId'] = callbackId;
  }

  // 还记得 sendMessageQueue 的作用吗?上一节笔记学习 JS 调用 Native 方法时有用到
  // 它是个数组,可以存放很多的 message
  sendMessageQueue.push(message);

  // *** 重点在这里 ***
  // 前端修改 iframe 的 src 属性值,主动触发 Native 拦截(JS 调用 Native 方法的过程)
  // 然后 Native 就可以拿到 sendMessageQueue 中的数据,并解析
  messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

这时,逻辑又回到 Native 拦截 URL 请求的那个方法中了。其实最终还是 webview 执行stringByEvaluatingJavaScriptFromString 方法并传入 sendMessageQueue 对象字符串。

然后来到 flushMessageQueue方法(WebViewJavascriptBridgeBase),这个时候 responseId 有值了:

- (void)flushMessageQueue:(NSString *)messageQueueString{
    // ... 省略判断

    id messages = [self _deserializeMessageJSON:messageQueueString];
    for (WVJBMessage* message in messages) {
        // ...省略判断
        
        NSString* responseId = message[@"responseId"];
        if (responseId) {
            // 执行 sendData 方法时,已将 Native 主动调用 JS 方法的回调放入了 responseCallbacks 中
            // self.responseCallbacks[callbackId] = [responseCallback copy];
            // 现在取出来用
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            // responseCallback 执行了,JS 成功间接调用 Native 方法了
            responseCallback(message[@"responseData"]);
            // 调用成功之后,删除多余引用
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            // ... 这次走上面的 if 分支,responseId 有值
        }
    }
}

至此,Native 主动调用 JS 方法的整个逻辑就走完了。


image.png

作者:思考的快与慢

原文链接:https://www.jianshu.com/p/e338aec403e8

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐