1. 程式人生 > >React-Native系列Android——Native與Javascript通信原理(一)

React-Native系列Android——Native與Javascript通信原理(一)

from 直接 最新 一點 明顯 rem 負責 receive esp

React-Native最核心的是NativeJavascript之間的通信,並且是雙向通信。Native層到Javascript層,Javascript層到Native層。雖說是兩個方向,但實現上大同小異,我們先從Native層入手,研究一下Native調用Javascript的過程。


1、通信模型

Android應用層的程序語言是JavaReact-NativeNative端的框架實現用的也是Java語言,所以實質上是JavaJavascript兩種程序語言的調用。

事實上這個過程,在Android

系統上已經有了實現。就是WebView。熟悉WebView的都知道底層實現是WebKit,雖然在Android 4.4系統上切換成了Chromium,但歸根結底還是WebKit的變種,僅僅是加了谷歌自己的一些東西。然而React-NativeWebView並沒有一點關系,並且後者的WebKit內核也不支持ES6特性(React語法大多基於ES6),那怎麽辦?僅僅能自己弄一套最新的WebKit作為React-Native的解釋器了,這個從安卓projectlib文件夾以下的libjsc.so動態鏈接庫文件能夠印證,這樣做還有兩個重要優點就是兼容絕大多少設備版本號和方便加入自己定義功能。

所以由此,我們大概能夠猜到React-Native

的通信原理,畫一張圖來簡單地描寫敘述一下:

技術分享圖片


2、Java層實現

之前說過。React-Native的重要設計思想是組件化,為了便於維護擴展和減少耦合,React-Native並沒有為了實現某一詳細的通信編寫代碼(比方上篇博文所講的觸摸事件傳遞),而是設計了一套標準用於組件化。

這套標準是向開發人員開放的,開發人員能夠自行編寫須要的組件用來在NativeJavascript之間通信,雖然這並非推薦的選擇。

2.1 JavaScriptModule組件

React-Native官方實現了一定數量的組件,比方觸摸事件組件。按鍵組件等。這些組件都位於CoreModulesPackage

中,屬於默認載入的。全部的組件都必須繼承JavaScriptModule接口標準。JavaScriptModule位於com.facebook.react.bridge包以下:

/**
 * Interface denoting that a class is the interface to a module with the same name in JS. Calling
 * functions on this interface will result in corresponding methods in JS being called.
 *
 * When extending JavaScriptModule and registering it with a CatalystInstance, all public methods
 * are assumed to be implemented on a JS module with the same name as this class. 
 *
 * NB: JavaScriptModule does not allow method name overloading because JS does not allow method name
 * overloading.
 */
@DoNotStrip
public interface JavaScriptModule {
}

閱讀一下凝視,主要有三點信息:
1、全部組件必須繼承JavaScriptModule,並註冊在CatalystInstance中。
2、全部public方法與Javascript層保持同名並由後者詳細實現。


3、因為Javascript不支持重載。所以Java中也不能有重載。

細致的讀者會發現,凝視裏有兩個單詞非常關鍵。extendingimplementedJavaextendJavascriptimplement,也就是說Java層僅僅做接口定義。而實現由Javascript完畢。所以。搜索一下JavaScriptModule的子類會發現它們都是接口。沒有詳細實現類。

有點晦澀但事實上非常好理解,舉個簡單的樣例。去餐館吃飯,顧客(Java)僅僅要定義(interface)好想吃什麽菜,然後和餐館(Bridge)說。餐館會通知自己的廚師(Javascript)把詳細的菜做好。這就是一個簡單的通信過程Java->Bridge->Javascript

2.2 JavaScriptModule組件的註冊

上一篇文章講的觸摸事件的處理。裏面提到一個RCTEventEmitter的類,用來將每一個觸摸事件都傳遞給Javascript層,這個組件就是繼承於JavaScriptModule

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

先前凝視中第1點,全部JavaScriptModule組件都必須在CatalystInstance中註冊。那我們來看一下註冊的過程。

RCTEventEmitterfacebook官方定義的。組裝在CoreModulesPackage中。而全部的package都是在com.facebook.react.ReactInstanceManagerImpl中處理的,看一下代碼:

class ReactInstanceManagerImpl extends ReactInstanceManager {

  ...

  private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader) {
     ...
     try {
      CoreModulesPackage coreModulesPackage =
          new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
      processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
    } finally {
      ...
    }

    for (ReactPackage reactPackage : mPackages) {
      ...
      try {
        processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
      } finally {
        ...
      }
    }

  }

  private void processPackage(ReactPackage reactPackage, ReactApplicationContext reactContext, NativeModuleRegistry.Builder nativeRegistryBuilder, JavaScriptModulesConfig.Builder jsModulesBuilder) {

    ...

    for (Class<? extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()){
      jsModulesBuilder.add(jsModuleClass);
    }
  }
  ...

}

能夠看到CoreModulesPackage和開發人員擴展自己定義的mPackages都是通過processPackage方法裏加入到JavaScriptModulesConfig裏註冊的。

簡單的建造者模式,我們直接看一下JavaScriptModulesConfig類,位於包com.facebook.react.bridge下。

public class JavaScriptModulesConfig {

  private final List<JavaScriptModuleRegistration> mModules;

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

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

  ...
}

JavaScriptModule明顯是通過構造函數傳入,然後又通過一個getter方法提供出去了,看樣子JavaScriptModulesConfig僅僅起到了一個中間者的作用,並非真正的註冊類。

回看一下之前的ReactInstanceManagerImpl類代碼,createReactContext中另一段。例如以下:

private ReactApplicationContext createReactContext(
      JavaScriptExecutor jsExecutor,
      JSBundleLoader jsBundleLoader)
     ...

     JavaScriptModulesConfig.Builder jsModulesBuilder = new JavaScriptModulesConfig.Builder();
     JavaScriptModulesConfig javaScriptModulesConfig;
    try {
      javaScriptModulesConfig = jsModulesBuilder.build();
    } finally {
      ...
    }
     ...
    CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
        .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
        .setJSExecutor(jsExecutor)
        .setRegistry(nativeModuleRegistry)
        .setJSModulesConfig(javaScriptModulesConfig)
        .setJSBundleLoader(jsBundleLoader)
        .setNativeModuleCallExceptionHandler(exceptionHandler);

    ...

    CatalystInstance catalystInstance;
    try {
      catalystInstance = catalystInstanceBuilder.build();
    } finally {
      ...
    }

    ...
}

看來終於javaScriptModulesConfig是用來構建CatalystInstance的,正如凝視所講。果然沒有騙我。

CatalystInstance僅僅是一個接口。實現類是CatalystInstanceImpl。相同位於包com.facebook.react.bridge下。Catalyst單詞的中文意思是催化劑,化學中是用來促進化學物之間的反應,難道說CatalystInstance是用來催化NativeJavascript之間的反應?讓我們來瞧一瞧真面目吧。

public class CatalystInstanceImpl implements CatalystInstance {

   ...

     private CatalystInstanceImpl(
      final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
      final JavaScriptExecutor jsExecutor,
      final NativeModuleRegistry registry,
      final JavaScriptModulesConfig jsModulesConfig,
      final JSBundleLoader jsBundleLoader,
      NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {

    ...

    mJSModuleRegistry = new JavaScriptModuleRegistry(CatalystInstanceImpl.this, jsModulesConfig);

    ...

    try {
      mBridge = mReactQueueConfiguration.getJSQueueThread().callOnQueue(
          new Callable<ReactBridge>() {
            @Override
            public ReactBridge call() throws Exception {
              ...
              try {
                return initializeBridge(jsExecutor, jsModulesConfig);
              } finally {
                  ...
              }
            }
          }).get();
    } catch (Exception t) {
      throw new RuntimeException("Failed to initialize bridge", t);
    }
  }

  private ReactBridge initializeBridge(
      JavaScriptExecutor jsExecutor,
      JavaScriptModulesConfig jsModulesConfig) {
    ...
    ReactBridge bridge;
    try {
      bridge = new ReactBridge(
          jsExecutor,
          new NativeModulesReactCallback(),
          mReactQueueConfiguration.getNativeModulesQueueThread());
    } finally {
       ...
    }
    ...
    try {
      bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
      bridge.setGlobalVariable(
          "__RCTProfileIsProfiling",
          Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

"true" : "false"); } finally { ... } return bridge; } ... }

CatalystInstanceImpl構造方法裏,jsModulesConfig又被用來初始化JavaScriptModuleRegistry,字面意思是JavaScriptModule註冊表。看樣子終於找到註冊類了。

先不著急。繼續往下看CatalystInstanceImpl中還初始化了ReactBridge 。字面意思就是真正連接NativeJavascript的橋梁了。ReactBridge幹了什麽呢?調用了setGlobalVariable方法,參數裏面的buildModulesConfigJSONProperty方法又用到了JavaScriptModulesConfig,順便來看看。

  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();
  }

這種方法終於的目的是生成一個JSON字符串,而字符串由什麽構成呢?nativeModulejsModulesnativeModule先無論,jsModulesConfig調用了writeModuleDescriptions

回頭看看剛才講的JavaScriptModulesConfig這個中間類。

public class JavaScriptModulesConfig {

   ...

     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();
  }

   ...
}

writeModuleDescriptions這種方法幹了什麽事呢?遍歷全部JavaScriptModulepublic方法,然後通過methodID標識作為key存入JSON生成器中,用來終於生成JSON字符串。

這裏略微梳理一下。從initializeBridge->setGlobalVariable->buildModulesConfigJSONProperty->writeModuleDescriptions。整個過程作用是將全部JavaScriptModule的信息生成JSON字符串預先保存到Bridge中。至於為什麽這麽做。先挖個坑,研究到後面自然就明確了。

2.3 JavaScriptModule組件的調用

繼續之前說到的NativeModuleRegistry註冊表類。位於包com.facebook.react.bridge中。

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

  ...
}

當每次看到這段代碼的時候,都有一種驚艷的感覺。前面說過JavaScriptModule組件都是接口定義。在Java端是沒有實現類的,被註冊的都是Class類。沒有真正的實例,Java端又怎樣來調用呢?答案是:動態代理

這裏使用動態代理除了創建JavaScriptModule組件的實例化類外。另一個關鍵的數據,即JavaScriptModule全部的方法調用都會被invoke攔截,這樣就能夠統一處理全部從Java端向Javascript端的通信請求。

JavaScriptModuleInvocationHandlerJavaScriptModuleRegistry的一個內部類,動態代理的攔截類。

  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;
    }
  }

JavaScriptModule方法攔截invoke裏調用了CatalystInstancecallFunction方法,主要傳入了ModuleIdMethodIdArguments這三個重要參數(tracingName忽略)。

public class CatalystInstanceImpl implements CatalystInstance {

   ...

    private final ReactBridge mBridge;

    void callFunction(
      final int moduleId,
      final int methodId,
      final NativeArray arguments,
      final String tracingName) {

    ...

    mReactQueueConfiguration.getJSQueueThread().runOnQueue(
        new Runnable() {
          @Override
          public void run() {

            ...

            try {  
             Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId,arguments);
            } finally {
               ...
            }
          }
        });
  }

   ...

}

分析這裏終於豁然開朗了,原來全部Java層向Javascript層的通信請求都是走的ReactBridge.callFunction

又有了一個問題,Javascript層詳細怎麽知道Java層的調用信息呢?

還是之前舉的餐館吃飯的樣例,顧客(Java)把菜名告訴餐館(Bridge),餐館再通知廚師(Javascript)。廚師自然就知道該做什麽菜了。當然前提是要約定一個菜單了,菜單包括全部的菜名。

還記得之前挖的一個坑嗎?就是CatalystInstanceImpl中初始化ReactBridge的時候,全部JavaScriptModule信息都被以moduleID+methodID形式生成的JSON字符串預先存入了ReactBridge中,這事實上就是一個菜單索引表了。餐館(Bridge)知道了菜名(moduleID+methodID)就能告訴廚師(Javascript)顧客(Java)想吃什麽了,當然有時還少不了不放辣這樣的需求了(arguments)。

所以callFunction中有了moduleId + methodId + arguments,就能夠調用到Javascript中的實現了。


3、Bridge層實現

通信模型圖中要調用WebKit的實現,少不了Bridge這個橋梁。因為Java是不能直接調用WebKit,可是假設Java通過JNIJNI再調用WebKit不就OK了麽?

繼續前面說的ReactBridgesetGlobalVariablecallFunction方法。

public class ReactBridge extends Countable {
  static {
    SoLoader.loadLibrary(REACT_NATIVE_LIB);
  }

   public native void callFunction(int moduleId, int methodId, NativeArray arguments);

  public native void setGlobalVariable(String propertyName, String jsonEncodedArgument);
}

果然是JNI調用,而JNI層的入口是react/jni/OnLoad.cpp,和常規的javah規則不同,它是通過RegisterNatives方式註冊的,JNI_OnLoad裏面註冊了setGlobalVariablecallFunctionnative本地方法。

廢話不多說。來看看c++setGlobalVariablecallFunction的實現吧。

namespace bridge {

    static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstring jsonValue) {
    auto bridge = extractRefPtr<CountableBridge>(env, obj);
    bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue));
  }

   static void callFunction(JNIEnv* env, jobject obj, JExecutorToken::jhybridobject jExecutorToken, jint moduleId, jint methodId,
                         NativeArray::jhybridobject args, jstring tracingName) {
   auto bridge = extractRefPtr<CountableBridge>(env, obj);
   auto arguments = cthis(wrap_alias(args));
   try {
      bridge->callFunction(
      cthis(wrap_alias(jExecutorToken))->getExecutorToken(wrap_alias(jExecutorToken)),
      folly::to<std::string>(moduleId),
      folly::to<std::string>(methodId),
      std::move(arguments->array),
      fromJString(env, tracingName)
    );
   } catch (...) {
     translatePendingCppExceptionToJavaException();
    }
  }

}
struct CountableBridge : Bridge, Countable {
  using Bridge::Bridge;
};

OnLoad僅僅是一個調用入口。終於走的還是CountableBridge,而CountableBridge繼承的是Bridge。僅僅是加了一個計數功能。實現代碼在react/Bridge.cpp中。

void Bridge::setGlobalVariable(const std::string& propName, const std::string& jsonValue) {
  runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
    executor->setGlobalVariable(propName, jsonValue);
  });
}
void Bridge::callFunction(
    ExecutorToken executorToken,
    const std::string& moduleId,
    const std::string& methodId,
    const folly::dynamic& arguments,
    const std::string& tracingName) {
  #ifdef WITH_FBSYSTRACE
  int systraceCookie = m_systraceCookie++;
  ...
  #endif

  #ifdef WITH_FBSYSTRACE
  runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {
  ...
  #else
  runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName] (JSExecutor* executor) {
  #endif
    executor->callFunction(moduleId, methodId, arguments);
  });
}

兩個方法調用的過程幾乎相同,都是塞進runOnExecutorQueue運行隊列裏面等待調用,回調都是走的JSExecutor。所以還是要看JSExecutor了。

這邊提一下,Bridge類構造的時候會初始化ExecutorQueue,通過JSCExecutorFactory創建JSExecutor,而JSExecutor的真正實現類是JSCExecutor

通過jni/react/JSCExecutor.h頭文件能夠驗證這一點,此處略過不細講。

繞來繞去,略微有點暈。最後又跑到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);
}

finally哈。前面Java層構造的JavaScriptModule信息JSON串,終於在這裏被處理了,不用想也知道肯定是解析後存為一張映射表,然後等callFunction的時候映射調用。接下來看callFunction的處理。

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  // TODO:  Make this a first class function instead of evaling. #9317773
  std::vector<folly::dynamic> call{
    moduleId,
    methodId,
    std::move(arguments),
  };
  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
  m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {

  ...

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

callFunction裏面運行的是executeJSCallWithJSC。而executeJSCallWithJSC裏面將methodNamejsonArgs拼接成了一個applyJavascript運行語句。最後調用jni/react/JSCHelpers.cppevaluateScript的來運行這個語句,完畢BridgeJavascript的調用。(JSCHelpersWebKit的一些API做了封裝,暫不深究,僅僅要知道它負責終於調用WebKit即可了)

當然JSCExecutor::callFunction方法最後另一個Bridge.cpp類的callNativeModules反向通信,意圖是將Javascript語句運行結果通知回Native,這個過程留在以後的文章中慢慢研究,先行略過。

最後,總結一下Bridge層的調用過程: OnLoad.cpp->Bridge.cpp->JSCExecutor.cpp->JSCHelpers.cpp->WebKit


4、Javascript層實現

Javascript的通信,實質上是Weikit運行Javascript語句,調用流程是Bridge->WebKit->JavascriptWebKit中提供了很多與Javascript通信的API。比方evaluateScriptJSContextGetGlobalObjectJSObjectSetProperty等。

4.1、JavaScriptModule映射表

前面說過,全部JavaScriptModule信息是調用的setGlobalVariable方法生成一張映射表,這張映射表終於肯定是要保存在Javascript層的,回頭細致分析下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);
}

JSContextGetGlobalObjectWeiKit的方法。其目的是獲取Global全局對象。jsPropertyName方法字面意思就是Javascript對象的屬性名,參數propName是從Java層傳遞過來的,在CatalystInstanceImpl.java類中能夠印證這一點,詳細值有兩個:__fbBatchedBridgeConfig__RCTProfileIsProfiling

bridge.setGlobalVariable(
          "__fbBatchedBridgeConfig",
          buildModulesConfigJSONProperty(mJavaRegistry, jsModulesConfig));
      bridge.setGlobalVariable(
          "__RCTProfileIsProfiling",
          Systrace.isTracing(Systrace.TRACE_TAG_REACT_APPS) ?

"true" : "false");

我們所關註的是__fbBatchedBridgeConfig。這個值被傳遞到剛剛說的JSCExecutor::setGlobalVariable生成jsPropertyName對象,而jsonValue相同被JSValueMakeFromJSONString處理成一個jsValue對象,這樣一來property-value就全都有了。最後JSObjectSetProperty方法,顧名思義,就是設置屬性,使用的是Global全局對象,假設翻譯成Javascript代碼,大概應該是這樣:

global.__fbBatchedBridgeConfig = jsonValue;

或者

Object.defineProperty(global, ‘__fbBatchedBridgeConfig‘, { value: jsonValue});

作用事實上是一樣的。

既然javascript接收到了關於JavaScriptModule的信息,那就要生成一張映射表了。

我們來看node_modules\react-native\Libraries\BatchedBridge\BatchedBridge.js的代碼。

const MessageQueue = require(‘MessageQueue‘);

const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

因為__fbBatchedBridgeConfig對象是被直接定義成Global全局對象的屬性,就能夠直接調用了,相似於window對象。__fbBatchedBridgeConfig對象裏又有兩個屬性:remoteModuleConfiglocalModulesConfig

哪兒冒出來的呢?

有心的讀者能猜到這兩個屬性都是定義在jsonValue裏面的,為了驗證這一點。我們再回頭搜索下生成JSON串的地方,代碼在CatalystInstanceImpl.java裏面。

  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裏面存的就是JavaScriptModule的信息,果然沒錯!

再來看剛剛的BatchedBridge.js

const MessageQueue = require(‘MessageQueue‘);

const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

JavaScriptModule的信息。又被傳入MessageQueue的構造函數裏面了,繼續往MessageQueue裏面看,代碼在node_modules\react-native\Libraries\Utilities\MessageQueue.js

class MessageQueue {

  constructor(remoteModules, localModules) {
    ...

    localModules && this._genLookupTables(
      this._genModulesConfig(localModules),this._moduleTable, this._methodTable
    );
}

localModules參數就是JavaScriptModule信息了,又被傳進了_genLookupTables的方法裏,同一時候還有兩個參數_moduleTable_methodTable。推測一下,應該就是我們找的映射表了。一張module映射表,一張method映射表。

_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);
  }

哈哈,和推測的一樣,生成了兩張映射表,存放在了MessageQueue類裏面。

4.2、callFunction的調用

回想一下JSCExecutor.cpp中的終於callFunction調用過程。

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
  // TODO:  Make this a first class function instead of evaling. #9317773
  std::vector<folly::dynamic> call{
    moduleId,
    methodId,
    std::move(arguments),
  };
  std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
  m_bridge->callNativeModules(*this, calls, true);
}
static std::string executeJSCallWithJSC(
    JSGlobalContextRef ctx,
    const std::string& methodName,
    const std::vector<folly::dynamic>& arguments) {

  ...

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

executeJSCallWithJSC中有個生成語句的代碼,methodName的值為callFunctionReturnFlushedQueue,所以拼裝成的Javascript語句是:

__fbBatchedBridge.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

首先,在Javascript的運行環境下,當前作用域條件下__fbBatchedBridge能被直接調用。必須是Global全局對象的屬性。

4.1__fbBatchedBridgeConfig不同的是,jni並沒有手動設置__fbBatchedBridge為全局對象的屬性,那唯一的可能就是在Javascript裏面通過Object.defineProperty來設置了。

搜索一下。在BatchedBridge.js中找到例如以下代碼:

const MessageQueue = require(‘MessageQueue‘);

const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);

...

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

module.exports = BatchedBridge;

這段代碼等價於

global.__fbBatchedBridge = new MessageQueue(...args);

再次替換一下,callFuction調用的是:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, jsonArgs);

Arguments參數再詳細一下。就變成了:

MessageQueue.callFunctionReturnFlushedQueue.apply(null, module, method, args);

又回到MessageQueue.js了,前面才分析到它裏面存放了兩張映射表,如今第一件事當然是作匹配查找了,看MessageQueue.callFunctionReturnFlushedQueue的詳細調用吧。

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

    return this.flushedQueue();
  }

var guard = (fn) => {
  try {
    fn();
  } catch (error) {
    ErrorUtils.reportFatalError(error);
  }
};

Lambda+閉包,代碼非常簡潔,但閱讀起來比較吃力。而React裏面都是這樣的。強烈吐槽一下。

定義guard目的是為了統一捕獲錯誤異常,忽略這一步,以上代碼等價於:

callFunctionReturnFlushedQueue(module, method, args) {
    this.__callFunction(module, method, args);
    this.__callImmediates();
    return this.flushedQueue();
  }

this指的是當前MessageQueue 對象。所以找到MessageQueue.__callFunction方法:

__callFunction(module, method, args) {
    ...
    if (isFinite(module)) {
      method = this._methodTable[module][method];
      module = this._moduleTable[module];
    }
    ...
    var moduleMethods = this._callableModules[module];
    invariant(
      !!moduleMethods,
      ‘Module %s is not a registered callable module.‘,
      module
    );

    moduleMethods[method].apply(moduleMethods, args);
    ...
  }

這裏就是通過moduleIDmethodID來查詢兩張映射Table了。獲取到了詳細的moduleNamemethodName,接著肯定要做調用Javascript相應組件了。

假設在餐館吃飯的樣例中,場景應該是這樣的:顧客點完菜。餐館服務人員也已經把菜名通知到廚師了,廚師該做菜了吧。等等。當中還漏了一步,就是這個廚師會不會做這道菜。假設讓川菜師傅去做粵菜肯定是不行的,所以廚師的能力裏還應該有一張技能清單,做菜前廚師須要推斷下自己的技能單子裏面有沒有這道菜。

代碼同理。MessageQueue裏面有一個_callableModules數組。它就是用來存放哪些Javascript組件是能夠被調用的。正常情況下_callableModules的數據和JavaScriptModules的數據(包括方法名和參數)理應是全然相應的。

我們來瞧瞧_callableModules數據初始化的過程,相同是在MessageQueue.js中:

registerCallableModule(name, methods) {
    this._callableModules[name] = methods;
}

全部的Javascript組件都是通過registerCallableModule來註冊的,比方觸摸事件RCTEventEmitter.java相應的組件RCTEventEmitter.js,代碼路徑是
node_modules\react-native\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;

BatchedBridge能夠看成是MessageQueue。被註冊的組件是ReactNativeEventEmitter。代碼位於node_modules\react-native\Libraries\ReactNative\ReactNativeEventEmitter.js

receiveEvent: function(tag: number, topLevelType: string, nativeEventParam: Object) {
    ...
},

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

細致對比RCTEventEmitter .java比較,是不是全然一致,哈哈

public interface RCTEventEmitter extends JavaScriptModule {

  public void receiveEvent(int targetTag, String eventName,  WritableMap event);
  public void receiveTouches(String eventName, WritableArray touches, WritableArray changedIndices);

}

繼續__callFunction方法代碼的最後一步

moduleMethods[method].apply(moduleMethods, args)

假設以RCTEventEmitterreceiveTouches方法調用為例。詳細語句應該是這樣:

ReactNativeEventEmitter.receiveTouches.apply(moduleMethods, eventTopLevelType, touches, changedIndices);

結束!通信完畢。大功告成!

5、總結

整個通信過程涉及到三種程序語言:JavaC++Javascript,這還僅僅是單向的通信流程。假設是逆向則更加復雜。因為篇幅的關系。留到以後的博客裏面研究。

最後總結一下幾個關鍵點:

1、Java層

JavaScriptModule接口類定義通信方法,在ReactApplicationContext創建的時候存入註冊表類JavaScriptModuleRegistry中。同一時候通過動態代理生成代理實例,並在代理攔截類JavaScriptModuleInvocationHandler中統一處理發向Javascript的全部通信請求。

CatalystInstanceImpl類內部的ReactBridge詳細實現與Javascript的通信請求,它是調用Bridge Jni 的出口。

ReactBridge被創建的時候會將JavaScriptModule信息表預先發給Javascript層用來生成映射表。

2、C++層

OnLoadjni層的調用入口,註冊了全部的native方法。其內部調用又都是通過CountableBridge來完畢的,CountableBridgeBridge的無實現子類。而在Bridge裏面JSCExecutor才是真正的運行者。

JSCExecutor將全部來自Java層的通信請求封裝成Javascript運行語句。交給WebKit內核完畢向Javascript層的調用。

3、Javascript層

BatchedBridgeJavascript層的調用入口,而其又是MessageQueue的偽裝者。MessageQueue預先註冊了全部能夠接收通信請求的組件_callableModules 。同一時候也保存著來自JavaJavaScriptModule的兩張映射表。

接收通信請求時,先通過映射表確認詳細請求信息,再確認Javascript組件能否夠被調用,最後通過apply方式完畢運行。

整個通信過程流程例如以下圖:
技術分享圖片


本博客不定期持續更新,歡迎關註和交流:

http://blog.csdn.net/megatronkings

React-Native系列Android——Native與Javascript通信原理(一)