React Native通訊原理原始碼分析一

juexingzhe發表於2018-04-08

本篇文章已授權微信公眾號 JueCode 獨家釋出

小夥伴們都知道在Android開發中實現Java和JS的通訊可以通過WebView來實現,包括註冊JSBridge或者在介面中攔截都可以。然而React Native中並沒有用WebView控制元件的方式,而是基於WebKit核心的方式來實現Java與JS的通訊,也就是Java與JS之間的通訊都是通過中間層C++來實現的,今天我們來分析下Android中React Native怎麼實現Java和JS之間的通訊。

React Native中呼叫所有的行為都是從Native端發起的,使用者操作直接面向的也是Native。所以這個通訊模型又可以看成是Native發起會話,然後Javascript進行應答。

關於JavaScript呼叫Java,可以參考我的另外一篇部落格:React Native通訊原理原始碼分析二

Java要能呼叫到JS需要在Java層註冊JS模組,先來看下JS模組的註冊過程。

1.JavaScriptModule模組註冊(Java層)

系統註冊了一些常用的JS模組,在CoreModulesPackage的createJSModules中註冊了一些系統核心的JS模組,比如AppRegistry(RN元件註冊模組)/RCTEventEmitter(事件發射模組)等。

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
   * @return list 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<Class<? extends JavaScriptModule>> createJSModules();

  /**
   * @return a list of view managers that should be registered with {@link UIManagerModule}
   */
  List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
複製程式碼

以RCTEventEmitter這個JavaScriptModule模組為例來看下JS模組在Java層的實現。

所有JS層元件實現JavaScriptModule介面,比如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);
}
複製程式碼

可以看出來JS模組在Java層只是簡單的繼承JavaScriptModule,並沒有具體的實現,那麼問題就來了,光是一個介面是沒法呼叫的,好像到這裡就跟蹤不下去了?其實可以換種思路,JS模組肯定是要在Native中註冊的,我們先到ReactInstanceManagerImpl中的createReactContext方法中去看下怎麼註冊的。

所有的NativeModule和JSModule在ReactInstanceManagerImpl中註冊

方法的程式碼比較多,但是邏輯不復雜,關鍵的幾個步驟我都在程式碼中直接做了註釋,就不多說了,這裡主要分析JS模組的註冊,所以我們看到JavaScriptModulesConfig中。

private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
    FLog.i(ReactConstants.TAG, "Creating react context.");
  //Native模組
    NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
  //JS模組
    JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();

  //React Context
    ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
    if (mUseDeveloperSupport) {
      reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
    }

  //處理CoreModules---包含Native模組和JS模組
    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);
    }

    // 處理使用者註冊的模組
    for (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.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;
  }
複製程式碼

看到JavaScriptModulesConfig,主要是對每個JavaScriptModule構造JavaScriptModuleRegistration,存放在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() {
      return new JavaScriptModulesConfig(mModules);
    }
  }
}
複製程式碼
/**
 * 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) {
        return lhs.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());
    }
  }
  ......
}
複製程式碼

到這裡就有了所有的JavaScriptModules,並且都掃描存放在JavaScriptModulesConfig中,那麼RN框架是怎麼使用的?再回顧前面ReactInstanceManagerImpl,將JavaScriptModulesConfig用來例項化catalystInstance,這個是三方通訊的中轉站。

 //createCatalystInstance---Java/JS/C++三方通訊總管
    CatalystInstanceImpl.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);
    }
複製程式碼

catalystInstance是一個介面,實現類是CatalystInstanceImpl,跟到建構函式中:

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);
    ...
  }
複製程式碼

通過JavaScriptModulesConfig例項化JavaScriptModuleRegistry,前面分析過JavaScriptModule在Java層都是implements JavaScriptModule的介面,那麼怎麼去呼叫方法?這裡就是對每個介面通過動態代理例項化一個代理物件,Java呼叫JS方法時統一分發到JavaScriptModuleInvocationHandler中的invoke進行處理,在invoke中其實就是呼叫mCatalystInstance.callFunction

很明顯,接下來需要到JavaScriptModuleRegistry中看看:

/**
 * 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);
      return null;
    }
  }
}
複製程式碼

在Java呼叫JS模組的方法時,都會通過代理物件呼叫invoke方法,在invoke方法中呼叫CatalystInstance.callFunction,CatalystInstance是一個介面,實現類CatalystInstanceImpl,看到CatalystInstanceImpl中的callFunction:

/* 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);
            }
          }
        });
  }
複製程式碼

所有Java層向Javascript層的通訊請求都是走的ReactBridge.callFunction

其實就是在JSQueueThread執行緒中呼叫ReactBridge的callFunction,是Native函式,到這裡就從Java轉到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();
}
複製程式碼

再回頭看看ReactBridge在CatalystInstanceImpl裡面的初始化,初始化會ReactBridge呼叫setGlobalVariable,這是個Native函式,是在C++層註冊用的,先按下後面再分析元件呼叫過程再分析,先看看 buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig)做了什麼。

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;
  }
複製程式碼

在CatalystInstanceImpl中的實現,其實就是寫一個JSON字串,有兩個欄位"remoteModuleConfig"和"localModulesConfig",分別對應NativeModule(Native提供給JS呼叫)和JSModule(JS提供給Native呼叫)

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();
  }
複製程式碼

再看看localModulesConfig都寫入了什麼,跟進去JavaScriptModulesConfig.java,就是往JSON字元創中先寫入介面名、moduleID、methods(方法名、methodID)等。

/*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();
  }
複製程式碼

initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions

整個過程作用是將所有JavaScriptModule的資訊生成JSON字串預先儲存到Bridge中, 通過methodID標識作為key存入JSON生成器中,用來最終生成JSON字串:

其中setGlobalVariable也是Native函式,buildModulesConfigJSONProperty把所有的JavaScriptModule模組以moduleID+methodID形式生成JSON字串,通過setGlobalVariable把JSON字串預先存入了ReactBridge

 bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
複製程式碼

Java層的呼叫就是上面的過程,最後通過JNI呼叫到C++層,接下來進入這一層看看。

2.ReactBridge實現

通訊模型圖中要呼叫WebKit的實現,少不了Bridge這個橋樑,因為Java是不能直接呼叫WebKit,但是可以Java通過JNIJNI再呼叫WebKit

JNI入口react/jni/OnLoad.cpp,通過RegisterNatives方式註冊的,JNI_OnLoad裡面註冊了setGlobalVariablecallFunctionnative本地方法

//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),

    });
}
複製程式碼

可以看到setGlobalVariable真正呼叫的是Bridge.cpp裡面的setGlobalVariable:

//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);
}
複製程式碼

最終呼叫的是JSThreadState裡面的setGlobalVariable:

//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;
};
複製程式碼

最終呼叫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);
}
複製程式碼

從JSCExecutor::setGlobalVariable中可以看到java層傳過來的JSON串(包含Native Modules和JS Modules)和屬性名(__fbBatchedBridgeConfig),賦值給全域性變數globalObject,這個全域性變數通過JSContextGetGlobalObject獲取到,在JSCExecutor.h標頭檔案中:

#include <memory>
#include <unordered_map>
#include <JavaScriptCore/JSContextRef.h>
#include "Executor.h"
#include "JSCHelpers.h"
#include "JSCWebWorker.h"
複製程式碼

JavaScriptCore/JSContextRef.h是Webkit這個庫中的檔案,在ReactAndroid下的build.gradle中有索引這個庫,到build.gradle中看看:

ReactAndroid.png

//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
}
複製程式碼

開啟jscAPIBaseURL這個url可以找到JSContextRef.h標頭檔案:

//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);
...
複製程式碼

JSCExecutor::setGlobalVariable最後給JS的全域性變數設定一個屬性__fbBatchedBridgeConfig,這個屬性的值就是JavaScriptModule,Java呼叫JS的時候從屬性中查詢,看下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();
}
複製程式碼

最終以

__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, arguments})的形式拼接一個applyJavascript執行語句,最後呼叫jni/react/JSCHelpers.cppevaluateScript的來執行這個語句,完成BridgeJavascript的呼叫,JSCHelpersWebKit的一些API做了封裝,它負責最終呼叫WebKit

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

} }

複製程式碼

Bridge層的呼叫過程: OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->JSCHelpers.cpp->WebKit

3.JavaScript層實現

Javascript的通訊,實質上是Weikit執行Javascript語句,呼叫流程是Bridge->WebKit->JavascriptWebKit中提供了許多與Javascript通訊的API,比如evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

回顧一下前面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);
}
複製程式碼

所有JavaScriptModule資訊是呼叫的setGlobalVariable方法生成一張對映表,這張對映表最終肯定是要儲存在Javascript層的,JSContextGetGlobalObjectWeiKit的方法,其目的是獲取Global全域性物件,jsPropertyName方法字面意思就是Javascript物件的屬性名,引數propName是從Java層傳遞過來的,在CatalystInstanceImpl.java類中可以印證這一點,具體值有兩個:__fbBatchedBridgeConfig和**__RCTProfileIsProfiling**.

//com/facebook/react/bridge/CatalystInstanceImpl
bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
複製程式碼

翻譯成JS語句大概是:

global.__fbBatchedBridgeConfig = jsonValue;
複製程式碼

由於**__fbBatchedBridgeConfig物件是被直接定義成Global全域性物件的屬性,就可以直接呼叫了,類似於window物件,__fbBatchedBridgeConfig物件裡又有兩個屬性:remoteModuleConfiglocalModulesConfig**,分別對應Java模組和JS模組。

到JS層的BatchedBridge.js:

//Libraries/BatchedBridge/BatchedBridge.js
const MessageQueue = require('MessageQueue');

const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,	//Native(Java)模組
  __fbBatchedBridgeConfig.localModulesConfig,	//JS模組
);

// TODO: Move these around to solve the cycle in 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;
複製程式碼

1.在全域性變數global中定義fbBatchedBridge,在前面JSCExecutor::callFunction中呼叫JS語句的形式是fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, arguments})

2.BatchedBridge=new MessageQueue

所以再繼續跟到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;
複製程式碼

把remoteModules, localModules傳進了**_genLookupTables的方法裡,同時還有兩個引數_moduleTable**、_methodTable,就是我們找的對映表了,一張module對映表,一張method對映表

再回顧下前面Java呼叫JS的語句形式:__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null,{moduleId, methodId, 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;
複製程式碼

其中有個callableModules, 它就是用來存放哪些Javascript元件是可以被呼叫的,正常情況下callableModules的資料和JavaScriptModules的資料(包括方法名和引數)理應是完全對應的.

看下哪些JS元件呼叫registerCallableModule進行方法的註冊

resigterCallableModule.png

看一下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;
複製程式碼

ReactNativeEventEmitter.js:

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

  receiveTouches: function(
    eventTopLevelType: string,
    touches: Array<Object>,
    changedIndices: Array<number>
    ) {
      ......
    });
複製程式碼

對比下Java層的介面表示,方法名稱和引數個數是一致的

public interface RCTEventEmitter extends JavaScriptModule {
  public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap event);
  public void receiveTouches(
      String eventName,
      WritableArray touches,
      WritableArray changedIndices);
}
複製程式碼

繼續前面__callFunction的最後一步:moduleMethods[method].apply(moduleMethods, args);呼叫對應模組的方法。

###4.總結 Java呼叫JavaScript可以總結為下面:

呼叫流程.png

歡迎關注哈,

公眾號: JueCode

個人在簡書部落格: juexingzhe

相關文章