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

You all know that Java and JS communication in Android development can be achieved through WebView, including registering JSBridge or intercepting in the interface. However, React Native does not use WebView control, but realizes Java and JS communication based on WebKit kernel. In other words, the communication between Java and JS is realized through the middle layer C++. React Native allows Java to communicate with JS.

React Native calls all actions are initiated from Native, and user operations are directly directed to Native. Therefore, this communication model can also be regarded as Native initiating a session and then Javascript responding.

React Native: How to use JavaScript to call Java

Java to be able to call JS needs to register JS modules in the Java layer, first look at the JS module registration process.

JavaScriptModule module registration (Java layer)

Common JS modules are registered in the system. Core JS modules such as AppRegistry(RN component registry module) and RCTEventEmitter(event emission module) are registered in createJSModules of CoreModulesPackage.

class CoreModulesPackage implements ReactPackage{
  @Override
  public List<NativeModule> createNativeModules(
      ReactApplicationContext catalystApplicationContext) {
    ......
    return Arrays.<NativeModule>asList(
        new AnimationsDebugModule(
            catalystApplicationContext,
            mReactInstanceManager.getDevSupportManager().getDevSettings()),
        new AndroidInfoModule(),
        new DeviceEventManagerModule(catalystApplicationContext, mHardwareBackBtnHandler),
        new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()),
        new Timing(catalystApplicationContext),
        new SourceCodeModule(
            mReactInstanceManager.getSourceUrl(),
            mReactInstanceManager.getDevSupportManager().getSourceMapUrl()),
        uiManagerModule,
        new DebugComponentOwnershipModule(catalystApplicationContext));
  }
  
  @Override
  public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Arrays.asList(
        DeviceEventManagerModule.RCTDeviceEventEmitter.class,
        JSTimersExecution.class,
        RCTEventEmitter.class,
        RCTNativeAppEventEmitter.class,
        AppRegistry.class,
        com.facebook.react.bridge.Systrace.class,
        DebugComponentOwnershipModule.RCTDebugComponentOwnership.class);
  }
}

public interface ReactPackage {

  /**
   * @param reactContext react application context that can be used to create modules
   * @returnlist of native modules to register with the newly created catalyst instance */ List<NativeModule> createNativeModules(ReactApplicationContext reactContext); / * * * @return list of JS modules to register with the newly created catalyst instance.
   *
   * IMPORTANT: Note that only modules that needs to be accessible from the native code should be
   * listed here. Also listing a native module here doesn't imply that the JS implementation of it * will be automatically included in the JS bundle. */ List
      
       > createJSModules(); /** * @return a list of view managers that should be registered with {@link UIManagerModule} */ List
       
         createViewManagers(ReactApplicationContext reactContext); }
       
      >Copy the code

Take the JavaScriptModule module RCTEventEmitter as an example to see the implementation of JS modules in the Java layer.

All JS layer components implement the JavaScriptModule interface, such as RCTEventEmitter:

public interface RCTEventEmitter extends JavaScriptModule {
  public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
  public void receiveTouches(
      String eventName,
      WritableArray touches,
      WritableArray changedIndices);
}
Copy the code

Javascript modules inherit javascript Modules in the Java layer, and there is no specific implementation. So the problem is that you can’t call an interface. It seems that you can’t track it down here. In fact, we can think of another way, JS module must be registered in Native, let’s go to ReactInstanceManagerImpl createReactContext method to see how to register.

All NativeModule and JSModule are registered in the ReactInstanceManagerImpl

There is a lot of code in the method, but the logic is not complicated. I have annotated several key steps in the code directly. I will not go into more details.

private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    FLog.i(ReactConstants.TAG, "Creating react context."); / / Native modules NativeModuleRegistry Builder nativeRegistryBuilder = new NativeModuleRegistry. Builder (); / / JS module JavaScriptModulesConfig. Builder jsModulesBuilder = new JavaScriptModulesConfig. Builder (); //React Context ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);if(mUseDeveloperSupport) { reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager); } // Handle CoreModules-- including Native modules and JS modules systrace.beginSection (systrace.trace_tag_react_java_bridge,"createAndProcessCoreModulesPackage"); try { CoreModulesPackage coreModulesPackage = new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider); processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } // The module that handles user registrationfor (ReactPackage reactPackage : mPackages) {
      Systrace.beginSection(
          Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
          "createAndProcessCustomReactPackage");
      try {
        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
      } finally {
        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
      }
    }

  //buildNativeModuleRegistry
    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
    NativeModuleRegistry nativeModuleRegistry;
    try {
       nativeModuleRegistry = nativeRegistryBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

  //buildJSModuleConfig
    Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildJSModuleConfig"); JavaScriptModulesConfig javaScriptModulesConfig; try { javaScriptModulesConfig = jsModulesBuilder.build(); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }... / / createCatalystInstance - Java/JS/c + + CatalystInstanceImpl tripartite communication manager. The Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSModulesConfig(javaScriptModulesConfig) .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(exceptionHandler); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,"createCatalystInstance");
    CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    if(mBridgeIdleDebugListener ! = null) { catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener); } reactContext.initializeWithInstance(catalystInstance); //runJSBundle Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,"runJSBundle");
    try {
      catalystInstance.runJSBundle();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }

    ReactMarker.logMarker("CREATE_REACT_CONTEXT_END");
    return reactContext;
  }
Copy the code

See JavaScriptModulesConfig, mainly for each JavaScriptModule tectonic JavaScriptModuleRegistration in mModules

/**
 * Class stores configuration of javascript modules that can be used across the bridge
 */
public class JavaScriptModulesConfig {

  private final List<JavaScriptModuleRegistration> mModules;

  private JavaScriptModulesConfig(List<JavaScriptModuleRegistration> modules) {
    mModules = modules;
  }

  /*package*/ List<JavaScriptModuleRegistration> getModuleDefinitions() {
    return mModules;
  }

  /*package*/ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
    jg.writeStartObject();
    for (JavaScriptModuleRegistration registration : mModules) {
      jg.writeObjectFieldStart(registration.getName());
      appendJSModuleToJSONObject(jg, registration);
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }

  private void appendJSModuleToJSONObject(
      JsonGenerator jg,
      JavaScriptModuleRegistration registration) throws IOException {
    jg.writeObjectField("moduleID", registration.getModuleId());
    jg.writeObjectFieldStart("methods");
    for (Method method : registration.getMethods()) {
      jg.writeObjectFieldStart(method.getName());
      jg.writeObjectField("methodID", registration.getMethodId(method));
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }

  public static class Builder {

    private int mLastJSModuleId = 0;
    private List<JavaScriptModuleRegistration> mModules =
        new ArrayList<JavaScriptModuleRegistration>();

    public Builder add(Class<? extends JavaScriptModule> moduleInterfaceClass) {
      int moduleId = mLastJSModuleId++;
      mModules.add(new JavaScriptModuleRegistration(moduleId, moduleInterfaceClass));
      return this;
    }

    public JavaScriptModulesConfig build() {
      returnnew JavaScriptModulesConfig(mModules); }}}Copy the code
/**
 * Registration info for a {@link JavaScriptModule}. 
 	Maps its methods to method ids.
 */
class JavaScriptModuleRegistration {

  private final int mModuleId;
  private final Class<? extends JavaScriptModule> mModuleInterface;
  private final Map<Method, Integer> mMethodsToIds;
  private final Map<Method, String> mMethodsToTracingNames;

  JavaScriptModuleRegistration(int moduleId, Class<? extends JavaScriptModule> moduleInterface) {
    mModuleId = moduleId;
    mModuleInterface = moduleInterface;

    mMethodsToIds = MapBuilder.newHashMap();
    mMethodsToTracingNames = MapBuilder.newHashMap();
    final Method[] declaredMethods = mModuleInterface.getDeclaredMethods();
    Arrays.sort(declaredMethods, new Comparator<Method>() {
      @Override
      public int compare(Method lhs, Method rhs) {
        returnlhs.getName().compareTo(rhs.getName()); }}); // Methods are sorted by name so we can dupe check and have obvious ordering String previousName = null;for(int i = 0; i < declaredMethods.length; i++) { Method method = declaredMethods[i]; String name = method.getName(); Assertions.assertCondition( ! name.equals(previousName),"Method overloading is unsupported: " + mModuleInterface.getName() + "#" + name);
      previousName = name;

      mMethodsToIds.put(method, i);
      mMethodsToTracingNames.put(method, "JSCall__" + getName() + "_"+ method.getName()); }}... }Copy the code

Here you have all of the JavaScriptModules scanned and stored in JavaScriptModulesConfig. How does the RN framework work? To review the ReactInstanceManagerImpl, will JavaScriptModulesConfig used to instantiate catalystInstance, this is three sides communication station.

/ / createCatalystInstance - Java/JS/c + + CatalystInstanceImpl tripartite communication manager. The Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder() .setCatalystQueueConfigurationSpec(CatalystQueueConfigurationSpec.createDefault()) .setJSExecutor(jsExecutor) .setRegistry(nativeModuleRegistry) .setJSModulesConfig(javaScriptModulesConfig) .setJSBundleLoader(jsBundleLoader) .setNativeModuleCallExceptionHandler(exceptionHandler); Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,"createCatalystInstance");
    CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
    }
Copy the code

CatalystInstance is an interface, and the implementation class is CatalystInstanceImpl, which follows the constructor:

private CatalystInstanceImpl( final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec, final JavaScriptExecutor jsExecutor, final NativeModuleRegistry registry, final JavaScriptModulesConfig jsModulesConfig, final JSBundleLoader jsBundleLoader, NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) { ... mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig); . }Copy the code

Through JavaScriptModulesConfig instantiation JavaScriptModuleRegistry, analysis before JavaScriptModule is implements in Java layer JavaScriptModule interface, So how do you call a method? Here is for each interface by dynamic proxy instantiate a proxy objects, Java calls JS method unified distribution to invoke in JavaScriptModuleInvocationHandler for processing, Is invoked in the invoke mCatalystInstance callFunction

JavaScriptModuleRegistry is the obvious next step:

/**
 * Class responsible for holding all the {@link JavaScriptModule}s registered to this
 * {@link CatalystInstance}. 
 Uses Java proxy objects to dispatch method calls on JavaScriptModules
 * to the bridge using the corresponding module and method ids so the proper function is executed in
 * JavaScript.
 */
/*package*/ class JavaScriptModuleRegistry {

  private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;

  public JavaScriptModuleRegistry(
      CatalystInstanceImpl instance,
      JavaScriptModulesConfig config) {
    mModuleInstances = new HashMap<>();
    for (JavaScriptModuleRegistration registration : config.getModuleDefinitions()) {
      Class<? extends JavaScriptModule> moduleInterface = registration.getModuleInterface();
      JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
          moduleInterface.getClassLoader(),
          new Class[]{moduleInterface},
          new JavaScriptModuleInvocationHandler(instance, registration));

      mModuleInstances.put(moduleInterface, interfaceProxy);
    }
  }

  public <T extends JavaScriptModule> T getJavaScriptModule(Class<T> moduleInterface) {
    return (T) Assertions.assertNotNull(
        mModuleInstances.get(moduleInterface),
        "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
  }

  private static class JavaScriptModuleInvocationHandler implements InvocationHandler {

    private final CatalystInstanceImpl mCatalystInstance;
    private final JavaScriptModuleRegistration mModuleRegistration;

    public JavaScriptModuleInvocationHandler(
        CatalystInstanceImpl catalystInstance,
        JavaScriptModuleRegistration moduleRegistration) {
      mCatalystInstance = catalystInstance;
      mModuleRegistration = moduleRegistration;
    }

    @Override
    public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String tracingName = mModuleRegistration.getTracingName(method);
      mCatalystInstance.callFunction(
          mModuleRegistration.getModuleId(),
          mModuleRegistration.getMethodId(method),
          Arguments.fromJavaArgs(args),
          tracingName);
      returnnull; }}}Copy the code

In Java calls JS module method, will call the invoke method through a proxy object, invoke method invokes the CatalystInstance. CallFunction, CatalystInstance is an interface, Implement the CatalystInstanceImpl class and see the callFunction in CatalystInstanceImpl:

/* package */ void callFunction(
      final int moduleId,
      final int methodId,
      final NativeArray arguments,
      final String tracingName) {
    if (mDestroyed) {
      FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
      return;
    }

    incrementPendingJSCalls();

    final int traceID = mTraceID++;
    Systrace.startAsyncFlow(
        Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
        tracingName,
        traceID);

    mCatalystQueueConfiguration.getJSQueueThread().runOnQueue(
        new Runnable() {
          @Override
          public void run() {
            mCatalystQueueConfiguration.getJSQueueThread().assertIsOnThread();

            Systrace.endAsyncFlow(
                Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
                tracingName,
                traceID);

            if (mDestroyed) {
              return; } Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, tracingName); try { Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); }}}); }Copy the code

All communication requests from the Java layer to the Javascript layer go through reactbridge.callFunction

Call the ReactBridge Native function in the JSQueueThread thread. This is where you switch from Java to C++

/**
 * Interface to the JS execution environment and means of transport for messages Java<->JS.
 */
@DoNotStrip
public class ReactBridge extends Countable {

  /* package */ static final String REACT_NATIVE_LIB = "reactnativejni";

  static {
    SoLoader.loadLibrary(REACT_NATIVE_LIB);
  }

  private final ReactCallback mCallback;
  private final JavaScriptExecutor mJSExecutor;
  private final MessageQueueThread mNativeModulesQueueThread;

  /**
   * @param jsExecutor the JS executor to use to run JS
   * @param callback the callback class used to invoke native modules
   * @param nativeModulesQueueThread the MessageQueueThread the callbacks should be invoked on
   */
  public ReactBridge(
      JavaScriptExecutor jsExecutor,
      ReactCallback callback,
      MessageQueueThread nativeModulesQueueThread) {
    mJSExecutor = jsExecutor;
    mCallback = callback;
    mNativeModulesQueueThread = nativeModulesQueueThread;
    initialize(jsExecutor, callback, mNativeModulesQueueThread);
  }

  @Override
  public void dispose() {
    mJSExecutor.close();
    mJSExecutor.dispose();
    super.dispose();
  }

  public void handleMemoryPressure(MemoryPressure level) {
    switch (level) {
      case MODERATE:
        handleMemoryPressureModerate();
        break;
      case CRITICAL:
        handleMemoryPressureCritical();
        break;
      default:
        throw new IllegalArgumentException("Unknown level: " + level);
    }
  }

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

  /**
   * All native functions are not thread safe and appropriate queues should be used
   */
  public native void loadScriptFromAssets(AssetManager assetManager, String assetName);
  public native void loadScriptFromFile(@Nullable String fileName, @Nullable String sourceURL);
  public native void callFunction(int moduleId, int methodId, NativeArray arguments);
  public native void invokeCallback(int callbackID, NativeArray arguments);
  public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
  public native boolean supportsProfiling();
  public native void startProfiler(String title);
  public native void stopProfiler(String title, String filename);
  private native void handleMemoryPressureModerate();
  private native void handleMemoryPressureCritical();
}
Copy the code

If you go back to the initialization of the ReactBridge in CatalystInstanceImpl, the initialization will ReactBridge call setGlobalVariable, which is a Native function registered with the C++ layer. First take a look at buildModulesConfigJSONProperty (mJavaRegistry, jsModulesConfig) do.

private CatalystInstanceImpl(
      final CatalystQueueConfigurationSpec catalystQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModulesConfig jsModulesConfig,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
	......
    try {
      mBridge = mCatalystQueueConfiguration.getJSQueueThread().callOnQueue(
          new Callable<ReactBridge>() {
            @Override
            public ReactBridge call() throws Exception {
              Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "initializeBridge");
              try {
                return initializeBridge(jsExecutor, jsModulesConfig);
              } finally {
                Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
              }
            }
          }).get(BRIDGE_SETUP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
    } catch (Exception t) {
      throw new RuntimeException("Failed to initialize bridge", t);
    }
  }

  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

The implementation in CatalystInstanceImpl is to write a JSON string, There are two fields “remoteModuleConfig” and “localModulesConfig”, which correspond to NativeModule(Native for JS calls) and JSModule(Native for JS calls) respectively.

private String buildModulesConfigJSONProperty(
      NativeModuleRegistry nativeModuleRegistry,
      JavaScriptModulesConfig jsModulesConfig) {
    JsonFactory jsonFactory = new JsonFactory();
    StringWriter writer = new StringWriter();
    try {
      JsonGenerator jg = jsonFactory.createGenerator(writer);
      jg.writeStartObject();
      jg.writeFieldName("remoteModuleConfig");
      nativeModuleRegistry.writeModuleDescriptions(jg);
      jg.writeFieldName("localModulesConfig");
      jsModulesConfig.writeModuleDescriptions(jg);
      jg.writeEndObject();
      jg.close();
    } catch (IOException ioe) {
      throw new RuntimeException("Unable to serialize JavaScript module declaration", ioe);
    }
    return writer.getBuffer().toString();
  }
Copy the code

Look at localModulesConfig write what, go in with JavaScriptModulesConfig. Java, is the water in the JSON characters and write first interface name, moduleID, the methods (the method name, methodID), etc.

/*package*/ void writeModuleDescriptions(JsonGenerator jg) throws IOException {
    jg.writeStartObject();
    for (JavaScriptModuleRegistration registration : mModules) {
      jg.writeObjectFieldStart(registration.getName());
      appendJSModuleToJSONObject(jg, registration);
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }

  private void appendJSModuleToJSONObject(
      JsonGenerator jg,
      JavaScriptModuleRegistration registration) throws IOException {
    jg.writeObjectField("moduleID", registration.getModuleId());
    jg.writeObjectFieldStart("methods");
    for (Method method : registration.getMethods()) {
      jg.writeObjectFieldStart(method.getName());
      jg.writeObjectField("methodID", registration.getMethodId(method));
      jg.writeEndObject();
    }
    jg.writeEndObject();
  }
Copy the code

From initializeBridge – > setGlobalVariable – > buildModulesConfigJSONProperty – > writeModuleDescriptions

JSON strings generated by all JavaScriptModule information are pre-stored in the Bridge, and stored in the JSON generator with the methodID identifier as the key, which is used to generate JSON strings:

The setGlobalVariable is also a Native function, all buildModulesConfigJSONProperty JavaScriptModule module generates a JSON string, in the form of moduleID + methodID JSON strings are pre-stored into the ReactBridge using setGlobalVariable

 bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
Copy the code

The Java layer call is the above process, and finally through the JNI call to the C++ layer, then go to this layer to see.

2. ReactBridge implementation

Communication model diagram to call the implementation of WebKit, the Bridge is indispensable, because Java can not directly call WebKit, but Java through JNI, JNI and then call WebKit

JNI react/ JNI/onload. CPP is registered by RegisterNatives, and native methods such as setGlobalVariable and callFunction are registered in JNI_OnLoad

//jni/react/jni/OnLoad.cpp
namespace bridge {
  ......

static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
  auto bridge = extractRefPtr<Bridge>(env, obj);
  bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue)); }... } // namespace bridge extern"C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  return initialize(vm, [] {
    // get the current env
    JNIEnv* env = Environment::current();
    registerNatives("com/facebook/react/bridge/ReadableNativeMap$ReadableNativeMapKeySetIterator"{... 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

SetGlobalVariable actually calls bridge. CPP:

//jni/react/Bridge.h
class JSThreadState;
class Bridge : public Countable {
public:
  typedef std::function<void(std::vector<MethodCall>, bool isEndOfBatch)> Callback;

  Bridge(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Callback callback);
  virtual ~Bridge();

  /**
   * Flush get the next queue of changes.
   */
  void flush();

  /**
   * Executes a function with the module ID and method ID and any additional
   * arguments in JS.
   */
  void callFunction(const double moduleId, const double methodId, const folly::dynamic& args);

  /**
   * Invokes a callback with the cbID, and optional additional arguments in JS.
   */
  void invokeCallback(const double callbackId, const folly::dynamic& args);

  void executeApplicationScript(const std::string& script, const std::string& sourceURL);
  void setGlobalVariable(const std::string& propName, const std::string& jsonValue);
  bool supportsProfiling();
  void startProfiler(const std::string& title);
  void stopProfiler(const std::string& title, const std::string& filename);
  void handleMemoryPressureModerate();
  void handleMemoryPressureCritical();
private:
  Callback m_callback;
  std::unique_ptr<JSThreadState> m_threadState;
  // This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(),
  // on the same thread. In that case, the callback will try to run the task on m_callback which
  // will have been destroyed within ~Bridge(), thus causing a SIGSEGV.
  std::shared_ptr<bool> m_destroyed;
};

void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  m_threadState->setGlobalVariable(propName, jsonValue);
}
Copy the code

The final call is setGlobalVariable in JSThreadState:

//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 */);
    });
  }

  void setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
    m_jsExecutor->setGlobalVariable(propName, jsonValue);
  }

private:
  std::unique_ptr<JSExecutor> m_jsExecutor;
  Bridge::Callback m_callback;
};
Copy the code

Finally call JSExecutor setGlobalVariable:

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

  virtual void executeApplicationScript(
    const std::string& script,
    const std::string& sourceURL) override;
  virtual std::string flush() override;
  virtual std::string callFunction(
    const double moduleId,
    const double methodId,
    const folly::dynamic& arguments) override;
  virtual std::string invokeCallback(
    const double callbackId,
    const folly::dynamic& arguments) override;
  virtual void setGlobalVariable(
    const std::string& propName,
    const std::string& jsonValue) override;
  virtual bool supportsProfiling() override;
  virtual void startProfiler(const std::string &titleString) override;
  virtual void stopProfiler(const std::string &titleString, const std::string &filename) override;
  virtual void handleMemoryPressureModerate() override;
  virtual void handleMemoryPressureCritical() override;

  void flushQueueImmediate(std::string queueJSON);
  void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback);
  virtual void onMessageReceived(int workerId, const std::string& message) override;
  virtual JSGlobalContextRef getContext() override;
  virtual std::shared_ptr<JMessageQueueThread> getMessageQueueThread() override;
};

} }

//jni/react/JSCExecutor.cpp
void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  auto globalObject = JSContextGetGlobalObject(m_context);
  String jsPropertyName(propName.c_str());

  String jsValueJSON(jsonValue.c_str());
  auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);

  JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}
Copy the code

From JSCExecutor: : setGlobalVariable can see Java layer over the JSON string (including Native Modules and JS Modules) and attribute names (__fbBatchedBridgeConfig), Assigns to global globalObject, which is obtained via JSContextGetGlobalObject, in the jscExecutor.h header:

#include <memory>
#include <unordered_map>
#include <JavaScriptCore/JSContextRef.h>
#include "Executor.h"
#include "JSCHelpers.h"
#include "JSCWebWorker.h"
Copy the code

JavaScriptCore/JSContextRef. H is its files in the library, the build under ReactAndroid. Gradle indexed in the library, to build. The gradle look at:

//ReactAndroid/build.gradle
task downloadJSCHeaders(type: Download) {
    def jscAPIBaseURL = 'https://svn.webkit.org/repository/webkit/! svn/bc/174650/trunk/Source/JavaScriptCore/API/'
    def jscHeaderFiles = ['JSBase.h'.'JSContextRef.h'.'JSObjectRef.h'.'JSRetainPtr.h'.'JSStringRef.h'.'JSValueRef.h'.'WebKitAvailability.h']
    def output = new File(downloadsDir, 'jsc')
    output.mkdirs()
    src(jscHeaderFiles.collect { headerName -> "$jscAPIBaseURL$headerName" })
    onlyIfNewer true
    overwrite false
    dest output
}
Copy the code

Open the jscAPIBaseURL url to find the jsContextref.h header:

//JavaScriptCore/API/JSContextRef.h ... / *! @function
@abstract Gets the global object of a JavaScript execution context.
@param ctx The JSContext whose global object you want to get.
@result ctx's global object. */ JS_EXPORT JSObjectRef JSContextGetGlobalObject(JSContextRef ctx); .Copy the code

JSCExecutor: : setGlobalVariable finally to global variables to set an attribute __fbBatchedBridgeConfig JS, the value of this attribute is JavaScriptModule, Java calls when JS properties in the search, Look at the callFunction:

//jni/react/JSCExecutor.cpp
std::string JSCExecutor::callFunction(const double moduleId, const double methodId, const folly::dynamic& arguments) {
  // TODO:  Make this a first class function instead of evaling. # 9317773
  std::vector<folly::dynamic> call{
    (double) moduleId,
    (double) methodId,
    std::move(arguments),
  };
  return executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
}

static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {
  #ifdef WITH_FBSYSTRACE
  FbSystraceSection s(
      TRACE_TAG_REACT_CXX_BRIDGE, "JSCExecutor.executeJSCall"."method", methodName);
  #endif

  // Evaluate script with JSC
  folly::dynamic jsonArgs(arguments.begin(), arguments.end());
  auto js = folly::to<folly::fbstring>(
      "__fbBatchedBridge.", methodName, ".apply(null, ",
      folly::toJson(jsonArgs), ")");
  auto result = evaluateScript(ctx, String(js.c_str()), nullptr);
  return Value(ctx, result).toJSONString();
}
Copy the code

With a final

__fbBatchedBridge. CallFunctionReturnFlushedQueue. Apply (null, {moduleId, methodId, the arguments}) in the form of splicing a apply Javascript execution statement, EvaluateScript (jni/react/ jschelpers. CPP); JSCHelpers (JSCHelpers); JSCHelpers (JSCHelpers);

//jni/react/JSCHelpers.h
namespace facebook {
namespace react {

void installGlobalFunction(
    JSGlobalContextRef ctx,
    const char* name,
    JSObjectCallAsFunctionCallback callback);

JSValueRef makeJSCException(
    JSContextRef ctx,
    const char* exception_text);

JSValueRef evaluateScript(JSContextRef context, JSStringRef script, JSStringRef source); }}Copy the code

CPP -> jscexecutor. CPP -> jschelpers. CPP ->WebKit

3. The JavaScript layer

Communication with Javascript is essentially Weikit executing Javascript statements, and the call process is Bridge->WebKit->Javascript. WebKit provides many apis for communicating with Javascript. Examples include evaluateScript, JSContextGetGlobalObject, JSObjectSetProperty, etc.

Review the front JSCExecutor: : setGlobalVariable:

//jni/react/JSCExecutor.cpp
void JSCExecutor::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  auto globalObject = JSContextGetGlobalObject(m_context);
  String jsPropertyName(propName.c_str());

  String jsValueJSON(jsonValue.c_str());
  auto valueToInject = JSValueMakeFromJSONString(m_context, jsValueJSON);

  JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);
}
Copy the code

All JavaScriptModule information is called setGlobalVariable methods generate a mapping table, the ultimate mapping table must be stored in the Javascript layer, JSContextGetGlobalObject is WeiKit method, Global object, its purpose is to obtain the Global jsPropertyName method literally means Javascript object attribute name, parameter propName passed from Java layer, in CatalystInstanceImpl. Java classes can confirm this, Two values are __fbBatchedBridgeConfig and **__RCTProfileIsProfiling**.

//com/facebook/react/bridge/CatalystInstanceImpl
bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
Copy the code

The JS statement is like:

global.__fbBatchedBridgeConfig = jsonValue;
Copy the code

**__fbBatchedBridgeConfig is defined as a Global object and can be called directly, similar to the window object. __fbBatchedBridgeConfig has two attributes: RemoteModuleConfig and localModulesConfig** correspond to Java modules and JS modules respectively.

Batchedbridge.js to JS layer:

//Libraries/BatchedBridge/BatchedBridge.js
const MessageQueue = require('MessageQueue'); const BatchedBridge = new MessageQueue( __fbBatchedBridgeConfig.remoteModuleConfig, . / / Native (Java) module __fbBatchedBridgeConfig localModulesConfig, / / JS module); // TODO: Move these around to solve the cyclein a cleaner way.

const Systrace = require('Systrace');
const JSTimersExecution = require('JSTimersExecution');

BatchedBridge.registerCallableModule('Systrace', Systrace);
BatchedBridge.registerCallableModule('JSTimersExecution', JSTimersExecution);

if (__DEV__) {
  BatchedBridge.registerCallableModule('HMRClient', require('HMRClient'));
}

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

module.exports = BatchedBridge;
Copy the code

FbBatchedBridge fbBatchedBridge fbBatchedBridge JSCExecutor in front: : callFunction called JS statements form is fbBatchedBridge callFunctionReturnFlushedQueue. Apply (null, {moduleId, methodId, arguments})

2.BatchedBridge=new MessageQueue

So continue to messagequeue.js:


class MessageQueue {

  constructor(remoteModules, localModules) {
    this.RemoteModules = {};

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

    [
      'invokeCallbackAndReturnFlushedQueue'.'callFunctionReturnFlushedQueue'.'flushedQueue',
    ].forEach((fn) => this[fn] = this[fn].bind(this));

    let modulesConfig = this._genModulesConfig(remoteModules);
    this._genModules(modulesConfig);
    localModules && this._genLookupTables(
      this._genModulesConfig(localModules),this._moduleTable, this._methodTable
    );

    this._debugInfo = {};
    this._remoteModuleTable = {};
    this._remoteMethodTable = {};
    this._genLookupTables(
      modulesConfig, this._remoteModuleTable, this._remoteMethodTable
    );
  }

  /**
   * Public APIs
   */
  callFunctionReturnFlushedQueue(module, method, args) {
    guard(() => {
      this.__callFunction(module, method, args);
      this.__callImmediates();
    });

    return this.flushedQueue();
  }
  
  _genLookupTables(modulesConfig, moduleTable, methodTable) {
    modulesConfig.forEach((config, moduleID) => {
      this._genLookup(config, moduleID, moduleTable, methodTable);
    });
  }

  _genLookup(config, moduleID, moduleTable, methodTable) {
    if(! config) {return;
    }

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

    moduleTable[moduleID] = moduleName;
    methodTable[moduleID] = Object.assign({}, methods);
  }
}

module.exports = MessageQueue;
Copy the code

RemoteModules and localModules are added to the **_genLookupTables method, along with two parameters _moduleTable** and _methodTable. A method mapping table

Java calls in front of the JS statements form under review: __fbBatchedBridge. CallFunctionReturnFlushedQueue. Apply (null, {moduleId, methodId, the arguments})


class MessageQueue {

 constructor(remoteModules, localModules) {
   this.RemoteModules = {};

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

   [
     'invokeCallbackAndReturnFlushedQueue'.'callFunctionReturnFlushedQueue'.'flushedQueue',
   ].forEach((fn) => this[fn] = this[fn].bind(this));

   let modulesConfig = this._genModulesConfig(remoteModules);
   this._genModules(modulesConfig);
   localModules && this._genLookupTables(
     this._genModulesConfig(localModules),this._moduleTable, this._methodTable
   );

   this._debugInfo = {};
   this._remoteModuleTable = {};
   this._remoteMethodTable = {};
   this._genLookupTables(
     modulesConfig, this._remoteModuleTable, this._remoteMethodTable
   );
 }

 /**
  * Public APIs
  */
 callFunctionReturnFlushedQueue(module, method, args) {
   guard(() => {
     this.__callFunction(module, method, args);
     this.__callImmediates();
   });

   return this.flushedQueue();
 }
 
 _genLookupTables(modulesConfig, moduleTable, methodTable) {
   modulesConfig.forEach((config, moduleID) => {
     this._genLookup(config, moduleID, moduleTable, methodTable);
   });
 }

 _genLookup(config, moduleID, moduleTable, methodTable) {
   if(! config) {return;
   }

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

   moduleTable[moduleID] = moduleName;
   methodTable[moduleID] = Object.assign({}, methods);
 }
}

module.exports = MessageQueue;
Copy the code

There is callableModules, which is used to store which Javascript components can be called. Normally, callableModules data and JavaScriptModules data (including method names and parameters) should correspond exactly.

Take a look at which JS components call registerCallableModule for method registration

Look at the RCTEventEmitter. Js:

//Libraries/BatchedBridge/BatchedBridgedModules/RCTEventEmitter.js
var BatchedBridge = require('BatchedBridge');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');

BatchedBridge.registerCallableModule(
  'RCTEventEmitter',
  ReactNativeEventEmitter
);

// Completely locally implemented - no native hooks.
module.exports = ReactNativeEventEmitter;
Copy the code

ReactNativeEventEmitter.js:

var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, {
  receiveEvent: function(
    tag: number,
    topLevelType: string,
    nativeEventParam: Object
  ) {
    ......
  },

  receiveTouches: function(
    eventTopLevelType: string,
    touches: Array<Object>,
    changedIndices: Array<number>
    ) {
      ......
    });
Copy the code

In contrast to the Java layer interface representation, the method name and the number of parameters are the same

public interface RCTEventEmitter extends JavaScriptModule {
  public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
  public void receiveTouches(
      String eventName,
      WritableArray touches,
      WritableArray changedIndices);
}
Copy the code

ModuleMethods [method]. Apply (moduleMethods, args); Invokes the corresponding module’s methods.

###4. Java calls to JavaScript can be summarized as follows:

Welcome to join us,

Official account: JueCode

Personal zhe at Jane Book blog: Juexingzhe