About the author

Guo Xiaoxing, programmer and guitarist, is mainly engaged in the infrastructure of Android platform. Welcome to exchange technical questions. You can go to my Github to raise an issue or send an email to [email protected] to communicate with me.

More articles: github.com/guoxiaoxing…

This series of articles mainly analyze the source code of ReactNative, and analyze the startup process, rendering principle, communication mechanism and thread model of ReactNative.

  • 1ReactNative source code: source code
  • 2ReactNative source code: code call
  • 3ReactNative source code: startup process
  • 4ReactNative source code: rendering principle
  • 5ReactNative source code: thread model
  • 6ReactNative source code: communication mechanism

Communication refers to the communication between Java and JS in RN, that is, how JSX code in JS is converted into real Views and events in the Java layer, and how the JavaFile layer calls JS to find the views and events it needs.

In the previous article ReactNative source code article: In the startup process, we know that RN applications create JavaScriptModuleRegistry and NativeModuleRegistry at startup time. Communication between the Java layer and JS layer in RN is done through these two tables, so let’s look at them in detail.

Before we begin our analysis of communication mechanisms in earnest, let’s understand some important concepts related to this article.

Before we formally introduce communication mechanisms, let’s look at some of the core concepts.

JavaScript Module registry

Speaking of JavaScript Module registry, we need to understand first 3 class/interface: JavaScriptModule, JavaScriptModuleRegistration, JavaScriptModuleRegistry.

JavaScriptModule

JavaScriptModule: JavaScriptModuleRegistry implements the methods defined by this interface. The javascript Module uses dynamic proxies to generate the interface's proxy class, which is passed to the JS layer via C++. Then call the methods of the JS layer.Copy the code

JavaScriptModuleRegistration

JavaScriptModuleRegistration used to describe JavaScriptModule relevant information, it USES reflection to gather in the interface definition Method.Copy the code

JavaScriptModuleRegistry

JavaScriptModuleRegistry: JS Module registry that internally maintains a HashMap: HashMap<Class<? Extends JavaScriptModule >, JavaScriptModuleRegistration > mModuleRegistrations, JavaScriptModuleRegistry uses dynamic proxies to generate the proxy class corresponding to the interface JavaScriptModule, which is then passed to the JS layer through C++ to invoke the methods of the JS layer.Copy the code

Java Module registry

To understand the Java Module registry, we also need to understand three classes/interfaces: NativeModule, ModuleHolder, and NativeModuleRegistry.

NativeModule

NativeModule: Is an interface that implements this interface can be JS layer calls, when we provide Java API JS layers usually inherited BaseJavaModule/ReactContextBaseJavaModule, these two classes are implemented NativeModule interface.Copy the code

ModuleHolder

ModuleHolder: A Holder class for NativeModule that enables lazy loading of NativeModule.Copy the code

NativeModuleRegistry

NativeModuleRegistry: Java Module registry that holds Map: Map<Class<? Extends NativeModule>, ModuleHolder> mModules, NativeModuleRegistry traverses and returns Java Modules for the caller to use.Copy the code

Ok, so with these important concepts in mind, let’s start analyzing the communication mechanisms of the entire RN.

Implementation of a configuration table

1.1 Definition of a Configuration table

The function configuration table is defined in nativemodule.h.

struct MethodDescriptor {
  std::string name;
  // type is one of js MessageQueue.MethodTypes
  std::string type;

  MethodDescriptor(std::string n, std::string t)
      : name(std::move(n))
      , type(std::move(t)) {}
};Copy the code

The definition of a method

public class JavaModuleWrapper {
  public class MethodDescriptor {
    @DoNotStrip
    Method method;
    @DoNotStrip
    String signature;
    @DoNotStrip
    String name;
    @DoNotStripString type; }}Copy the code

1.2 Creating a Configuration table

In the article ReactNative source text: In the startup process, we know that the JavaScriptModuleRegistry and NativeModuleRegistry tables are created when the ReactInstanceManager executes createReactContext(). Let’s track how they were created and where they went once they were created.

JavaScriptModuleRegistry is held by the CatalystInstanceImpl instance after it is created, which can be used when Java calls JS. The Java layer now holds a table of JS modules through which it can later call JS. And NativeModuleRegistry CatalystInstanceImpl. InitializeBridge () method is introduced into c + + layer, finally can be got by JS layers.

public class CatalystInstanceImpl(
  private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModuleRegistry jsModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {... initializeBridge(new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      mJavaRegistry.getJavaModules(this),JavaModuleWrapper>. JavaModuleWrapper is a Wrapper class from NativeHolder that corresponds to the C++ layer JavaModuleWrapper.
      mJavaRegistry.getCxxModules());ModuleHolder is a Holder class of NativeModule that implements lazy loading of NativeModule.
    FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge"); mMainExecutorToken = getMainExecutorToken(); })Copy the code

Here we notice two collections passed into C++

MJavaRegistry. GetJavaModules (this) : We pass in Collection<JavaModuleWrapper>. JavaModuleWrapper is a Wrapper class from NativeHolder that corresponds to the C++ layer JavaModuleWrapper. JS will eventually call this class's inovke() method in Java. MJavaRegistry. GetCxxModules ()) : the incoming is Collection < ModuleHolder >, ModuleHolder NativeModule is a Holder class, can realize NativeModule lazy loading.Copy the code

We continue to track where the two collections go once they reach the C++ layer.

CatalystInstanceImpl.cpp

void CatalystInstanceImpl::initializeBridge(
    jni::alias_ref<ReactCallback::javaobject> callback,
    // This executor is actually a factory holder.
    JavaScriptExecutorHolder* jseh,
    jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
    jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {


  instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback),
                              jseh->getExecutorFactory(),
                              folly::make_unique<JMessageQueueThread>(jsQueue),
                              folly::make_unique<JMessageQueueThread>(moduleQueue),
                              buildModuleRegistry(std::weak_ptr<Instance>(instance_),
                                                  javaModules, cxxModules));
}Copy the code

These two collections in CatalystInstanceImpl: : initializeBridge () be packaged into ModuleRegistry was introduced into the Instance. The CPP. And, as shown below:

ModuleRegistryBuilder.cpp

std: :unique_ptr<ModuleRegistry> buildModuleRegistry(
    std::weak_ptr<Instance> winstance,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject> javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject> cxxModules) {

  std: :vector<std: :unique_ptr<NativeModule>> modules;
  for (const auto& jm : *javaModules) {
    modules.emplace_back(folly::make_unique<JavaNativeModule>(winstance, jm));
  }
  for (const auto& cm : *cxxModules) {
    modules.emplace_back(
      folly::make_unique<CxxNativeModule>(winstance, cm->getName(), cm->getProvider()));
  }
  if (modules.empty()) {
    return nullptr;
  } else {
    return folly::make_unique<ModuleRegistry>(std::move(modules)); }}Copy the code

Packaged good ModuleRegistry through Instance: : initializeBridge () passed to NativeToJsBridge. The CPP, and in the constructor of NativeToJsBridge to JsToNativeBridge, In the future, JS calls to Java can be made using ModuleRegistry.

The realization of communication bridge

The communication mechanism of the entire RN can be summed up in one sentence:

JNI acts as a bridge between C++ and Java, JSC acts as a bridge between C++ and JavaScript, and C++ ultimately connects Java and JavaScript.

RN application communication bridge structure diagram is as follows:

2.1 About the implementation of communication bridge in Java layer

From the article ReactNative source code chapter: Startup process we learned in

Initialize the communication bridge in the CatalystInstanceImpl constructor:

public class CatalystInstanceImpl implements CatalystInstance {

private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModuleRegistry jsModuleRegistry,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {...// Call the initializeBridge() method and create the BridgeCallback instance to initialize the Bridge.
    initializeBridge(
      new BridgeCallback(this),
      jsExecutor,
      mReactQueueConfiguration.getJSQueueThread(),
      mReactQueueConfiguration.getNativeModulesQueueThread(),
      mJavaRegistry.getJavaModules(this),
      mJavaRegistry.getCxxModules());
    FLog.w(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
    mMainExecutorToken = getMainExecutorToken();
  }

  // initialize the ReactBridge at the C++ layer
  private native void initializeBridge( ReactCallback callback, JavaScriptExecutor jsExecutor, MessageQueueThread jsQueue, MessageQueueThread moduleQueue, Collection
       
         javaModules, Collection
        
          cxxModules)
        
       ;
}Copy the code

Several arguments passed in:

ReactCallback Callback: The static inner class ReactCallback of CatalystInstanceImpl, responsible for interface callbacks. JavaScriptExecutor jsExecutor: JS executor that passes JS calls to the C++ layer. MessageQueueThread jsQueue. GetJSQueueThread () : JS thread, through mReactQueueConfiguration. GetJSQueueThread (), Create mReactQueueConfiguration through ReactQueueConfigurationSpec. CreateDefault (). MessageQueueThread moduleQueue: Native threads, through mReactQueueConfiguration. GetNativeModulesQueueThread (), Create mReactQueueConfiguration through ReactQueueConfigurationSpec. CreateDefault (). Collection < JavaModuleWrapper > javaModules: Java modules, from mJavaRegistry. GetJavaModules (this). Collection < ModuleHolder > cxxModules) : c + + modules, from mJavaRegistry. GetCxxModules ().Copy the code

Let’s trace its function call chain:

CatalystInstanceImpl. InitializeBridge () - > CatalystInstanceImpl: : initializeBridge () - > Instance: : initializeBridge ()Copy the code

Eventually can be found that the Instance: : initializeBridge () to achieve the ReactBridge (c + + layer corresponding JsToNativeBridge and NativeToJsBridge) initialization.

Instance.cpp

void Instance::initializeBridge(
    std::unique_ptr<InstanceCallback> callback,
    std::shared_ptr<JSExecutorFactory> jsef,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::unique_ptr<MessageQueueThread> nativeQueue,
    std::shared_ptr<ModuleRegistry> moduleRegistry) {
  callback_ = std::move(callback);

  if(! nativeQueue) { // TODO pass down a thread/queue from java, instead of creating our own. auto queue = folly::make_unique<CxxMessageQueue>(); std::thread t(queue->getUnregisteredRunLoop()); t.detach(); nativeQueue = std::move(queue); } jsQueue->runOnQueueSync( [this, &jsef, moduleRegistry, jsQueue, NativeQueue = Folly ::makeMoveWrapper(STD :: Move (nativeQueue))] () mutable {// Initialize the NativeToJsBridge object nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>( jsef.get(), moduleRegistry, jsQueue, nativeQueue.move(), callback_); std::lock_guard<std::mutex> lock(m_syncMutex); m_syncReady =true;
      m_syncCV.notify_all();
    });

  CHECK(nativeToJsBridge_);
}Copy the code

We then go to the C++ layer to see the implementation of jstonian bridge and NativeToJsBridge.

2.2 about the implementation of communication bridge in C++ layer

The entire Java invocation JS is defined in the C++ layer’s executor.h file, and JS invokes Java logic.

The executor. h file defines the abstract ExecutorDelegate class and the methods to execute the Native Module, which is a bridge for JS to call Java. JsToNativeBridge implements the pure virtual functions (abstract methods) of this class. The abstract class also holds a reference to JSExecutor (used to execute JS).

class ExecutorDelegate {

 public:

  virtual ~ExecutorDelegate() {}



  virtual void registerExecutor(std::unique_ptr<JSExecutor> executor,

                                std::shared_ptr<MessageQueueThread> queue) = 0;

  virtual std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) = 0;



  virtual std::shared_ptr<ModuleRegistry> getModuleRegistry() = 0;



  virtual void callNativeModules(

    JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) = 0;

  virtual MethodCallResult callSerializableNativeHook(

    JSExecutor& executor, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) = 0;

};Copy the code

The Executor. H file defines the abstract JSExecutor class, which defines the methods used to execute JS Modules to interpret the execution of JS. JSCExecutor implements pure virtual functions (abstract methods) in this class. Another class corresponding to JsToNativeBridge is NativeToBridge, which is a bridge between Java and JS. NativeToBridge holds a reference to JSCExecutor, JSCExecutor is called if NativeToBridge needs to execute JS.


class JSExecutor {
public:
  /** * Execute an application script bundle in the JS context. */
  virtual void loadApplicationScript(std: :unique_ptr<const JSBigString> script,
                                     std: :string sourceURL) = 0;

  /** * Add an application "unbundle" file */
  virtual void setJSModulesUnbundle(std: :unique_ptr<JSModulesUnbundle> bundle) = 0;

  /** * Executes BatchedBridge.callFunctionReturnFlushedQueue with the module ID, * method ID and optional additional arguments in JS. The executor is responsible * for using Bridge->callNativeModules to invoke any necessary native modules methods. */
  virtual void callFunction(const std: :string& moduleId, const std: :string& methodId, const folly::dynamic& arguments) = 0;

  /** * Executes BatchedBridge.invokeCallbackAndReturnFlushedQueue with the cbID, * and optional additional arguments in JS and returns the next queue. The executor * is responsible for using Bridge->callNativeModules to invoke any necessary * native modules methods. */
  virtual void invokeCallback(const double callbackId, const folly::dynamic& arguments) = 0;

  virtual void setGlobalVariable(std: :string propName,
                                 std: :unique_ptr<const JSBigString> jsonValue) = 0;
  virtual void* getJavaScriptContext(a) {
    return nullptr;
  }
  virtual bool supportsProfiling(a) {
    return false;
  }
  virtual void startProfiler(const std: :string &titleString) {}
  virtual void stopProfiler(const std: :string &titleString, const std: :string &filename) {}
  virtual void handleMemoryPressureUiHidden(a) {}
  virtual void handleMemoryPressureModerate(a) {}
  virtual void handleMemoryPressureCritical(a) {
    handleMemoryPressureModerate();
  }
  virtual void destroy(a) {}
  virtual ~JSExecutor() {}
};Copy the code

2.3 About the implementation of communication bridge in JS layer

JS layer Libraries/BatchedBridge package contains three JS files: BatchedBridge. JS, MessageQueue. JS, and NativeModules.

BatchedBridge.js

'use strict';

const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue();

// Wire up the batched bridge on the global object so that we can call into it.
// Ideally, this would be the inverse relationship. I.e. the native environment
// provides this global directly with its script embedded. Then this module
// would export it. A possible fix would be to trim the dependencies in
// MessageQueue to its minimal features and embed that in the native runtime.

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

module.exports = BatchedBridge;Copy the code

You can see that the MessageQueue object is created in BatchedBridge.

MessageQueue.js

The implementation of MessageQueue is a bit long, so let’s look at its implementation step by step. Let’s look at the constructor of MessageQueue.

class MessageQueue {

  //
  _callableModules: {[key: string]: Object};
  // Queue to hold the module array, method array, parameter array, and array size to be called
  _queue: [Array<number>, Array<number>, Array<any>, number];
  // Array of callback functions, corresponding to _quueue, each method called in _queue, if there are callback methods, then the array corresponding to the subscript.
  _callbacks: [];
  // The callback ID is automatically added.
  _callbackID: number;
  // Call the function ID, automatic increment.
  _callID: number;
  _lastFlush: number;
  // Message queue event cycle start time
  _eventLoopStartTime: number;

  _debugInfo: Object;
  //Module Table, for Native Module
  _remoteModuleTable: Object;
  //Method Table for Native Module
  _remoteMethodTable: Object;

  __spy: ?(data: SpyData) = > void;

  constructor() {
    this._callableModules = {};
    this._queue = [[], [], [], 0];
    this._callbacks = [];
    this._callbackID = 0;
    this._callID = 0;
    this._lastFlush = 0;
    this._eventLoopStartTime = new Date().getTime();

    if (__DEV__) {
      this._debugInfo = {};
      this._remoteModuleTable = {};
      this._remoteMethodTable = {};
    }

    // Bind a function, that is, create a function that has the same this value no matter how it is called.
    (this:any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind(this);
    (this:any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind(this);
    (this:any).flushedQueue = this.flushedQueue.bind(this);
    (this:any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind(this); }}Copy the code

NativeModules.jS

NativeModules describes the structure of modules for parsing and generating modules.

The structure of the Module is defined as follows:

type ModuleConfig = [
  string, /* name */
  ?Object./* constants */
  Array<string>, /* functions */
  Array<number>, /* promise method IDs */
  Array<number>, /* sync method IDs */
];Copy the code

For example,

{
  "remoteModuleConfig": {
    "Logger": {
      "constants": { /* If we had exported constants... * / },
      "moduleID": 1."methods": {
        "requestPermissions": {
          "type": "remote"."methodID": 1}}}}} {'ToastAndroid': {
    moduleId: 0.methods: {
      'show': {
        methodID: 0}},constants: {
      'SHORT': '0'.'LONG': '1'}},'moduleB': {
    moduleId: 0.methods: {
      'method1': {
        methodID: 0}},'key1': 'value1'.'key2': 'value2'}}Copy the code

Next, let’s examine the flow of JS code calling Java code.

The triple JS layer calls the Java layer

RN application communication mechanism flow chart (JS->Java) is as follows:

For example,

Similarly, let’s first look at an example of JS code calling Java code. We write a ToastModule for JS code to call.

1 write ToastModule inheritance in ReactContextBaseJavaModule, the ToastModule implement specific functionality for JS code calls.

public class ToastModule extends ReactContextBaseJavaModule {

  private static final String DURATION_SHORT_KEY = "SHORT";
  private static final String DURATION_LONG_KEY = "LONG";

  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  // Return the module name for the JS code to call
  @Override
  public String getName(a) {
    return "ToastAndroid";
  }

  // Return constant for JS code to call
  @Override
  public Map<String, Object> getConstants(a) {
    final Map<String, Object> constants = new HashMap<>();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

  // method exposed to JS code annotated with @reactMethod and always void.
  @ReactMethod
  public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); }}Copy the code

Write classes that inherit ReactPackage and register ToastModule.

public class AnExampleReactPackage implements ReactPackage {

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Collections.emptyList();
  }

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }

  @Override
  public List<NativeModule> createNativeModules( ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();

    modules.add(new ToastModule(reactContext));

    returnmodules; }}Copy the code
protected List<ReactPackage> getPackages(a) {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new AnExampleReactPackage()); // <-- Add this line with your package name.
}Copy the code

3. In order to facilitate the invocation of JS code, write a JS Module to package the functions of Native Module.


'use strict';
/** * This exposes the native ToastAndroid module as a JS module. This has a * function 'show' which takes the following  parameters: * * 1. String message: A string with the text to toast * 2. int duration: The duration of the toast. May be ToastAndroid.SHORT or * ToastAndroid.LONG */
import { NativeModules } from 'react-native';
module.exports = NativeModules.ToastAndroid;Copy the code

4 Finally we can make the call directly in the JS code.


import ToastAndroid from './ToastAndroid';

ToastAndroid.show('Awesome', ToastAndroid.SHORT);Copy the code

The above is the JS code to call the Java code of the entire process, we will specifically analyze its implementation details.

From the above example, we can see that the first step of the call is to get the Java Module of the corresponding Java layer from the NativeModule registry of the JS layer. ModuleID and methodID are pushed into a messageQueue, waiting for events from the Java layer to drive it. When the Events from the Java layer are delivered, The JS layer returns all the data in messageQUeue to the Java layer and invokes the methods via the registry JavaRegistry.

The JS layer calls the Java layer to implement the flow:

JS will push the moduleID, methodID, and arguments of the method to be called into MessageQueue and wait for the Java layer event driver. 2Copy the code

We have to call the Java code through NativeModules. XxxModule. XxxMethod () to call, now let’s look at NativeModules. The realization of js.

3.1 NativeModules. XxxModule. XxxMethod ()

When we use NativeModules. XxxModule. XxxMethod () this way to call, JS will by JS NativeModules Java to find the corresponding Module.

NativeModules.js


let NativeModules : {[moduleName: string]: Object} = {};

  NativeModuleProxy is a global variable. As the name suggests, it is the proxy of NativeModules. Js at the C++ layer.
  NativeModules = global.nativeModuleProxy;
} else {
  const bridgeConfig = global.__fbBatchedBridgeConfig;
  invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules');

  (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) = > {
    // Initially this config will only contain the module name when running in JSC. The actual
    // configuration of the module will be lazily loaded.
    const info = genModule(config, moduleID);
    if(! info) {return;
    }

    if (info.module) {
      NativeModules[info.name] = info.module;
    }
    // If there's no module config, define a lazy getter
    else {
      defineLazyObjectProperty(NativeModules, info.name, {
        get: (a)= >loadModule(info.name, moduleID) }); }}); }module.exports = NativeModules;Copy the code

NativeModuleProxy is essentially in the startup process, JSCExecutor::JSCExecutor() is created with installGlobalProxy(m_Context, “nativeModuleProxy”, ExceptionWrapMethod < & JSCExecutor: : getNativeModule > create ()), so when JS calls NativeModules, actually in the calling JSCExecutor: : getNativeModule () method, Let’s take a look at the implementation of this method.

3.2 JSCExecutor: : getNativeModule (JSObjectRef object, JSStringRef propertyName)


JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) {
  if (JSC_JSStringIsEqualToUTF8CString(m_context, propertyName, "name")) {
    return Value(m_context, String(m_context, "NativeModules"));
  }
  //m_nativeModules is of type JSCNativeModules
  return m_nativeModules.getModule(m_context, propertyName);
}Copy the code

This method further calls the getModule() method of JSCNativeModules. CPP, let’s look at its implementation.

3.3 JSCNativeModules: : getModule (JSContextRef context, JSStringRef jsName)

JSCNativeModules.cpp

JSValueRef JSCNativeModules::getModule(JSContextRef context, JSStringRef jsName) {
  if(! m_moduleRegistry) {return Value::makeUndefined(context);
  }

  std: :string moduleName = String::ref(context, jsName).str();

  const auto it = m_objects.find(moduleName);
  if(it ! = m_objects.end()) {return static_cast<JSObjectRef>(it->second);
  }

  // Call this method, get the JS properties of the global Settings through JSC, look up the Java layer registry through JNI, and fire the JS layer method.
  auto module = createModule(moduleName, context);
  if (!module.hasValue()) {
    return Value::makeUndefined(context);
  }

  // Protect since we'll be holding on to this value, even though JS may not
  module->makeProtected();

  auto result = m_objects.emplace(std::move(moduleName), std::move(*module)).first;
  return static_cast<JSObjectRef>(result->second);
}

folly::Optional<Object> JSCNativeModules::createModule(const std: :string& name, JSContextRef context) {
  if(! m_genNativeModuleJS) {auto global = Object::getGlobalObject(context);
    //NativeModules. Js points the global variable __fbGenNativeModule to the NativeModules::gen() method here
    // Get the JSC to call this method
    m_genNativeModuleJS = global.getProperty("__fbGenNativeModule").asObject();
    m_genNativeModuleJS->makeProtected();

    // Initialize the module name list, otherwise getModuleConfig won't work
    // TODO (pieterdb): fix this in ModuleRegistry
    m_moduleRegistry->moduleNames();
  }
  // Get the Native configuration table
  auto result = m_moduleRegistry->getConfig(name);
  if(! result.hasValue()) {return nullptr;
  }

  // Call the NativeModules::gen() methodValue moduleInfo = m_genNativeModuleJS->callAsFunction({ Value::fromDynamic(context, result->config), Value::makeNumber(context, result->index) }); CHECK(! moduleInfo.isNull()) <<"Module returned from genNativeModule is null";

  return moduleInfo.asObject().getProperty("module").asObject();
}Copy the code

The function of the above method is divided into two steps:

1 call ModuleRegistry: : getConfig ModuleConfig (). 2 Call the genModule() method of NativeModulesCopy the code

ModuleRegistry passed from the Java layer of JavaModuleRegistry ModuleRegistry: : getConfig Java () query Module configuration table, It is then sent to nativemodule.js to generate the corresponding JS Module.

ModuleRegistry.cpp


folly::Optional<ModuleConfig> ModuleRegistry::getConfig(const std: :string& name) {
  SystraceSection s("getConfig"."module", name);
  auto it = modulesByName_.find(name);
  if (it == modulesByName_.end()) {
    return nullptr;
  }

  CHECK(it->second < modules_.size());
  / / modules_ list from CatalystInstanceImpl: : initializeBridge ()
  // Module is essentially a constructor summary of ModuleregistryHolder. CPP that wraps modules passed from the Java layer into CxxNativeModule and JavaModule, both subclasses of NativeModule.
  NativeModule* module = modules_[it->second].get();

  // String Name, Object Constants, array methodNames Ready to create a dynamic object.
  // string name, object constants, array methodNames (methodId is index), [array promiseMethodIds], [array syncMethodIds]
  folly::dynamic config = folly::dynamic::array(name);

  {
    SystraceSection s("getConstants");
    / / calls through reflection of Java layer JavaModuleWrapper. GetContants shi () method.
    config.push_back(module->getConstants());
  }

  {
    SystraceSection s("getMethods");
    / / by reflection to invoke Java layer JavaModuleWrapper getMethods () method, namely BaseJavaModule. GetMethods (), internal calls this method
    //findMethos() queries methods annotated with ReactMoethod.
    std: :vector<MethodDescriptor> methods = module->getMethods();

    folly::dynamic methodNames = folly::dynamic::array;
    folly::dynamic promiseMethodIds = folly::dynamic::array;
    folly::dynamic syncMethodIds = folly::dynamic::array;

    for (auto& descriptor : methods) {
      // TODO: #10487027 compare tags instead of doing string comparison?
      methodNames.push_back(std::move(descriptor.name));
      if (descriptor.type == "promise") {
        promiseMethodIds.push_back(methodNames.size() - 1);
      } else if (descriptor.type == "sync") {
        syncMethodIds.push_back(methodNames.size() - 1); }}if(! methodNames.empty()) { config.push_back(std::move(methodNames));
      if(! promiseMethodIds.empty() || ! syncMethodIds.empty()) { config.push_back(std::move(promiseMethodIds));
        if(! syncMethodIds.empty()) { config.push_back(std::move(syncMethodIds)); }}}}if (config.size() == 1) {
    // no constants or methods
    return nullptr;
  } else {
    returnModuleConfig({it->second, config}); }}Copy the code

ModuleConfig will call the genModule() of NativeModules. Js generated js will call the corresponding JS Module.

3.4 NativeModules. GenModule (config:? ModuleConfig, moduleID: number): ? {name: string, module? : Object}

NativeModules.js


// export this method as a global so we can call it from native
global.__fbGenNativeModule = genModule;

function genModule(config: ? ModuleConfig, moduleID: number): ?{name: string, module? :Object} {
  if(! config) {return null;
  }

  // get the Java Module registry from the Java layer in C++ via JSC
  const[moduleName, constants, methods, promiseMethods, syncMethods] = config; invariant(! moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
    'Module name prefixes should\'ve been stripped by the native side ' +
    'but wasn\'t for ' + moduleName);

  if(! constants && ! methods) {// Module contents will be filled in lazily later
    return { name: moduleName };
  }

  const module = {};
  // Iterate over the properties and methods of the build Module
  methods && methods.forEach((methodName, methodID) = > {
    const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
    constisSync = syncMethods && arrayContains(syncMethods, methodID); invariant(! isPromise || ! isSync,'Cannot have a method that is both async and a sync hook');
    const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
    // Generate Module function methods
    module[methodName] = genMethod(moduleID, methodID, methodType);
  });
  Object.assign(module, constants);

  if (__DEV__) {
    BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
  }

  return { name: moduleName, module };
}Copy the code

This method obtains the Java Module registry from the Java layer in C++ through JSC, traverses the properties and methods of the building Module, and calls genMethod() to generate the function method of the Module. When we call toastAndroid.show (‘ Awesome ‘, toastAndroid.short) we are actually calling the method generated by genMethod(), so let’s take a look at its implementation.

NativeModules.js

/ / this function will do different treatment according to different types of functions, but in the end will be called BatchedBridge. EnqueueNativeCall () method.
function genMethod(moduleID: number, methodID: number, type: MethodType) {
  let fn = null;
  if (type === 'promise') {
    fn = function(. args: Array
       ) {
      return new Promise((resolve, reject) = > {
        BatchedBridge.enqueueNativeCall(moduleID, methodID, args,
          (data) => resolve(data),
          (errorData) => reject(createErrorFromErrorData(errorData)));
      });
    };
  } else if (type === 'sync') {
    fn = function(. args: Array
       ) {
      return global.nativeCallSyncHook(moduleID, methodID, args);
    };
  } else {
    fn = function(. args: Array
       ) {
      const lastArg = args.length > 0 ? args[args.length - 1] : null;
      const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
      const hasSuccessCallback = typeof lastArg === 'function';
      const hasErrorCallback = typeof secondLastArg === 'function';
      hasErrorCallback && invariant(
        hasSuccessCallback,
        'Cannot have a non-function arg after a function arg.'
      );
      const onSuccess = hasSuccessCallback ? lastArg : null;
      const onFail = hasErrorCallback ? secondLastArg : null;
      const callbackCount = hasSuccessCallback + hasErrorCallback;
      args = args.slice(0, args.length - callbackCount);
      BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
    };
  }
  fn.type = type;
  return fn;
}Copy the code

This function will do different treatment according to different types of functions, but in the end will be called BatchedBridge. EnqueueNativeCall () method, we take a look at it.

3.5 MessageQueue. EnqueueNativeCall (moduleID: number, methodID: number, params: Array, onFail:? Function, onSucc: ? Function)

MessageQueue.js


  enqueueNativeCall(moduleID: number, methodID: number, params: Array<any>, onFail:?Function.onSucc:?Function) {
    if (onFail || onSucc) {
      if (__DEV__) {
        const callId = this._callbackID >> 1;
        this._debugInfo[callId] = [moduleID, methodID];
        if (callId > DEBUG_INFO_LIMIT) {
          delete this._debugInfo[callId - DEBUG_INFO_LIMIT];
        }
      }
      onFail && params.push(this._callbackID);
      /* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error * detected during the deployment of V0.38.0. To see the error, remove * this comment and run flow */
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      /* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error * detected during the deployment of V0.38.0. To see the error, remove * this comment and run flow */
      this._callbacks[this._callbackID++] = onSucc;
    }

    if (__DEV__) {
      global.nativeTraceBeginAsyncFlow &&
        global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native'.this._callID);
    }
    this._callID++;

    //_queue is a queue that holds information about the modules, methods, and parameters to be called.
    this._queue[MODULE_IDS].push(moduleID);
    this._queue[METHOD_IDS].push(methodID);

    if (__DEV__) {
      // Any params sent over the bridge should be encodable as JSON
      JSON.stringify(params);

      // The params object should not be mutated after being queued
      deepFreezeAndThrowOnMutationInDev((params:any));
    }
    this._queue[PARAMS].push(params);

    const now = new Date().getTime();
    // If there are more than one method call in 5ms, wait in the queue first to prevent too many method calls. Or call the JSCExecutor: : nativeFlushQueueImmediate (size_t argumentCount, const JSValueRef the arguments []) method.
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
    Systrace.counterEvent('pending_js_to_native_queue'.this._queue[0].length);
    if (__DEV__ && this.__spy && isFinite(moduleID)) {
      this.__spy(
        { type: TO_NATIVE,
          module: this._remoteModuleTable[moduleID],
          method: this._remoteMethodTable[moduleID][methodID],
          args: params }
      );
    } else if (this.__spy) {
      this.__spy({type: TO_NATIVE, module: moduleID + ' '.method: methodID, args: params}); }}Copy the code

When the process reaches this method, the JS layer call process ends. The JS layer mainly obtains the Registry of Java Module through JSC bridge and converts it into the corresponding JS Native Module (property and method conversion). When JS is ready to call a Java method via nativemodule.xxxMethod (), xxxMethod() is placed in a JS queue, where:

1 If it is within 5m (MIN_TIME_BETWEEN_FLUSHES_MS), it continues to wait in the queue for events from the Java layer to drive it. 2 If within 5m (MIN_TIME_BETWEEN_FLUSHES_MS), Directly trigger JSCExecutor: : nativeFlushQueueImmediate (size_t argumentCount, const JSValueRef the arguments []) method to call the corresponding Java method.Copy the code

In fact, if a Java method calls a JS method in a queue, the method previously stored in the queue will be processed through JSCExecutor:: Flush ().

We take a look at JSCExecutor: : nativeFlushQueueImmediate (size_t argumentCount, const JSValueRef the arguments []).

3.6 JSValueRef JSCExecutor: : nativeFlushQueueImmediate (size_t argumentCount, const JSValueRef the arguments [])

JSCExecutor.cpp

JSValueRef JSCExecutor::nativeFlushQueueImmediate(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if(argumentCount ! =1) {
    throw std::invalid_argument("Got wrong number of args");
  }

  flushQueueImmediate(Value(m_context, arguments[0]));
  return Value::makeUndefined(m_context);
}

void JSCExecutor::flushQueueImmediate(Value&& queue) {
  auto queueStr = queue.toJSONString();
  // Call callNativeModules() of jstonativebridge.cpp, passing isEndOfBatch=false
  m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false);
}Copy the code

Can see nativeFlushQueueImmediate flushQueueImmediate () will further call () method, m_delegate type is ExecutorDelegate, It actually calls the callNativeModules() method of jstonativeBridge.cpp, an ExecutorDelegate subclass. Recall that the implementation of step 7 of Java code calling JS code also follows this method, Just pass isEndOfBatch=true.

JsToNativeBridge: 3.7: callNativeModules ()

JsToNativeBridge.cpp

  void callNativeModules()(
      JSExecutor& executor, folly::dynamic&& calls, bool isEndOfBatch) override {

    CHECK(m_registry || calls.empty()) <<
      "native module calls cannot be completed with no native modules"; ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor); // Execute m_nativeQueue->runOnQueue([this, token, calls= STD ::move(calls), IsEndOfBatch] () mutable {// Record the list of calling methods from the JS queue // An exception anywherein here stops processing of the batch.  This
      // was the behavior of the Android bridge, and since exception handling
      // terminates the whole bridge, there's not much point in continuing. for (auto& call : React ::parseMethodCalls(STD ::move(calls))) {//m_registry is of type ModuleRegistry,  m_registry->callNativeMethod( token, call.moduleId, call.methodId, std::move(call.arguments), call.callId); } if (isEndOfBatch) {// mark Java status m_callback->onBatchComplete(); m_callback->decrementPendingJSCalls(); }}); }Copy the code

In this method to retrieve all JS in the queue JS invokes the Java method, and through the ModuleRegistry: : callNativeMethod () method to traverse the call, let’s take a look at the realization of this method.

3.8 ModuleRegistry: : callNativeMethod (ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId)

void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
                                      folly::dynamic&& params, int callId) {
  if (moduleId >= modules_.size()) {
    throw std::runtime_error(
      folly::to<std: :string> ("moduleId ", moduleId,
                             " out of range [0..", modules_.size(), ")"));
  }

#ifdef WITH_FBSYSTRACE
  if(callId ! =- 1) {
    fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
  }
#endif

  Modules_ is a C++ NativeModule created from the Java layer ModuleRegistryHolder when the ModuleRegistryHolder is created
  //moduleId indicates the index value of the module in the current list
  modules_[moduleId]->invoke(token, methodId, std::move(params));
}Copy the code

Modules_ is STD ::vector> modules_. NativeModule is a C++ wrapper for Java modules. NativeModule subclass is JavaNativeModule. Let’s go to its invoke method, invoke().

3.9 JavaNativeModule::invoke(ExecutorToken Token, unsigned int reactMethodId, Folly :: Dynamic && Params)

Pure virtual functions defined by the abstract class NativeModule (abstract methods)

NativeModule.cpp

class NativeModule {
 public:
  virtual ~NativeModule() {}
  virtual std: :string getName(a) = 0;
  virtual std: :vector<MethodDescriptor> getMethods() = 0;
  virtual folly::dynamic getConstants(a) = 0;
  virtual bool supportsWebWorkers(a) = 0;
  // TODO mhorowitz: do we need initialize()/onCatalystInstanceDestroy() in C++
  // or only Java?
  virtual void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) = 0;
  virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& args) = 0; }; }}Copy the code

NativeModule has two subclasses. The class diagram for NativeModule looks like this:

class JavaNativeModule : public NativeModule {

  void JavaNativeModule::invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) {

  //wrapper_- is of type javamoduleWrapper. CPP, which maps to Java layer javamoduleWrapper. Java, The Java Module passed in from Java in the moduleregistryholder. CPP constructor is wrapped in C++
  / / JavaModuleWrapper object. The Invoke () method of JavamoduleWrapper.java of the Java layer is called by reflection, passing in mothodId and parameters.
  static auto invokeMethod =
    wrapper_->getClass()->getMethod<void(JExecutorToken::javaobject, jint, ReadableNativeArray::javaobject)>("invoke");
  invokeMethod(wrapper_, JExecutorToken::extractJavaPartFromToken(token).get(), static_cast<jint>(reactMethodId),
               ReadableNativeArray::newObjectCxxArgs(std::move(params)).get()); }}Copy the code

This method calls the Invoke () method of Java layer JavamoduleWrapper.java via reflection, passing in mothodId and parameters. Let’s look at the implementation of the invoke() method of JavamoduleWrapper.java.

3.10 JavaModuleWrapper. Invoke (ExecutorToken Token, int methodId, ReadableNativeArray Parameters)


/ / NativeModules is an interface, we know that want to implement for JS call we need to write Java API classes inherit BaseJavaModule/ReactContextBaseJavaModule, BaseJavaModule/ReactContextBaseJavaModule is
// Implements the NativeModule interface,
 private final ArrayList<NativeModule.NativeMethod> mMethods;

@DoNotStrip
public class JavaModuleWrapper {
  @DoNotStrip
  public void invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
      return;
    }
    / / mMethods. For all classes inherit BaseJavaModule BaseJavaModule JavaMethod object, by ReactMethod annotation mark method.mMethods.get(methodId).invoke(mJSInstance, token, parameters); }}Copy the code

JavaModuleWrapper corresponds to the NativeModule of the C++ layer. This class is wrapped for Java BaseJavaModule, making it easier for the C++ layer to call Java modules through JNI.

The four Java layers call the JS layer

RN application communication mechanism flow chart (Java->JS) is as follows:

For example,

In the article ReactNative source text: Start the process, we’re ReactInstanceManager. OnAttachedToReactInstance APPRegistry () method calls. JS runApplication () to start the RN application, This is a typical Java layer call JS layer example, we will specifically analyze the implementation of this example.

The interface AppRegistry, which inherits from JavaScriptModule, is defined as follows:

/** * JS module interface - main entry point for launching React application for a given key. */
public interface AppRegistry extends JavaScriptModule {

  void runApplication(String appKey, WritableMap appParameters);
  void unmountApplicationComponentAtRootTag(int rootNodeTag);
  void startHeadlessTask(int taskId, String taskKey, WritableMap data);
}Copy the code

2 then in CoreModulesPackage. CreateJSModules () to add it to the list of JavaScriptModule, this list will eventually be added to the JavaScriptModuleRegistry.

class CoreModulesPackage extends LazyReactPackage implements ReactPackageLogger {

  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    List<Class<? extends JavaScriptModule>> jsModules = new ArrayList<>(Arrays.asList(
        DeviceEventManagerModule.RCTDeviceEventEmitter.class,
        JSTimersExecution.class,
        RCTEventEmitter.class,
        RCTNativeAppEventEmitter.class,
        AppRegistry.class,
        com.facebook.react.bridge.Systrace.class,
        HMRClient.class));

    if (ReactBuildConfig.DEBUG) {
      jsModules.add(DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
      jsModules.add(JSCHeapCapture.HeapCapture.class);
      jsModules.add(JSCSamplingProfiler.SamplingProfiler.class);
    }

    returnjsModules; }}Copy the code

3. Call the runApplication() method of appregistry.js from the Java layer as follows:

// Start process entry: started by a Java layer call
catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);Copy the code

Java layer invokes JS layer implementation process:

Java layer

Write the function to be implemented as an interface that inherits JavaScriptModule and is managed by ReactPackage, which will eventually be added to the JavaScriptModuleRegistry registry when RN is initialized. 2 Use ReactContext or CatalystInstanceImpl to obtain the JavaScriptModule. They may end JavaScriptModuleRegistry. GetJavaScriptModule () to obtain the corresponding JavaScriptModule. 3. JavaScriptModuleRegistry generates the corresponding JavaScriptModule via dynamic proxy and invokes the corresponding JS method via invoke(). . This method can further to call CatalystInstanceImpl callJSFunction () calls a native method this method CatalystInstanceImpl. JniCallJSFunction () method will related parameters passed to the c + + layer, at this point, The whole process moves to the C++ layer.Copy the code

C + + layer

CatalystInstanceImpl corresponds to the CatalystInstanceImpl. CPP class in C++. CatalystInstanceImpl. CPP is RN's wrapper class for the Android platform. The specific function is accomplished by instance. CPP, i.e. the callJSFunction() method of instance. CPP. 5 The instance.cpp callJSFunction() method follows the call chain: The Instance. CallJSFunction () - > NativeToJsBridge. CallFunction () - > JSCExecutor. CallFunction () will eventually The functionality is left to the callFunction() method of jscexecutor.cpp. 6 JSCExecutor. CPP callFunction () method calls through its JSC of JS layer MessageQueue. In JS callFunctionReturnFlushedQueue () method, From there the whole process moves into the JavaScript layer.Copy the code

JS layers

In 7 MessageQueue. Js callFunctionReturnFlushedQueue () method, the method according to the invocation chain: MessageQueue.callFunctionReturnFlushedQueue()->MessageQueue.__callFunction() Find the corresponding JavaScriptModule and method in the JavaScriptModule registry in the JS layer.Copy the code

Let’s examine how the above code is called.

AppRegistry inherits from JavaScriptModule. AppRegistry is added to CoreModulesPackage as one of the core logic. We know that in ReactInstanceManager. CreateReactContext () method, CoreModulesPackage as ReactPackage was added into the JavaScriptModuleRegistry, JavaScriptModuleRegistry is managed by the CatalystInstanceImpl.

Hence the generic format for Java layer to call JS layer code:

CatalystInstanceImpl.getJSModule(xxx.class).method(params, params, ...) ;Copy the code

Of course, if we call our own JSModule, we use reactContext.getjsmodule () because ReactContext holds an instance of CatalystInstanceImpl, CatalystInstanceImpl is not directly public.

When Java layer code calls JS layer code, it needs to register JavaScriptModule with JavaScriptModuleRegistry, obtain various parameters of the method through dynamic proxy, and pass the parameters to JS layer through C++ layer to complete the call. Let’s look at how the CatalystInstanceImpl gets the JavaScriptModule.

CatalystInstanceImpl. GetJSModule () call JavaScriptModuleRegistry. GetJavaScriptModule JavaScriptModule () to query.

4.1 JavaScriptModuleRegistry. GetJavaScriptModule (CatalystInstance instance, ExecutorToken ExecutorToken, Class moduleInterface)

Its implementation is as follows:

public class JavaScriptModuleRegistry {

  public synchronized <T extends JavaScriptModule> T getJavaScriptModule( CatalystInstance instance, ExecutorToken executorToken, Class
       
         moduleInterface)
        {

    // If the JavaScriptModule is loaded once, it is saved in the cache and fetched directly from the cache on the second load.
    HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
        mModuleInstances.get(executorToken);
    if (instancesForContext == null) {
      instancesForContext = new HashMap<>();
      mModuleInstances.put(executorToken, instancesForContext);
    }
    JavaScriptModule module = instancesForContext.get(moduleInterface);
    if (module! =null) {
      return (T) module;
    }

    // Use dynamic proxies to get JavaScriptModule objects

    / / JavaScriptModuleRegistration of JavaScriptModule packaging, check whether there is a class reloading, implementing the JavaScriptModule interface because JS does not support overloaded.
    JavaScriptModuleRegistration registration =
        Assertions.assertNotNull(
            mModuleRegistrations.get(moduleInterface),
            "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
        moduleInterface.getClassLoader(),
        new Class[]{moduleInterface},
        new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
  }

  private static class JavaScriptModuleInvocationHandler implements InvocationHandler {

    private final WeakReference<ExecutorToken> mExecutorToken;
    private final CatalystInstance mCatalystInstance;
    private final JavaScriptModuleRegistration mModuleRegistration;

    public JavaScriptModuleInvocationHandler( ExecutorToken executorToken, CatalystInstance catalystInstance, JavaScriptModuleRegistration moduleRegistration) {
      mExecutorToken = new WeakReference<>(executorToken);
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    @Override
    public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
      ExecutorToken executorToken = mExecutorToken.get();
      if (executorToken == null) {
        FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
        return null; } NativeArray jsArgs = args ! =null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
      / / call CatalystInstanceImpl. CallFunction () method.MCatalystInstance. CallFunction () method. ( executorToken, mModuleRegistration.getName(), method.getName(), jsArgs );return null; }}}Copy the code

JavaScriptModuleRegistry uses dynamic proxies to obtain javascriptModules. For those unfamiliar with Java dynamic proxies, let’s take a quick look at Java dynamic proxies.

Java dynamic proxy

Java dynamic Proxy mainly involves two classes: java.lang.reflect.Proxy: used to generate Proxy classes. Java. Lang. Reflect. InvocationHandler: call processor, we need to define a class to specify dynamically generated proxy class need to complete the specific content. Static Object newProxyInstance(ClassLoader loader, Class<? >[] interfaces, InvocationHandler handler)// create a proxy object. Class<? >[] interfaces: interfaces that the proxy class needs to implement. InvocationHandler Handler: Calls the handler instance to specify what the proxy class does. Implementing Java dynamic proxies requires the following three steps: 1 define a delegate class and a public interface. 2 define the InvocationHandler class to implement the InvocationHandler interface, specifying the specific tasks to be completed by the proxy class. 3 Generating proxy objects One proxy object corresponds to one delegate class and one invocation handler classCopy the code

JavaScriptModuleInvocationHandler. Invoke () method to obtain the moduleID, methodID, and to call CatalystInstanceImpl. CallFunction ();

4.2 CatalystInstanceImpl. CallFunction (ExecutorToken ExecutorToken, final String module, final String method, final NativeArray arguments)


public class CatalystInstanceImpl{

  @Override
  public void callFunction(
      ExecutorToken executorToken,
      final String module.final String method,
      final NativeArray arguments) {
    if (mDestroyed) {
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
      return;
    }
    if(! mAcceptCalls) {// Most of the time the instance is initialized and we don't need to acquire the lock
      synchronized (mJSCallsPendingInitLock) {
        if(! mAcceptCalls) { mJSCallsPendingInit.add(new PendingJSCall(executorToken, module, method, arguments));
          return;
        }
      }
    }

    jniCallJSFunction(executorToken, module, method, arguments);
  }

  private native void jniCallJSCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments);
}Copy the code

Method at this point, the implementation logic has been transferred from the Java layer to the C++ layer, let’s go to the C++ layer to see the implementation.

CatalystInstanceImpl. Java class in c + + layer had a corresponding CatalystInstanceImpl. CPP.

4.3 CatalystInstanceImpl. JniCallJSFunction (JExecutorToken token, std::string module, std::string method, NativeArray arguments)

CatalystInstanceImpl.cpp

void CatalystInstanceImpl::jniCallJSFunction(

    JExecutorToken* token, std: :string module.std: :string method, NativeArray* arguments) {
  // We want to share the C++ code, and on iOS, modules pass module/method
  // names as strings all the way through to JS, and there's no way to do
  // string -> id mapping on the objc side. So on Android, we convert the
  // number to a string, here which gets passed as-is to JS. There, they they
  // used as ids if isFinite(), which handles this case, and looked up as
  // strings otherwise. Eventually, we'll probably want to modify the stack
  // from the JS proxy through here to use strings, too.
  instance_->callJSFunction(token->getExecutorToken(nullptr),
                            std::move(module),
                            std::move(method),
                            arguments->consume());
}Copy the code

CPP jniCallJSFunction() calls instance.cpp callJSFunction(). CPP is RN’s encapsulation for the Android platform. It mainly does write parameter type conversion. In essence, it corresponds to instance. CPP in the ReactCommon package. Let’s look at the implementation in instance.cpp.

4.4 Instance. CallJSFunction (ExecutorToken Token, STD :: String && Module, STD :: String && Method, Folly :: Dynamic && Params)

Instance.cpp

void Instance::callJSFunction(ExecutorToken token, std: :string&& module.std: :string&& method,
                              folly::dynamic&& params) {
  callback_->incrementPendingJSCalls();
  nativeToJsBridge_->callFunction(token, std::move(module), std::move(method), std::move(params));
}Copy the code

The callJSFunction() of instance.cpp further calls the callFunction() method of nativeTojsbridge.cpp. Let’s look at its implementation.

4.5 NativeToJsBridge. CallFunction (ExecutorToken ExecutorToken, STD: : string && module, STD: : string && method, folly::dynamic&& arguments)

NativeToJsBridge.cpp

void NativeToJsBridge::callFunction( ExecutorToken executorToken, std::string&& module, std::string&& method, folly::dynamic&& arguments) { int systraceCookie = -1; #ifdef WITH_FBSYSTRACE systraceCookie = m_systraceCookie++; std::string tracingName = fbsystrace_is_tracing(TRACE_TAG_REACT_CXX_BRIDGE) ? folly::to<std::string>("JSCall__", module, '_', method) : std::string(); SystraceSection s(tracingName.c_str()); FbSystraceAsyncFlow::begin( TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str(), systraceCookie); #else std::string tracingName; #endif runOnExecutorQueue(executorToken, [module = std::move(module), method = std::move(method), arguments = std::move(arguments), tracingName = std::move(tracingName), systraceCookie] (JSExecutor* executor) { #ifdef WITH_FBSYSTRACE FbSystraceAsyncFlow::end( TRACE_TAG_REACT_CXX_BRIDGE, tracingName.c_str(), systraceCookie); SystraceSection s(tracingName.c_str()); #endif // Call jscexecutor.cppd callFunction() // This is safe because we are running on the executor thread: it won't // destruct until after it's been unregistered (which we check above) and // that will happen on this thread executor->callFunction(module, method, arguments); }); }Copy the code

The callFunction() of nativeTojsbridge.cpp further calls the callFunction() method of jscexecutor.cppd, which lets us look at its implementation.

4.6 jscexecutor.callFunction (Const STD :: String & moduleId, const STD :: String & methodId, const Folly :: Dynamic & Arguments)

JSCExecutor.cpp

void JSCExecutor::callFunction(const std: :string& moduleId, const std: :string& methodId, const folly::dynamic& arguments) {
  SystraceSection s("JSCExecutor::callFunction");
  // This weird pattern is because Value is not default constructible.
  // The lambda is inlined, so there's no overhead.

  auto result = [&] {
    try {
      // See flush()
      CHECK(m_callFunctionReturnFlushedQueueJS)
        << "Attempting to call native methods without loading BatchedBridge.js";
      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))
      });
    } catch(...). {std::throw_with_nested(
        std::runtime_error("Error calling " + moduleId + "." + methodId));
    }
  }();

  // Returns the result of the call to the Java layer
  callNativeModules(std::move(result));
}Copy the code

It can be seen that the function call further m_callFunctionReturnFlushedQueueJS – > callAsFunction (), let’s explain m_callFunctionReturnFlushedQueueJS the origin of this variable. It’s initialized in JSCExecutor::bindBridge(), which essentially takes JS layer code associated objects and method references via Webkit JSC, M_callFunctionReturnFlushedQueueJS is MessageQueue. Js callFunctionReturnFlushedQueue in reference () method.

void JSCExecutor::bindBridge() throw(JSException) {

  ...

 m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject(); . }Copy the code

4.7 MessageQueue. CallFunctionReturnFlushedQueue (module: string, method: string, args: Array)

MessageQueue. CallFunctionReturnFlushedQueue () method implementation is as follows:

MessageQueue.js

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

    return this.flushedQueue();
  }

  __callFunction(module: string, method: string, args: Array<any>) {
    this._lastFlush = new Date().getTime();
    this._eventLoopStartTime = this._lastFlush;
    Systrace.beginEvent(`The ${module}.${method}(a) `);
    if (this.__spy) {
      this.__spy({ type: TO_JS, module, method, args});
    }

    // Find appregistry.js from the JavaScriptModule registry in the JAVASCRIPT layer
    const moduleMethods = this._callableModules[module]; invariant( !! moduleMethods,'Module %s is not a registered callable module (calling %s)'.module, method ); invariant( !! moduleMethods[method],'Method %s does not exist on module %s',
      method, module
    );
    // Get the JS layer method called by the Java layer, for example, the runApplication() method of appregistry.js
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }Copy the code

Appregistry.js is found in the JavaScriptModule registry in the JS layer, and the runApplication() method of apPregistry.js is finally called.

Ok, the above is the whole process of Java layer code calling JS layer code, let’s summarize again:

Java layer

Write the function to be implemented as an interface that inherits JavaScriptModule and is managed by ReactPackage, which will eventually be added to the JavaScriptModuleRegistry registry when RN is initialized. 2 Use ReactContext or CatalystInstanceImpl to obtain the JavaScriptModule. They may end JavaScriptModuleRegistry. GetJavaScriptModule () to obtain the corresponding JavaScriptModule. 3. JavaScriptModuleRegistry generates the corresponding JavaScriptModule via dynamic proxy and invokes the corresponding JS method via invoke(). . This method can further to call CatalystInstanceImpl callJSFunction () calls a native method this method CatalystInstanceImpl. JniCallJSFunction () method will related parameters passed to the c + + layer, at this point, The whole process moves to the C++ layer.Copy the code

C + + layer

CatalystInstanceImpl corresponds to the CatalystInstanceImpl. CPP class in C++. CatalystInstanceImpl. CPP is RN's wrapper class for the Android platform. The specific function is accomplished by instance. CPP, i.e. the callJSFunction() method of instance. CPP. 5 The instance.cpp callJSFunction() method follows the call chain: The Instance. CallJSFunction () - > NativeToJsBridge. CallFunction () - > JSCExecutor. CallFunction () will eventually The functionality is left to the callFunction() method of jscexecutor.cpp. 6 JSCExecutor. CPP callFunction () method calls through its JSC of JS layer MessageQueue. In JS callFunctionReturnFlushedQueue () method, From there the whole process moves into the JavaScript layer.Copy the code

JavaScript layer

In 7 MessageQueue. Js callFunctionReturnFlushedQueue () method, the method according to the invocation chain: MessageQueue.callFunctionReturnFlushedQueue()->MessageQueue.__callFunction() Find the corresponding JavaScriptModule and method in the JavaScriptModule registry in the JS layer.Copy the code