This paper analyzes the communication mechanism of Native to JS in ReactNative step by step by analyzing the source code.

This post is also published on my personal blog

Native to JS


In the article “ReactNative source code Analysis — Detailed Explanation of communication Mechanism (1/2)”, THE communication mechanism of JS to Native is gradually analyzed through RN source code, and the whole process is quite complicated. This paper also analyzes the process of Native to JS step by step by analyzing the source code. Compared with JS to Native, Native to JS is much easier.

ReactNative source code parsing — communication mechanism details (1/2)

RCTBridge
ReactNative source code parsing — communication mechanism details (1/2)
NativeToJsBridge
NativeToJsBridge
JSCExecutor
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));
}
Copy the code

In the callFunction method, first determine if the environment is ready (line 3), and if not, enter 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();
  });
}
Copy the code

Initialization bindBridge methods mainly do some preparation work: get batchedBridge from JS, object and a number of methods (m_callFunctionReturnFlushedQueueJS, etc.). The definition of __fbBatchedBridge can be found in batchedbridge.js:

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

It can be seen that batchedBridge obtained in Native is a JS object of MessageQueue type. Methods the MessageQueue callFunctionReturnFlushedQueue m_callFunctionReturnFlushedQueueJS is JS class. Back to JSCExecutor: : callFunction, on line 6 callFunctionReturnFlushedQueue method.

MessageQueue.callFunctionReturnFlushedQueue(JS)

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

    return this.flushedQueue();
  }
Copy the code

CallFunctionReturnFlushedQueue method calls the internal __callFunction method.

__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;
  }
Copy the code

__callFunction finds the Module in the JS Module registry using moduleName and calls the corresponding method.

PS: JSCExecutor: : callFunction – > MessageQueue. CallFunctionReturnFlushedQueue this interface is not going to call the JS method return values to the Native side. If you want to return to JS method return values, can call another set of interfaces: JSCExecutor: : callFunctionSyncWithValue – > MessageQueue. CallFunctionReturnResultAndFlushedQueue. But, in callFunctionSyncWithValue method statement in a note: * * * “This method is experimental, and may be modified or removed” * * *

JS Module registry

In the last section, we mentioned the JS Modulde registry (_lazyCallableModules), which requires registration for all JS modules exposed to Native.

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

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

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

The JS Module registry supports lazy loading.

Registered by registerCallableModule or registerLazyCallableModule interface. For example, the message module RCTEventEmitter registered in RCTEventEmitter. Js:

BatchedBridge.registerCallableModule('RCTEventEmitter', eventEmitter);
Copy the code

Base modules registered in initializecore.js (lazy loading) :

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'));
Copy the code

At this point, the Native to JS process is basically over. But it’s not over yet. Previously, callFunctionReturnFlushedQueue doesn’t return the adjustable JS method return values, but it does have a return value (from JS to Native) :

/ / callFunctionReturnFlushedQueue return statement
return this.flushedQueue();
Copy the code

As introduced in the analysis of JS to Native, all calls from JS to Native will be queued first for performance reasons and will only be executed if they meet certain conditions (more than 5ms from the last flush queue). At the end of all Native to JS calls, a Flush queue is triggered to flush all enqueued JS to Native calls.

summary

The communication process of Native to JS is relatively simple, which can be summarized as follows:

  • All JS modules exposed to Native need to be registered in advance;
  • Flush JS to Native Queue is triggered at the end of the Native to JS call.

conclusion


The basic analysis of the communication mechanism between Native and JS in RN has been completed, and the main conclusions are as follows:

  • RN projects involve many languages, but the communication between Native and JS takes place inC++withJavaScriptBetween;
  • The two parties are responsible for the communication respectively: NativeJSCExecutorWith JSMessageQueue;
  • Maintain a module registry exposed to JS on the Native side and a Module registry exposed to Native on the JS side;
  • Native to JS communication in RN is not usedJavaScriptCoreMechanisms provided (block,JSExport), but implemented a cross-platform communication mechanism of its own.

The resources

React Native Docs

ReactNative iOS source code parsing