This article has been published exclusively on JueCode, an official wechat account

In the last chapter, we analyzed the principle of Native JavaScript invocation. In this chapter, we will analyze the principle of JavaScript invocation. The content mentioned in the last article is not repeated here, I suggest friends to read the last article and then look at this one, it will be better to understand.

1.JavaScript initiates communication

For a simple Toast, first import the ToastAndroid module and then call the method display.

var ToastAndroid = require('ToastAndroid')
ToastAndroid.show('Clicking! ', ToastAndroid.SHORT);
Copy the code

This Toast needs to be displayed by Native. On Android, it needs to be processed by Java layer. So how is it transmitted?

ToastAndroid, first look at the first module in ToastAndroid. Android. Js file, you can see it is to introduce the ToastAndroid NativeModules:

//react-native/Libraries/Components/ToastAndroid/ToastAndroid.android.js
'use strict';

var RCTToastAndroid = require('NativeModules').ToastAndroid;

/**
 * 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
 */

var ToastAndroid = {

  SHORT: RCTToastAndroid.SHORT,
  LONG: RCTToastAndroid.LONG,

  show: function( message: string, duration: number ): void { RCTToastAndroid.show(message, duration); }}; module.exports = ToastAndroid;Copy the code

What is actually called is the ToastAndroid module in NativeModules. Look at the Java register local module first, and return the module’s name ‘ToastAndroid’ in the getName method.

@ReactModule(name = "ToastAndroid")
public class ToastModule extends ReactContextBaseJavaModule {

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

  private static final String GRAVITY_TOP_KEY = "TOP";
  private static final String GRAVITY_BOTTOM_KEY = "BOTTOM";
  private static final String GRAVITY_CENTER = "CENTER";

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

  @Override
  public String getName() {
    return "ToastAndroid";
  }

  @Override
  public Map<String, Object> getConstants() {
    final Map<String, Object> constants = MapBuilder.newHashMap();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    constants.put(GRAVITY_TOP_KEY, Gravity.TOP | Gravity.CENTER_HORIZONTAL);
    constants.put(GRAVITY_BOTTOM_KEY, Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
    constants.put(GRAVITY_CENTER, Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
    return constants;
  }

  @ReactMethod
  public void show(final String message, final int duration) {
    UiThreadUtil.runOnUiThread(new Runnable() {
      @Override
      public void run() { Toast.makeText(getReactApplicationContext(), message, duration).show(); }}); }... }Copy the code

ToastAndroid is a Native module that is passed to the JS layer.

NativeModules. Js, where RemoteModules are registered with the Java layer for js layer to call modules, see the React Native source code analysis. We then loop through RemoteModules and place Module into the variable NativeModules, where we can find the ToastAndroid Module:

//react-native/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js
'use strict';

const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;

function normalizePrefix(moduleName: string): string {
  return moduleName.replace(/^(RCT|RK)/, ' ');
}

/**
 * Dirty hack to support old (RK) and new (RCT) native module name conventions.
 */
Object.keys(RemoteModules).forEach((moduleName) => {
  const strippedName = normalizePrefix(moduleName);
  if (RemoteModules['RCT' + strippedName] && RemoteModules['RK' + strippedName]) {
    throw new Error(
      'Module cannot be registered as both RCT and RK: ' + moduleName
    );
  }
  if (strippedName !== moduleName) {
    RemoteModules[strippedName] = RemoteModules[moduleName];
    delete RemoteModules[moduleName];
  }
});

/**
 * Define lazy getters for each module.
 * These will return the module if already loaded, or load it if not.
 */
const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    enumerable: true,
    get: () => {
      let module = RemoteModules[moduleName];
      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
        const json = global.nativeRequireModuleConfig(moduleName);
        const config = json && JSON.parse(json);
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      returnmodule; }}); }); . module.exports = NativeModules;Copy the code

Above traverse RemoteModules invokes BatchedBridge. ProcessModuleConfig (config module. ModuleID); Read on:

//react-native/Libraries/Utilities/MessageQueue.js
processModuleConfig(config, moduleID) {
    const module = this._genModule(config, moduleID);
    this._genLookup(config, moduleID, this._remoteModuleTable, this._remoteMethodTable);
    return module;
  }

_genModule(config, moduleID) {
    if(! config) {return;
    }

    let moduleName, constants, methods, asyncMethods;
    if (moduleHasConstants(config)) {
      [moduleName, constants, methods, asyncMethods] = config;
    } else {
      [moduleName, methods, asyncMethods] = config;
    }

    let module = {};
    methods && methods.forEach((methodName, methodID) = > {
      const methodType =
        asyncMethods && arrayContains(asyncMethods, methodID) ?
          MethodTypes.remoteAsync : MethodTypes.remote;
      module[methodName] = this._genMethod(moduleID, methodID, methodType);
    });
    Object.assign(module, constants);

    if(! constants && ! methods && ! asyncMethods) {module.moduleID = moduleID;
    }

    this.RemoteModules[moduleName] = module;
    return module;
  }

_genMethod(module, method, type) {
    let fn = null;
    let self = this;
    if (type === MethodTypes.remoteAsync) {
      fn = function(. args) {
        return new Promise((resolve, reject) = > {
          self.__nativeCall(module, method, args, resolve, (errorData) => {
            var error = createErrorFromErrorData(errorData);
            reject(error);
          });
        });
      };
    } else {
      fn = function(. args) {
        let lastArg = args.length > 0 ? args[args.length - 1] : null;
        let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
        let hasSuccCB = typeof lastArg === 'function';
        let hasErrorCB = typeof secondLastArg === 'function';
        hasErrorCB && invariant(
          hasSuccCB,
          'Cannot have a non-function arg after a function arg.'
        );
        let numCBs = hasSuccCB + hasErrorCB;
        let onSucc = hasSuccCB ? lastArg : null;
        let onFail = hasErrorCB ? secondLastArg : null;
        args = args.slice(0, args.length - numCBs);
        return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }
Copy the code

Eventually ToastAndroid. The show is called self * * *. * * * __nativeCall, then look at the function, in the if judgment if two call time interval is more than 5 ms, is called global. NativeFlushQueueImmediate (this. _queue), where this._queue is an array containing: MODULE_IDS, METHOD_IDS, PARAMS, _callID

//react-native/Libraries/Utilities/MessageQueue.js
let MIN_TIME_BETWEEN_FLUSHES_MS = 5;
__nativeCall(module, method, params, onFail, onSucc) {
    if (onFail || onSucc) {
      // eventually delete old debug info
      (this._callbackID > (1 << 5&& ())this._debugInfo[this._callbackID >> 5] = null);

      this._debugInfo[this._callbackID >> 1] = [module, method];
      onFail && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onSucc;
    }

    global.nativeTraceBeginAsyncFlow &&
      global.nativeTraceBeginAsyncFlow(TRACE_TAG_REACT_APPS, 'native'.this._callID);
    this._callID++;

    this._queue[MODULE_IDS].push(module);
    this._queue[METHOD_IDS].push(method);
    this._queue[PARAMS].push(params);

    var now = new Date().getTime();
    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__ && SPY_MODE && isFinite(module)) {
      console.log('JS->N : ' + this._remoteModuleTable[module] + '. ' +
        this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ') '); }}Copy the code

This is similar to the Java layer processing logic, which is unified call logic, Java call JS layer modules are processed by dynamic proxy in javascript. The JS layer calls the Java module and unifies the logic into the **__nativeCall** function in messagequeue.js. So the above global. NativeFlushQueueImmediate (enclosing _queue); Where is the method injected into global? Guess C++ layer, then look below.

C++ registration and delivery

Global nativeFlushQueueImmediate method is in the c + + layer for injection, to JSCExecutor. CPP, through installGlobalFunction injection function.

//react-native/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
    m_flushImmediateCallback(cb) {
  m_context = JSGlobalContextCreateInGroup(nullptr.nullptr);
  m_messageQueueThread = JMessageQueueThread::currentMessageQueueThread();
  s_globalContextRefToJSCExecutor[m_context] = this;
  installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
  installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook);
  installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow);
  installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
  installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
  installGlobalFunction(m_context, "nativeTerminateWorker", nativeTerminateWorker);

  #ifdef WITH_FB_JSC_TUNING
  configureJSCForAndroid();
  #endif

  #ifdef WITH_JSC_EXTRA_TRACING
  addNativeTracingHooks(m_context);
  addNativeProfilingHooks(m_context);
  addNativePerfLoggingHooks(m_context);
  #endif

  #ifdef WITH_FB_MEMORY_PROFILING
  addNativeMemoryHooks(m_context);
  #endif
}

// Native JS hooks
static JSValueRef nativeFlushQueueImmediate(
    JSContextRef ctx,
    JSObjectRef function,
    JSObjectRef thisObject,
    size_t argumentCount,
    const JSValueRef arguments[],
    JSValueRef *exception) {
  if(argumentCount ! =1) {
    *exception = createErrorString(ctx, "Got wrong number of args");
    return JSValueMakeUndefined(ctx);
  }

  JSCExecutor *executor;
  try {
    executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
  } catch (std::out_of_range& e) {
    *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
    return JSValueMakeUndefined(ctx);
  }

  std: :string resStr = Value(ctx, arguments[0]).toJSONString();

  executor->flushQueueImmediate(resStr);

  return JSValueMakeUndefined(ctx);
}

void JSCExecutor::flushQueueImmediate(std: :string queueJSON) {
  m_flushImmediateCallback(queueJSON, false);
}
Copy the code

M_flushImmediateCallback (queueJSON, false) is finally called, where m_flushImmediateCallback is passed in the constructor JSCExecutor:

//react-native/ReactAndroid/src/main/jni/react/JSCExecutor.cpp
JSCExecutor::JSCExecutor(FlushImmediateCallback cb) : m_flushImmediateCallback(cb)
Copy the code

In the header file jscexecutor.h, where JSCExecutor is constructed from JSCExecutorFactory:

//react-native/ReactAndroid/src/main/jni/react/JSCExecutor.h
class JSCExecutorFactory : public JSExecutorFactory {
public:
  virtual std: :unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback cb) override;
};

class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner {
public:
  /** * Should be invoked from the JS thread. */
  explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback); ~JSCExecutor() override; . }Copy the code

As you can see in bridge. CPP, the Callback that ends up being called is the Callback passed in when the Bridge was constructed

//react-native/ReactAndroid/src/main/jni/react/Bridge.cpp
class JSThreadState {
public:
  JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
    m_callback(callback)
  {
    m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std: :string queueJSON, bool isEndOfBatch) {
      m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
    });
  }
}

Bridge::Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback) :
  m_callback(callback),
  m_destroyed(std: :shared_ptr<bool> (new bool(false)))
{
  auto destroyed = m_destroyed;
  auto proxyCallback = [this, destroyed] (std: :vector<MethodCall> calls, bool isEndOfBatch) {
    if (*destroyed) {
      return;
    }
    m_callback(std::move(calls), isEndOfBatch);
  };
  m_threadState.reset(new JSThreadState(jsExecutorFactory, std::move(proxyCallback)));
}
Copy the code

M_callback (parseMethodCalls(queueJSON), false /* = isEndOfBatch */); We parse this._queue from the previous js layer into a vector

//react-native/ReactAndroid/src/main/jni/react/MethodCall.cpp
std: :vector<MethodCall> parseMethodCalls(const std: :string& json) {
  folly::dynamic jsonData = folly::parseJson(json);

  if (jsonData.isNull()) {
    return {};
  }

  if(! jsonData.isArray()) { jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,"Did not get valid calls back from JS: %s", jsonData.typeName());
  }

  if (jsonData.size() < REQUEST_PARAMSS + 1) {
    jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,
                               "Did not get valid calls back from JS: size == %d", jsonData.size());
  }

  auto moduleIds = jsonData[REQUEST_MODULE_IDS];
  auto methodIds = jsonData[REQUEST_METHOD_IDS];
  auto params = jsonData[REQUEST_PARAMSS];
  int  callId = - 1;

  if(! moduleIds.isArray() || ! methodIds.isArray() || ! params.isArray()) { jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,"Did not get valid calls back from JS: %s",
                               json.c_str());
  }

  if (jsonData.size() > REQUEST_CALLID) {
    if(! jsonData[REQUEST_CALLID].isInt()) { jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,"Did not get valid calls back from JS: %s",
                               json.c_str());
    } else{ callId = jsonData[REQUEST_CALLID].getInt(); }}std: :vector<MethodCall> methodCalls;
  for (size_t i = 0; i < moduleIds.size(); i++) {
    auto paramsValue = params[i];
    if(! paramsValue.isArray()) { jni::throwNewJavaException(jni::gJavaLangIllegalArgumentException,"Call argument isn't an array");
    }

    methodCalls.emplace_back(
      moduleIds[i].getInt(),
      methodIds[i].getInt(),
      std::move(params[i]),
      callId);

    // only incremement callid if contains valid callid as callid is optionalcallId += (callId ! =- 1)?1 : 0;
  }

  return methodCalls;
}

struct MethodCall {
  int moduleId;
  int methodId;
  folly::dynamic arguments;
  int callId;

  MethodCall(int mod, int meth, folly::dynamic args, int cid)
    : moduleId(mod)
    , methodId(meth)
    , arguments(std::move(args))
    , callId(cid) {}
};
Copy the code

So where is the Bridge constructed? In onload. CPP, construct the bridge in the create function, which is dynamically registered with registerNatives. See here that the Java layer will initiate the bridge via JNI calls

//react-native/ReactAndroid/src/main/jni/react/OnLoad.cpp
static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback, jobject callbackQueueThread) {
  auto weakCallback = createNew<WeakReference>(callback);
  auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
  auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (std: :vector<MethodCall> calls, bool isEndOfBatch) {
    dispatchCallbacksToJava(weakCallback, weakCallbackQueueThread, std::move(calls), isEndOfBatch);
  };
  auto nativeExecutorFactory = extractRefPtr<JSExecutorFactory>(env, executor);
  auto bridge = createNew<Bridge>(nativeExecutorFactory, bridgeCallback);
  setCountableForJava(env, obj, std::move(bridge));
}

extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  return initialize(vm, [] {
    // get the current env
    JNIEnv* env = Environment::current();

    auto readableTypeClass = findClassLocal("com/facebook/react/bridge/ReadableType");
    type::gReadableReactType = (jclass)env->NewGlobalRef(readableTypeClass.get());
    type::initialize(env);

    NativeArray::registerNatives();
    ReadableNativeArray::registerNatives();
    WritableNativeArray::registerNatives();
    JNativeRunnable::registerNatives();
    registerJSLoaderNatives();

    registerNatives("com/facebook/react/bridge/JSCJavaScriptExecutor", {
      makeNativeMethod("initialize", executors::createJSCExecutor),
    });

    registerNatives("com/facebook/react/bridge/ProxyJavaScriptExecutor", {
        makeNativeMethod(
          "initialize"."(Lcom/facebook/react/bridge/JavaJSExecutor;) V",
          executors::createProxyExecutor),
    });

    jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
    bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call"."(IILcom/facebook/react/bridge/ReadableNativeArray;) V");
    bridge::gOnBatchCompleteMethod = env->GetMethodID(callbackClass, "onBatchComplete"."()V");

    jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
    bridge::gLogMarkerMethod = env->GetStaticMethodID(markerClass, "logMarker"."(Ljava/lang/String;) V");

    registerNatives("com/facebook/react/bridge/ReactBridge", {
        makeNativeMethod("initialize"."(Lcom/facebook/react/bridge/JavaScriptExecutor; Lcom/facebook/react/bridge/ReactCallback; Lcom/facebook/react/bridge/queue/MessageQueueThread;) V", bridge::create),
        makeNativeMethod(
          "loadScriptFromAssets"."(Landroid/content/res/AssetManager; Ljava/lang/String;) V",
          bridge::loadScriptFromAssets),
        makeNativeMethod("loadScriptFromFile", bridge::loadScriptFromFile),
        makeNativeMethod("callFunction", bridge::callFunction),
        makeNativeMethod("invokeCallback", bridge::invokeCallback),
        makeNativeMethod("setGlobalVariable", bridge::setGlobalVariable),
        makeNativeMethod("supportsProfiling", bridge::supportsProfiling),
        makeNativeMethod("startProfiler", bridge::startProfiler),
        makeNativeMethod("stopProfiler", bridge::stopProfiler),
        makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate),
        makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical),

    });
Copy the code

For (auto&& Call: Calls) indicates that JS calls to Java can make multiple calls, which are sent to the Java layer at once

//react-native/ReactAndroid/src/main/jni/react/OnLoad.cpp
static void dispatchCallbacksToJava(const RefPtr<WeakReference>& weakCallback,
                                    const RefPtr<WeakReference>& weakCallbackQueueThread,
                                    std::vector<MethodCall>&& calls,
                                    bool isEndOfBatch) {
  auto env = Environment::current();
  if (env->ExceptionCheck()) {
    FBLOGW("Dropped calls because of pending exception");
    return;
  }

  ResolvedWeakReference callbackQueueThread(weakCallbackQueueThread);
  if(! callbackQueueThread) { FBLOGW("Dropped calls because of callback queue thread went away");
    return;
  }

  auto runnableFunction = std::bind([weakCallback, isEndOfBatch] (std::vector<MethodCall>& calls) {
    auto env = Environment::current();
    if (env->ExceptionCheck()) {
      FBLOGW("Dropped calls because of pending exception");
      return;
    }
    ResolvedWeakReference callback(weakCallback);
    if (callback) {
      for (auto&& call : calls) {
        makeJavaCall(env, callback, std::move(call));
        if (env->ExceptionCheck()) {
          return; }}if (isEndOfBatch) {
        signalBatchComplete(env, callback);
      }
    }
  }, std::move(calls));

  auto jNativeRunnable = runnable::createNativeRunnable(env, std::move(runnableFunction));
  queue::enqueueNativeRunnableOnQueue(env, callbackQueueThread, jNativeRunnable.get());
}

static void makeJavaCall(JNIEnv* env, jobject callback, MethodCall&& call) {
  if (call.arguments.isNull()) {
    return;
  }

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

  auto newArray = ReadableNativeArray::newObjectCxxArgs(std::move(call.arguments));
  env->CallVoidMethod(callback, gCallbackMethod, call.moduleId, call.methodId, newArray.get());
}

static jmethodID gCallbackMethod;

extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  return initialize(vm, [] {
    // get the current env
    JNIEnv* env = Environment::current();

    auto readableTypeClass = findClassLocal("com/facebook/react/bridge/ReadableType");
    type::gReadableReactType = (jclass)env->NewGlobalRef(readableTypeClass.get());
    type::initialize(env); NativeArray::registerNatives(); ReadableNativeArray::registerNatives(); WritableNativeArray::registerNatives(); JNativeRunnable::registerNatives(); registerJSLoaderNatives(); . jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
    bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call"."(IILcom/facebook/react/bridge/ReadableNativeArray;) V"); . }Copy the code

At the top, C++ is called back to the Java layer via JNI.

3. The Java layer processing

How to find the Java equivalent method in the C++ layer above?

GCallbackMethod is found through GetMethodID method, including callbackClass is com/facebook/react/bridge/ReactCallback, call the call method of a class, there are three parameters:

int,int, com/facebook/react/bridge/ReadableNativeArray

Take a look at C++ and Java:

//react-native/ReactAndroid/src/main/jni/react/OnLoad.cpp        
makeNativeMethod("initialize"."(Lcom/facebook/react/bridge/JavaScriptExecutor; Lcom/facebook/react/bridge/ReactCallback; Lcom/facebook/react/bridge/queue/MessageQueueThread;) V", bridge::create),

Copy the code
public ReactBridge( JavaScriptExecutor jsExecutor, ReactCallback callback, MessageQueueThread nativeModulesQueueThread) {
    mJSExecutor = jsExecutor;
    mCallback = callback;
    mNativeModulesQueueThread = nativeModulesQueueThread;
    initialize(jsExecutor, callback, mNativeModulesQueueThread);
  }

private native void initialize( JavaScriptExecutor jsExecutor, ReactCallback callback, MessageQueueThread nativeModulesQueueThread);
Copy the code

Finally, callback is ReactCallback in the Java layer:

@DoNotStrip
public interface ReactCallback {

  @DoNotStrip
  void call(int moduleId, int methodId, ReadableNativeArray parameters);

  @DoNotStrip
  void onBatchComplete(a);
}
Copy the code

Bridge that is in initialize the CatalystInstanceImpl. In Java:

//com/facebook/react/bridge/CatalystInstanceImpl.java
private ReactBridge initializeBridge( JavaScriptExecutor jsExecutor, JavaScriptModulesConfig jsModulesConfig) {
    mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();
    Assertions.assertCondition(mBridge == null."initializeBridge should be called once");

    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactBridgeCtor");
    ReactBridge bridge;
    try {
      bridge = new ReactBridge(
          jsExecutor,
          new NativeModulesReactCallback(),
          mCatalystQueueConfiguration.getNativeModulesQueueThread());
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "setBatchedBridgeConfig");
    try {
      bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
      bridge.setGlobalVariable(
          "__RCTProfileIsProfiling",
          Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ? "true" : "false");
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    return bridge;
  }
Copy the code

Can see interface ReactCallback implementation class is NativeModulesReactCallback, is CatalystInstanceImpl inner class:

//com/facebook/react/bridge/CatalystInstanceImpl.java
private class NativeModulesReactCallback implements ReactCallback {

    @Override
    public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
      mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();

      // Suppress any callbacks if destroyed - will only lead to sadness.
      if (mDestroyed) {
        return;
      }

      mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
    }

    @Override
    public void onBatchComplete(a) {
      mCatalystQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();

      // The bridge may have been destroyed due to an exception during the batch. In that case
      // native modules could be in a bad state so we don't want to call anything on them. We
      // still want to trigger the debug listener since it allows instrumentation tests to end and
      // check their assertions without waiting for a timeout.
      if(! mDestroyed) { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,"onBatchComplete");
        try {
          mJavaRegistry.onBatchComplete();
        } finally{ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } } decrementPendingJSCalls(); }}Copy the code

Called to NativeModuleRegistry, where ModuleDefinition is a static private inner class of NativeModuleRegistry, which is also a wrapper around each NativeModule:

//com/facebook/react/bridge/NativeModuleRegistry.java
void call(
      CatalystInstance catalystInstance,
      int moduleId,
      int methodId,
      ReadableNativeArray parameters) {
    ModuleDefinition definition = mModuleTable.get(moduleId);
    if (definition == null) {
      throw new RuntimeException("Call to unknown module: " + moduleId);
    }
    definition.call(catalystInstance, methodId, parameters);
  }

public static class Builder {

    private final HashMap<String, NativeModule> mModules = MapBuilder.newHashMap();

    public Builder add(NativeModule module) {
      NativeModule existing = mModules.get(module.getName());
      if(existing ! =null&&!module.canOverrideExistingModule()) {
        throw new IllegalStateException("Native module " + module.getClass().getSimpleName() +
            " tried to override " + existing.getClass().getSimpleName() + " for module name " +
            module.getName() + ". If this was your intention, return true from " +
            module.getClass().getSimpleName() + "#canOverrideExistingModule()");
      }
      mModules.put(module.getName(), module);
      return this;
    }

    public NativeModuleRegistry build(a) {
      List<ModuleDefinition> moduleTable = new ArrayList<>();
      Map<Class<? extends NativeModule>, NativeModule> moduleInstances = new HashMap<>();

      int idx = 0;
      for (NativeModule module : mModules.values()) {
        ModuleDefinition moduleDef = new ModuleDefinition(idx++, module.getName(), module);
        moduleTable.add(moduleDef);
        moduleInstances.put(module.getClass(), module);
      }
      return newNativeModuleRegistry(moduleTable, moduleInstances); }}private static class ModuleDefinition {
    public final int id;
    public final String name;
    public final NativeModule target;
    public final ArrayList<MethodRegistration> methods;

    public ModuleDefinition(int id, String name, NativeModule target) {
      this.id = id;
      this.name = name;
      this.target = target;
      this.methods = new ArrayList<MethodRegistration>();

      for (Map.Entry<String, NativeModule.NativeMethod> entry : target.getMethods().entrySet()) {
        this.methods.add(
          new MethodRegistration(
            entry.getKey(), "NativeCall__" + target.getName() + "_"+ entry.getKey(), entry.getValue())); }}public void call(
        CatalystInstance catalystInstance,
        int methodId,
        ReadableNativeArray parameters) {
      MethodRegistration method = this.methods.get(methodId);
      Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, method.tracingName);
      try {
        this.methods.get(methodId).method.invoke(catalystInstance, parameters);
      } finally{ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }}}Copy the code

In the ModuleDefinition above, the call method finally calls MethodRegistration method.invoke:

//com/facebook/react/bridge/NativeModuleRegistry.java
private static class MethodRegistration {
    public MethodRegistration(String name, String tracingName, NativeModule.NativeMethod method) {
      this.name = name;
      this.tracingName = tracingName;
      this.method = method;
    }

    public String name;
    public String tracingName;
    public NativeModule.NativeMethod method;
  }
Copy the code

Call to NativeModule NativeMethod, is NativeModule internal interface:

//com/facebook/react/bridge/NativeModule.java
public interface NativeModule {
  interface NativeMethod {
    void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters);
    String getType(a);
  }
Copy the code

The implementation class is JavaMethod, which is the private inner class of BaseJavaModule, and finally reflects mMethod.invoke(BaseJavaModule. This, mArguments); Calls the methods of the target BaseJavaModule.

//com/facebook/react/bridge/BaseJavaModule.java
private class JavaMethod implements NativeMethod {

    private Method mMethod;
    private final ArgumentExtractor[] mArgumentExtractors;
    private final Object[] mArguments;
    private String mType = METHOD_TYPE_REMOTE;
    private final int mJSArgumentsNeeded;

    public JavaMethod(Method method) {
      mMethod = method;
      Class[] parameterTypes = method.getParameterTypes();
      mArgumentExtractors = buildArgumentExtractors(parameterTypes);
      // Since native methods are invoked from a message queue executed on a single thread, it is
      // save to allocate only one arguments object per method that can be reused across calls
      mArguments = new Object[parameterTypes.length];
      mJSArgumentsNeeded = calculateJSArgumentsNeeded();
    }

    private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
      ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
      for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
        Class argumentClass = paramTypes[i];
        if (argumentClass == Boolean.class || argumentClass == boolean.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
        } else if (argumentClass == Integer.class || argumentClass == int.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_INTEGER;
        } else if (argumentClass == Double.class || argumentClass == double.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_DOUBLE;
        } else if (argumentClass == Float.class || argumentClass == float.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_FLOAT;
        } else if (argumentClass == String.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_STRING;
        } else if (argumentClass == Callback.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
        } else if (argumentClass == Promise.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_PROMISE;
          Assertions.assertCondition(
              i == paramTypes.length - 1."Promise must be used as last parameter only");
          mType = METHOD_TYPE_REMOTE_ASYNC;
        } else if (argumentClass == ReadableMap.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_MAP;
        } else if (argumentClass == ReadableArray.class) {
          argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY;
        } else {
          throw new RuntimeException(
              "Got unknown argument class: "+ argumentClass.getSimpleName()); }}returnargumentExtractors; }...@Override
    public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
      Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
      try {
        if(mJSArgumentsNeeded ! = parameters.size()) {throw new NativeArgumentsParseException(
              BaseJavaModule.this.getName() + "." + mMethod.getName() + " got " +
              parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
        }

        int i = 0, jsArgumentsConsumed = 0;
        try {
          for(; i < mArgumentExtractors.length; i++) { mArguments[i] = mArgumentExtractors[i].extractArgument( catalystInstance, parameters, jsArgumentsConsumed); jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded(); }}catch (UnexpectedNativeTypeException e) {
          throw new NativeArgumentsParseException(
              e.getMessage() + " (constructing arguments for " + BaseJavaModule.this.getName() +
              "." + mMethod.getName() + " at argument index " +
              getAffectedRange(jsArgumentsConsumed, mArgumentExtractors[i].getJSArgumentsNeeded()) +
              ")",
              e);
        }

        try {
          mMethod.invoke(BaseJavaModule.this, mArguments);
        } catch (IllegalArgumentException ie) {
          throw new RuntimeException(
              "Could not invoke " + BaseJavaModule.this.getName() + "." + mMethod.getName(), ie);
        } catch (IllegalAccessException iae) {
          throw new RuntimeException(
              "Could not invoke " + BaseJavaModule.this.getName() + "." + mMethod.getName(), iae);
        } catch (InvocationTargetException ite) {
          // Exceptions thrown from native module calls end up wrapped in InvocationTargetException
          // which just make traces harder to read and bump out useful information
          if (ite.getCause() instanceof RuntimeException) {
            throw (RuntimeException) ite.getCause();
          }
          throw new RuntimeException(
              "Could not invoke " + BaseJavaModule.this.getName() + "."+ mMethod.getName(), ite); }}finally{ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }}Copy the code

ArgumentExtractor ArgumentExtractor ArgumentExtractor ArgumentExtractor

//com/facebook/react/bridge/BaseJavaModule.java
ARGUMENT_EXTRACTOR_BOOLEAN
ARGUMENT_EXTRACTOR_DOUBLE
ARGUMENT_EXTRACTOR_FLOAT
ARGUMENT_EXTRACTOR_INTEGER
ARGUMENT_EXTRACTOR_STRING
ARGUMENT_EXTRACTOR_ARRAY
ARGUMENT_EXTRACTOR_MAP
ARGUMENT_EXTRACTOR_CALLBACK
ARGUMENT_EXTRACTOR_PROMISE
Copy the code

This completes the analysis of the process of JavaScript calling Java modules.

4. To summarize

The process of JavaScript calling a Java module is a bit simpler than its reverse process. The whole process is summarized as follows:

Welcome to the public account JueCode