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 in
C++
withJavaScript
Between; - The two parties are responsible for the communication respectively: Native
JSCExecutor
With 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 used
JavaScriptCore
Mechanisms provided (block
,JSExport
), but implemented a cross-platform communication mechanism of its own.
The resources
React Native Docs
ReactNative iOS source code parsing