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

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


一、通訊模型php

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

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

因此由此,咱們大概可以猜到React-Native的通訊原理,畫一張圖來簡單地描寫敘述一下:node


二、Java層實現react

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

這套標準是向開發人員開放的,開發人員可以自行編寫需要的組件用來在NativeJavascript之間通訊,雖然這並不是推薦的選擇。json

2.1 JavaScriptModule組件react-native

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

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


三、由於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中的實現了。


三、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


四、Javascript層實現

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

4.一、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.二、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);

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

五、總結

整個通訊過程涉及到三種程序語言:JavaC++Javascript,這還僅僅是單向的通訊流程。假設是逆向則更加複雜。由於篇幅的關係。留到之後的博客裏面研究。

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

一、Java層

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

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

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

二、C++層

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

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

三、Javascript層

BatchedBridgeJavascript層的調用入口,而其又是MessageQueue的假裝者。MessageQueue預先註冊了所有可以接收通訊請求的組件_callableModules 。同一時候也保存着來自JavaJavaScriptModule的兩張映射表。

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

整個通訊過程流程例如如下圖:


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

http://blog.csdn.net/megatronkings

相關文章
相關標籤/搜索