ReactNative源码解析——通信机制详解(2/2)

1,881 阅读4分钟

本文通过分析源码,逐步解析 ReactNative 中 Native to JS 的通信机制。

本文同时发表于我的个人博客

Native to JS


在『ReactNative源码解析——通信机制详解(1/2)』一文中通过 RN 源码逐步分析了 JS to Native 的通信机制,整个过程相当还是比较复杂。本文同样通过解析源码,逐步分析 Native to JS 的过程。 相比 JS to Native,Native to JS 简单不少。

这是我们在『ReactNative源码解析——通信机制详解(1/2)』一文中给出的 RN 关键类类图。
上图是 Native to JS 的时序图,RCTBridge 作为 RN 对外接口,Native 调用 JS 的方法自然也需要从此发出。上图中的方法调用链比较简单,不一一分析。 在『ReactNative源码解析——通信机制详解(1/2)』中,我们知道NativeToJsBridge是 Native to JS 的桥接,NativeToJsBridge做的最重要的一件事就是线程管理——使所有的 JS 调用都在指定的线程上执行。(RN中关于线程问题,后面会另开文章专门讨论。) JSCExecutor是 RN 中 JS 执行引擎,今天的分析就从此开始。 JS to Native 的流程进入JSCExecutor::callFunction

JSCExecutor::callFunction

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
    auto result = [&] {
        if (!m_callFunctionReturnResultAndFlushedQueueJS) {
            bindBridge();
        }
        return m_callFunctionReturnFlushedQueueJS->callAsFunction({
            Value(m_context, String::createExpectingAscii(m_context, moduleId)),
            Value(m_context, String::createExpectingAscii(m_context, methodId)),
            Value::fromDynamic(m_context, std::move(arguments))
        });
    }();

    callNativeModules(std::move(result));
}

callFunction方法中,首先判断环境是否准备好(第3行),若尚未准备好,则进入bindBridge

void JSCExecutor::bindBridge() throw(JSException) {
    std::call_once(m_bindFlag, [this] {
        auto global = Object::getGlobalObject(m_context);
        auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
        auto batchedBridge = batchedBridgeValue.asObject();
        m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
        m_callFunctionReturnResultAndFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue").asObject();
  });
}

bindBridge方法主要完成一些初始化准备工作:从 JS 则获取batchedBridge object 以及若干个方法(m_callFunctionReturnFlushedQueueJS等)。 在BatchedBridge.js中可以找到__fbBatchedBridge的定义:

const BatchedBridge = new MessageQueue();
Object.defineProperty(global, '__fbBatchedBridge', {
  configurable: true,
  value: BatchedBridge,
});

可以看到,在 Native 里面拿到的batchedBridge是一个MessageQueue类型的 JS object。 m_callFunctionReturnFlushedQueueJS则是 JS 类MessageQueuecallFunctionReturnFlushedQueue方法。 回到JSCExecutor::callFunction,在第6行执行了callFunctionReturnFlushedQueue方法。

MessageQueue.callFunctionReturnFlushedQueue(JS)

  callFunctionReturnFlushedQueue(module: string, method: string, args: Array<any>) {
    this.__guard(() => {
      this.__callFunction(module, method, args);
    });

    return this.flushedQueue();
  }

callFunctionReturnFlushedQueue方法调用了内部的__callFunction方法。

__callFunction(module: string, method: string, args: Array<any>) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    const moduleMethods = this._getCallableModule(module);
    const result = moduleMethods[method].apply(moduleMethods, args);
    return result;
  }

__callFunction通过 moduleName在 JS Module 注册表中找到该 module,并调用相应的方法。

PS: JSCExecutor::callFunction->MessageQueue.callFunctionReturnFlushedQueue这套接口不会将要调用的 JS 方法的返回值传给 Native 侧。 如需返回 JS 方法的返回值,可调用另外一套接口:JSCExecutor::callFunctionSyncWithValue->MessageQueue.callFunctionReturnResultAndFlushedQueue。 but,在callFunctionSyncWithValue方法的声明处有注释:***『This method is experimental, and may be modified or removed』***

JS Module 注册表

上一节,我们提到 JS Modulde 注册表(_lazyCallableModules),所有曝露给 Native 的 JS Module都需要注册。

  registerCallableModule(name: string, module: Object) {
    this._lazyCallableModules[name] = () => module;
  }

  registerLazyCallableModule(name: string, factory: void => Object) {
    let module: Object;
    let getValue: ?(void => Object) = factory;
    this._lazyCallableModules[name] = () => {
      if (getValue) {
        module = getValue();
        getValue = null;
      }
      return module;
    };
  }

  _getCallableModule(name: string) {
    return this._lazyCallableModules[name]();
  }

JS Module 注册表支持懒加载。

可通过registerCallableModuleregisterLazyCallableModule接口注册。 如在RCTEventEmitter.js中注册的消息模块RCTEventEmitter:

BatchedBridge.registerCallableModule('RCTEventEmitter', eventEmitter);

在InitializeCore.js中注册的基础模块(懒加载):

BatchedBridge.registerLazyCallableModule('Systrace', () => require('Systrace'));
BatchedBridge.registerLazyCallableModule('JSTimers', () => require('JSTimers'));
BatchedBridge.registerLazyCallableModule('HeapCapture', () => require('HeapCapture'));
BatchedBridge.registerLazyCallableModule('SamplingProfiler', () => require('SamplingProfiler'));
BatchedBridge.registerLazyCallableModule('RCTLog', () => require('RCTLog'));
BatchedBridge.registerLazyCallableModule('RCTDeviceEventEmitter', () => require('RCTDeviceEventEmitter'));
BatchedBridge.registerLazyCallableModule('RCTNativeAppEventEmitter', () => require('RCTNativeAppEventEmitter'));
BatchedBridge.registerLazyCallableModule('PerformanceLogger', () => require('PerformanceLogger'));

至此,Native to JS 的流程基本结束。 but,事情并没有结束。 前面讲到,callFunctionReturnFlushedQueue不会返回所调 JS 方法的返回值,但它确有返回值(从 JS 传给 Native):

// callFunctionReturnFlushedQueue的 return 语句
return this.flushedQueue();

在分析 JS to Native 时介绍过,出于性能考虑所有从 JS to Native 的调用都会先入队,只有满足一定的条件(离上一次 flush queue 大于5ms)才会被执行。 所有 Native to JS的调用,在其结束时都会触发一次 flush queue 的操作,即 flush 所有入队的 JS to Native 的调用。

小结

Native to JS 的通信过程相对比较简单,总结主要有两点:

  • 所有曝露给 Native 的 JS Module 都需要提前注册;
  • 在 Native to JS 调用结束时,会触发 flush JS to Native Queue 的操作。

总结


RN 中 Native 与 JS 的通信机制基本分析完成,总结主要有以下几点:

  • RN 项目中涉及多种语言,但 Native 与 JS 的通信发生在C++JavaScript间;
  • 双方具体负责通信的分别是:Native 的JSCExecutor与 JS 的MessageQueue
  • 在 Native 侧维护了一份曝露给 JS 的 module 注册表,在 JS 侧维护了一份曝露给 Native 的 module 注册表;
  • RN 中 Native to JS 的通信没有使用JavaScriptCore提供的机制(blockJSExport),而是自己实现了一套跨平台通信机制。

参考资料

React Native Docs

ReactNative iOS源码解析