React Native通訊原理

之前寫過一篇文章 ReactNative Android源碼分析,在此文章的基礎上分析和總結下RN與Native的通訊流程。
本文基于Android代碼分析,iOS實現原理類似。

1. ?通訊框架圖

通訊框架圖

先來解析下各個模塊的角色與作用:

Java層,這塊的實現在ReactAndroid中

  • ReactContext : Android上下文子類,包含一個CatalystInstance實例,用于獲取NativeModule,JSModule、添加各種回調、處理異常等
  • ReactInstanceManager : 管理CatalystInstance的實例,處理RN Root View,啟動JS頁面,管理生命周期
  • CatalystInstance : 通訊的關鍵類,提供調用JS Module也支持JS調用Native Module,與Bridge進行交互,對開發者不可見

C++層,這塊實現在ReactCommon中,供Android與iOS使用

  • NativeToJsBridge : native與JS的橋接,負責調用JS Module、回調Native(調用JsToNativeBridge)、加載JS代碼(調用JavaScriptCore)
  • JsToNativeBridge : 調用Native Module的方法
  • JSCExecutor : 加載/執行JS代碼(調用JavaScriptCore)、調用JS Module、回調native、性能統計等,都是比較核心的功能

JS層,實現在Libraries中,RN JS相關的實現在都這個文件夾中

  • MessageQueue : 管理JS的調用隊列、調用Native/JS Module的方法、執行callback、管理JS Module等
  • JavaScriptModule : 代指所有的JSModule實現,在java層中也有對應的代碼(都是interface),使用動態代理調用,統一入口在CatalystInstance中

2. C++與JS間通訊

Native與JS通訊無非就是Java/OC與JS跨語言間的調用,在分析Native與JS通訊前先來了解下Java/OC與JS跨語言間的調用。
在ReactNative中使用JavaScriptCore來執行JS,這部分的關鍵就是如何利用JavaScriptCore。
看一下Android編譯腳本:

  • ReactAndroid/build.gradle
  compile 'org.webkit:android-jsc:r174650'

    task downloadJSCHeaders(type: Download) {
        def jscAPIBaseURL = 'https://svn.webkit.org/repository/webkit/  !svn/bc/174650/trunk/Source/JavaScriptCore/API/'
        def jscHeaderFiles = ['JavaScript.h', '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
    }
    
    // Create Android.mk library module based on so files from mvn + include headers fetched from webkit    .org
    task prepareJSC(dependsOn: downloadJSCHeaders) << {
        copy {
            from zipTree(configurations.compile.fileCollection { dep -> dep.name == 'android-jsc' }.    singleFile)
            from {downloadJSCHeaders.dest}
            from 'src/main/jni/third-party/jsc/Android.mk'
            include 'jni/**/*.so', '*.h', 'Android.mk'
            filesMatching('*.h', { fname -> fname.path = "JavaScriptCore/${fname.path}"})
            into "$thirdPartyNdkDir/jsc";
        }
    }

  • ReactAndroid/src/main/jni/third-party/jsc/Android.mk
LOCAL_SRC_FILES := jni/$(TARGET_ARCH_ABI)/libjsc.so

從這里可以看出RN并沒有用系統自帶的webkit,WebKit主要包括WebCore排版引擎和JSCore引擎,這里主要使用了JSCore引擎,排版交給Native去做。

在RN中通過下面的方法設置native方法和屬性:

JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL);

這個方法正是上面gradle腳本下載的JSObjectRef.h中,實現在libjsc.so中。這樣就可以在Native設置,然后在JS中取出執行,反過來也是同樣的。

3. Native與JS通訊

加載bundle文件

Native與JS的通訊首先需要加載Bundle文件,是在native初始化完成的時候,而Bundle文件的位置是可配置的。

public abstract class ReactNativeHost {

  ...

  /**
   * Returns the name of the main module. Determines the URL used to fetch the JS bundle
   * from the packager server. It is only used when dev support is enabled.
   * This is the first file to be executed once the {@link ReactInstanceManager} is created.
   * e.g. "index.android"
   */
  protected String getJSMainModuleName() {
    return "index.android";
  }

  /**
   * Returns a custom path of the bundle file. This is used in cases the bundle should be loaded
   * from a custom path. By default it is loaded from Android assets, from a path specified
   * by {@link getBundleAssetName}.
   * e.g. "file://sdcard/myapp_cache/index.android.bundle"
   */
  protected @Nullable String getJSBundleFile() {
    return null;
  }

  /**
   * Returns the name of the bundle in assets. If this is null, and no file path is specified for
   * the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will
   * always try to load the JS bundle from the packager server.
   * e.g. "index.android.bundle"
   */
  protected @Nullable String getBundleAssetName() {
    return "index.android.bundle";
  }

  /**
   * Returns whether dev mode should be enabled. This enables e.g. the dev menu.
   */
  protected abstract boolean getUseDeveloperSupport();

  ...

}

ReactNativeHost中的這些方法會根據需要在Application中重載,這些方法決定了從哪里加載Bundle,方法的注釋寫的非常清晰,不再介紹了,先看一下流程圖:

Bundle加載流程圖

JSBundleLoader?從哪里加載,也是根據文件的位置,可以看看其loadScript方法,最終都會調用CatalystIntance去加載,有三個實現

  /* package */ native void loadScriptFromAssets(AssetManager assetManager, String assetURL);
  /* package */ native void loadScriptFromFile(String fileName, String sourceURL);
  /* package */ native void loadScriptFromOptimizedBundle(String path, String sourceURL, int flags);

最后一個支持加載優化后的Bundle,目前沒有用到。這些方法都是c++實現,主要看一下前兩個

void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager,
                                                const std::string& assetURL) {
  const int kAssetsLength = 9;  // strlen("assets://");
  auto sourceURL = assetURL.substr(kAssetsLength);

  auto manager = react::extractAssetManager(assetManager);
  auto script = react::loadScriptFromAssets(manager, sourceURL);
  if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) {
    instance_->loadUnbundle(
      folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL),
      std::move(script),
      sourceURL);
    return;
  } else {
    instance_->loadScriptFromString(std::move(script), sourceURL);
  }
}

void CatalystInstanceImpl::loadScriptFromFile(jni::alias_ref<jstring> fileName,
                                              const std::string& sourceURL) {
  return instance_->loadScriptFromFile(fileName ? fileName->toStdString() : "",
                                       sourceURL);
}

從assets中加載就是先讀取bundle的內容,當作一個字符串,這里有一個UnBundle,是RN打包的一種方式,除了生成整合JS文件index.android.bundle外,還會生成各個單獨的未整合JS文件(但會被優化),全部放在js-modules目錄下,同時會生成一個名為UNBUNDLE的標識文件,一并放在其中。UNBUNDLE標識文件的前4個字節固定為0xFB0BD1E5,用于加載前的校驗。需要注意的是,js-modules目錄會一并打包到apkassets文件夾中,這里就是處理這種情況的,后面具體的加載暫不分析,可以參考這篇文章
對于開發模式有點特殊,在創建ReactInstanceManager之前會從server下載Bundle文件,然后保存起來,demo程序的路徑為:

/data/user/0/com.awesomeproject/files/ReactNativeDevBundle.js

下載完成后調用CatalystInstance.loadScriptFromFile(),傳遞緩存后的徑路,這個方法也是先讀取文件內容,存為字符串,也是調用loadScriptFromString

void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
                                    std::string sourceURL) {
  callback_->incrementPendingJSCalls();
  SystraceSection s("reactbridge_xplat_loadScriptFromString",
                    "sourceURL", sourceURL);
  // TODO mhorowitz: ReactMarker around loadApplicationScript
  nativeToJsBridge_->loadApplicationScript(std::move(string), std::move(sourceURL));
}
--------------------------------------
void NativeToJsBridge::loadApplicationScript(std::unique_ptr<const JSBigString> script,
                                             std::string sourceURL) {
  // TODO(t11144533): Add assert that we are on the correct thread
  m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL));
}

loadApplicationScript的作用請參考ReactNative Android源碼分析
這里JS代碼已經被執行了。

如何調用JS
Native調用JS序列圖

在Native中調用JS的方式如下 :

ReactContext.getJSModule(JSModule類名.class).方法名(params);

ReactContext調用的是CatalystInstance的同名方法

  @Override
  public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
    return getJSModule(mMainExecutorToken, jsInterface);
  }

  @Override
  public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
    return Assertions.assertNotNull(mJSModuleRegistry)
        .getJavaScriptModule(this, executorToken, jsInterface);
  }

mMainExecutorToken是在initializeBridge時創建的,?根據注釋是和web workers相關,是JS多線程相關的,即使用Token來區分線程。當前這種情況使用mMainExecutorToken就可以。看一下CatalystInstance的實現:

  public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
    CatalystInstance instance,
    ExecutorToken executorToken,
    Class<T> moduleInterface) {
    HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> instancesForContext =
        mModuleInstances.get(executorToken);
    if (instancesForContext == null) {
      instancesForContext = new HashMap<>();
      mModuleInstances.put(executorToken, instancesForContext);
    }

    JavaScriptModule module = instancesForContext.get(moduleInterface);
    if (module != null) {
      return (T) module;
    }

    JavaScriptModuleRegistration registration =
        Assertions.assertNotNull(
            mModuleRegistrations.get(moduleInterface),
            "JS module " + moduleInterface.getSimpleName() + " hasn't been registered!");
    JavaScriptModule interfaceProxy = (JavaScriptModule) Proxy.newProxyInstance(
        moduleInterface.getClassLoader(),
        new Class[]{moduleInterface},
        new JavaScriptModuleInvocationHandler(executorToken, instance, registration));
    instancesForContext.put(moduleInterface, interfaceProxy);
    return (T) interfaceProxy;
  }

在CatalystInstance創建的時候會把所有JavaScriptModule都收集到JavaScriptModuleRegistry的Map(mModuleRegistrations)中。而mModuleInstances是緩存已經調用過的JS Module的代理對象,如果已經調用過,則從map中直接返回,否則創建其代理對象,然后緩存起來。
這里使用的是動態代理模式,先創建一個interface的代理對象,當調用其方法時會InvocationHandler的invoke()方法。

  @Override
  public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args) throws 
Throwable {
    ExecutorToken executorToken = mExecutorToken.get();
    if (executorToken == null) {
      FLog.w(ReactConstants.TAG, "Dropping JS call, ExecutorToken went away...");
      return null;
    }
    NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
    mCatalystInstance.callFunction(
      executorToken,
      mModuleRegistration.getName(),
      method.getName(),
      jsArgs
    );
    return null;
  }

這個調用流程已經在ReactNative Android源碼分析中分析了。這次會走到JS的MessageQueue.callFunctionReturnFlushedQueue()中了。

JS接收調用和處理

先來解釋下為什么會走到callFunctionReturnFlushedQueue。

  1. 在生成的bundle.js中會把MessageQueue對象放到一個全局的屬性中
Object.defineProperty(global,"__fbBatchedBridge",{configurable:!0,value:BatchedBridge})

這里明明是BatchedBridge,為什么說是MessageQueue的對象呢,原來在BatchedBridge.js中有這樣幾句代碼

const BatchedBridge = new MessageQueue(
  () => global.__fbBatchedBridgeConfig,
  serializeNativeParams
);
  1. 在上面加載bundle文件的時候,會執行下面的方法
void JSCExecutor::bindBridge() throw(JSException) {
      auto global = Object::getGlobalObject(m_context);
      auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
      if (batchedBridgeValue.isUndefined()) {
        throwJSExecutionException("Could not get BatchedBridge, make sure your bundle is packaged   correctly");
      }
  
      auto batchedBridge = batchedBridgeValue.asObject();
      m_callFunctionReturnFlushedQueueJS = batchedBridge.getProperty("callFunctionReturnFlushedQueue"). asObject();
      m_invokeCallbackAndReturnFlushedQueueJS = batchedBridge.getProperty(  "invokeCallbackAndReturnFlushedQueue").asObject();
      m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
}

這里會把MessageQueue的三個方法會當作對象保存在c++中,當我們調用JS的方法時會直接用到。

void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
      try {
        auto result = m_callFunctionReturnFlushedQueueJS->callAsFunction({
          Value(m_context, String::createExpectingAscii(moduleId)),
          Value(m_context, String::createExpectingAscii(methodId)),
          Value::fromDynamic(m_context, std::move(arguments))
        });
        auto calls = Value(m_context, result).toJSONString();
        m_delegate->callNativeModules(*this, std::move(calls), true);
      } catch (...) {
        std::throw_with_nested(std::runtime_error("Error calling function: " + moduleId + ":" + methodId));
      }
}
Value Object::callAsFunction(JSObjectRef thisObj, int nArgs, const JSValueRef args[]) const {
      JSValueRef exn;
      JSValueRef result = JSObjectCallAsFunction(m_context, m_obj, thisObj, nArgs, args, &exn);
      if (!result) {
        std::string exceptionText = Value(m_context, exn).toString().str();
        throwJSExecutionException("Exception calling object as function: %s", exceptionText.c_str());
      }
      return Value(m_context, result);
}

最終還是通過JavaScriptCore的方法JSObjectCallAsFunction來調用JS的。下面就好辦了,直接分析JS代碼吧。

在callFunctionReturnFlushedQueue這個方法主要調用了__callFunction,來看一下它的實現:

  __callFunction(module: string, method: string, args: any) {
    ...
    const moduleMethods = this._callableModules[module];
    ...
    const result = moduleMethods[method].apply(moduleMethods, args);
    Systrace.endEvent();
    return result;
  }

方法是從_callableModules中取出來的,那他的值是從哪里來的呢,看了下這個文件原來答案是有往里添加的方法

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

也就是說所有的JS Module都需要把該Module中可供Native調用的方法都放到這里來,這樣才能夠執行。以AppRegistry.js為例,來看看它是怎么往里添加的

var AppRegistry = {
  registerConfig: function(config: Array<AppConfig>) {...},

  registerComponent: function(appKey: string, getComponentFunc: ComponentProvider): string {...},

  registerRunnable: function(appKey: string, func: Function): string {...},

  getAppKeys: function(): Array<string> {...},

  runApplication: function(appKey: string, appParameters: any): void {...},

  unmountApplicationComponentAtRootTag: function(rootTag : number) {...},

};

BatchedBridge.registerCallableModule(
  'AppRegistry',
  AppRegistry
);

到這里Native調用JS就已經完成了。
總結一下?整個流程:

  1. MessageQueue把Native調用的方法放到JavaScriptCore中
  2. JS Module?把可以調用的方法放到MessageQueue的一個對列中
  3. Native從JavaScriptCore中拿到JS的調用入口,并把Module Name、Method Name、Parameters傳過去
  4. 執行JS Module的方法

4. JS與Native通訊

JS處理Native Module列表

ReactNative Android源碼分析中分析了Native的初始化流程,這里總結一下對Native 模塊的處理。

  1. 在初始化CatalystInstance時會把所有的Native Module放在一個列表中,并在C++(ModuleRegistry)和Java(NativeModuleRegistry)中都保存了
  2. 在JavaScriptCore中設置了全局屬性__fbBatchedBridgeConfig,其值為Module Name列表

那么問題來了,在JS中只能取到Native Module的名字,怎么調用它的方法呢。下面來分析下這個問題。
在JSCExecutor初始化的時候,向JavaScriptCore中注冊了幾個c++的方法供JS調用,其中就有獲取Native Module詳細信息的方法

void JSCExecutor::initOnJSVMThread() throw(JSException) {
  
  ....
  installNativeHook<&JSCExecutor::nativeRequireModuleConfig>("nativeRequireModuleConfig");
  installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
  ...
  
}

JSValueRef JSCExecutor::nativeRequireModuleConfig(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if (argumentCount != 1) {
    throw std::invalid_argument("Got wrong number of args");
  }

  std::string moduleName = Value(m_context, arguments[0]).toString().str();
  folly::dynamic config = m_delegate->getModuleConfig(moduleName);
  return Value::fromDynamic(m_context, config);
}

從nativeRequireModuleConfig的入參和返回結果就可以看出來
是供JS調用的,用于獲取Native Module詳情信息,m_delegate-> getModuleConfig的實現下面會分析。

接著來分析下JS是如何處理Native Module的。入口是在MessageQueue.js中處理的。

class MessageQueue {
  constructor(configProvider: () => Config, serializeNativeParams: boolean) {
    ....
    lazyProperty(this, 'RemoteModules', () => {
      const {remoteModuleConfig} = configProvider();
      const modulesConfig = this._genModulesConfig(remoteModuleConfig);
      const modules = this._genModules(modulesConfig)
      ...
      return modules;
    });
  }

  ...

  function lazyProperty(target: Object, name: string, f: () => any) {
  Object.defineProperty(target, name, {
    configurable: true,
    enumerable: true,
    get() {
      const value = f();
      Object.defineProperty(target, name, {
        configurable: true,
        enumerable: true,
        writeable: true,
        value: value,
      });
      return value;
    }
  });
}

在它的構造函數中定義了一個RemoteModules的屬性,使用了懶加載的機制,只有真正使用的時候才會為其賦值。返回的是所有Modle列表,只添加了module id,其他信息并沒有。

這個RemoteModules是在哪里使用,Module的其他信息又是怎么獲取呢,路漫漫其修遠兮,接著分析吧
搜了下代碼,是在NativeModule.js中

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

...

/**
 * Define lazy getters for each module.
 * These will return the module if already loaded, or load it if not.
 */
const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
  Object.defineProperty(NativeModules, moduleName, {
    configurable: true,
    enumerable: true,
    get: () => {
      let module = RemoteModules[moduleName];
      if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
        // The old bridge (still used by iOS) will send the config as
        //  a JSON string that needs parsing, so we set config according
        //  to the type of response we got.
        const rawConfig = global.nativeRequireModuleConfig(moduleName);
        const config = typeof rawConfig === 'string' ? JSON.parse(rawConfig) : rawConfig;
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      Object.defineProperty(NativeModules, moduleName, {
        configurable: true,
        enumerable: true,
        value: module,
      });
      return module;
    },
  });
});

module.exports = NativeModules;

這塊會遍歷RemoteModules中所有的模塊名,每個模塊名都定義一個對象,使用的時候才會為其賦值。看到在賦值的時候會調用c++的nativeRequireModuleConfig,也就是獲取每個Module的詳細信息。
?獲取詳細信息就是調用上面提到的m_delegate->getModuleConfig(moduleName),m_delegate是JsToNativeBridge對象,getModuleConfig直接調用了ModuleRegistry::getConfig(name)

folly::dynamic ModuleRegistry::getConfig(const std::string& name) {
  SystraceSection s("getConfig", "module", name);
  auto it = modulesByName_.find(name);
  if (it == modulesByName_.end()) {
    return nullptr;
  }
  CHECK(it->second < modules_.size());

  NativeModule* module = modules_[it->second].get();

  // string name, [object constants,] array methodNames (methodId is index), [array asyncMethodIds]
  folly::dynamic config = folly::dynamic::array(name);

  {
    SystraceSection s("getConstants");
    folly::dynamic constants = module->getConstants();
    if (constants.isObject() && constants.size() > 0) {
      config.push_back(std::move(constants));
    }
  }

  {
    SystraceSection s("getMethods");
    std::vector<MethodDescriptor> methods = module->getMethods();

    folly::dynamic methodNames = folly::dynamic::array;
    folly::dynamic asyncMethodIds = folly::dynamic::array;
    folly::dynamic syncHookIds = folly::dynamic::array;

    for (auto& descriptor : methods) {
      methodNames.push_back(std::move(descriptor.name));
      if (descriptor.type == "remoteAsync") {
        asyncMethodIds.push_back(methodNames.size() - 1);
      } else if (descriptor.type == "syncHook") {
        syncHookIds.push_back(methodNames.size() - 1);
      }
    }

    if (!methodNames.empty()) {
      config.push_back(std::move(methodNames));
      config.push_back(std::move(asyncMethodIds));
      if (!syncHookIds.empty()) {
        config.push_back(std::move(syncHookIds));
      }
    }
  }

  if (config.size() == 1) {
    // no constants or methods
    return nullptr;
  } else {
    return config;
  }
}

這里需要解釋兩個數據結構,modules_是所有Native模塊對象的數組,而modulesByName_是一個Map,key值是模塊名字,value是該模塊在modules_中的索引值。這個方法返回值是一個數組,它的格式是

[
  "Module Name",
  [Object Constants],
  [Method Name Array],
  [Async Method Ids],
  [Sync Hook Ids]
]

前三個好理解,來解釋后兩是什么含意,asyncMethod字面意思是異步方法,也就是方法參數是Promise的。而syncHook類的方法,目前是沒有遇到,這種方法可以JS線程中直接調用,而其他的方法是扔到后臺線程隊列,然后等待被調用。
下面重點看一下NativeModule的getConstants和getMethods的實現。

NativeModule類圖

從NativeModule的類圖中看出它有兩個子類,JavaNativeModule就是普通的Java模塊,而NewJavaNativeModule是指c++跨平臺模塊,目前還未使用到,所以現在只分析下JavaNativeModule。

  std::vector<MethodDescriptor> getMethods() override {
    static auto getMDMethod =
      wrapper_->getClass()->getMethod<jni::JList<JMethodDescriptor::javaobject>::javaobject()>(
        "getMethodDescriptors");

    std::vector<MethodDescriptor> ret;
    auto descs = getMDMethod(wrapper_);
    for (const auto& desc : *descs) {
      static auto nameField =
        JMethodDescriptor::javaClassStatic()->getField<jstring>("name");
      static auto typeField =
        JMethodDescriptor::javaClassStatic()->getField<jstring>("type");

      ret.emplace_back(
        desc->getFieldValue(nameField)->toStdString(),
        desc->getFieldValue(typeField)->toStdString()
      );
    }
    return ret;
  }

  folly::dynamic getConstants() override {
    static auto constantsMethod =
      wrapper_->getClass()->getMethod<NativeArray::javaobject()>("getConstants");
    auto constants = constantsMethod(wrapper_);
    if (!constants) {
      return nullptr;
    } else {
      // See JavaModuleWrapper#getConstants for the other side of this hack.
      return cthis(constants)->array[0];
    }
  }

這里的wrapper_是指的JavaModuleWrapper,而wrapper_->getClass()是指的Java類:"Lcom/facebook/react/cxxbridge/JavaModuleWrapper;",也就是說上面是使用反射,調用JavaModuleWrapper的getMethodDescriptors和getConstants。
getContants就是調用Java具體模塊的getConstants方法,并把返回的map組裝成RN可以接受的WritableNativeMap的結構返回,具體看一下getMethodDescriptors的實現

@DoNotStrip
public class MethodDescriptor {
  @DoNotStrip
  Method method;
  @DoNotStrip
  String signature;
  @DoNotStrip
  String name;
  @DoNotStrip
  String type;
}
@DoNotStrip
public List<MethodDescriptor> getMethodDescriptors() {
  ArrayList<MethodDescriptor> descs = new ArrayList<>();
  for (Map.Entry<String, BaseJavaModule.NativeMethod> entry :
         mModule.getMethods().entrySet()) {
    MethodDescriptor md = new MethodDescriptor();
    md.name = entry.getKey();
    md.type = entry.getValue().getType();
    BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
    mMethods.add(method);
    descs.add(md);
  }
  return descs;
}

mModule.getMethods()是在BaseJavaModule中,也是使用反射查找當前模塊的方法,方法必須有@ReactMethod的注解才會收集。最終把拿到的信息封裝成一個MethodDescriptor的類。

到這里就已經分析了JS是如何拿到Native模塊的詳細信息的。

如何調用Native
JS調用Native序列圖

這里演示下在JS中如何調用Native的module,先假設一個場景,用戶點擊一個TextView,然后彈個Toast提示。
以demo工程的代碼為例:

class AwesomeProject extends Component {
  render() {                                                                                                                           
    return (
      <View style={styles.container}>
        <Text style={styles.welcome} onPress={onClick} >
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.android.js
        </Text>
        <Text style={styles.instructions}>
          Double tap R on your keyboard to reload,{'\n'}
          Shake or press menu button for dev menu
        </Text>
        <TextInput />
      </View>
    );
  }
}

function onClick(){
   var ToastAndroid = require('ToastAndroid')
   ToastAndroid.show('Click TextView...', ToastAndroid.SHORT);
}

來看一下ToastAndroid的實現

var RCTToastAndroid = require('NativeModules').ToastAndroid;
...
var ToastAndroid = {
  ...
  show: function (
    message: string,
    duration: number
  ): void {
    RCTToastAndroid.show(message, duration);
  },
  ...
};

這里調用的是RCTToastAndroid.show(),而RCTToastAndroid是從NativeModules中取出的。 在前面分析JS如何收集Native模塊的時候會生成modules屬性,調用Native方法時就是執行它里面的函數,看一下這個函數是如何生成的

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

就是準備好參數,然后__nativeCall。

  __nativeCall(module, method, params, onFail, onSucc) {
    if (onFail || onSucc) {
      ...
      onFail && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onFail;
      onSucc && params.push(this._callbackID);
      this._callbacks[this._callbackID++] = onSucc;
    }
    var preparedParams = this._serializeNativeParams ? JSON.stringify(params) : params;
    ...
    this._callID++;

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

    const now = new Date().getTime();
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
    Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
    ...
  }

把?模塊名、方法名、調用參數放到數組里存起來,如果上次調用和本次調用想著超過5ms則調用c++的nativeFlushQueueImmediate方法,如果小于5ms就直接返回了。

Native接收調用和處理

Native接收JS調用分兩種情況:

  • 兩次調用超過5ms時,進入nativeFlushQueueImmediate
  • Native調用JS的時候會把之前存的調用返回到JSCExecutor::flush()

先來看第一種情況

JSValueRef JSCExecutor::nativeFlushQueueImmediate(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if (argumentCount != 1) {
    throw std::invalid_argument("Got wrong number of args");
  }

  std::string resStr = Value(m_context, arguments[0]).toJSONString();
  flushQueueImmediate(std::move(resStr));
  return JSValueMakeUndefined(m_context);
}

void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
  m_delegate->callNativeModules(*this, std::move(queueJSON), false);
}

再來看一下第二種情況

void JSCExecutor::flush() {
  auto result = m_flushedQueueJS->callAsFunction({});
  try {
    auto calls = Value(m_context, result).toJSONString();
    m_delegate->callNativeModules(*this, std::move(calls), true);
  } catch (...) {
    std::string message = "Error in flush()";
    try {
      message += ":" + Value(m_context, result).toString().str();
    } catch (...) {
      // ignored
    }
    std::throw_with_nested(std::runtime_error(message));
  }
}

結果都是一樣的,把JS的調用轉成一個Json字符串,然后再調用JsToNativeBridge.callNativeModules().
?這個Json字符串,是一個數組,包含四個元素,?格式如下:

  void callNativeModules(
      JSExecutor& executor, std::string callJSON, bool isEndOfBatch) override {
    ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
    m_nativeQueue->runOnQueue([this, token, callJSON=std::move(callJSON), isEndOfBatch] {
      // An exception anywhere in here stops processing of the batch.  This
      // was the behavior of the Android bridge, and since exception handling
      // terminates the whole bridge, there's not much point in continuing.
      for (auto& call : react::parseMethodCalls(callJSON)) {
        m_registry->callNativeMethod(
          token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
      }
      if (isEndOfBatch) {
        m_callback->onBatchComplete();
        m_callback->decrementPendingJSCalls();
      }
    });
  }

這里根據ModuleId 和 MethodId調用Native模塊的方法。m_registry是c++的ModuleRegistry,先介紹它是怎么創建的。在CatalystInstance.initializeBridge()的時候傳遞一個Java層的ModuleRegistryHolder,同樣在c++中也有一個同名的對象,在創建的時候會把Native的Module列表保存起來并創建一個c++的ModuleRegistry,把Native的?模塊列表也傳過去了。

void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
                                      folly::dynamic&& params, int callId) {
    if (moduleId >= modules_.size()) {
      throw std::runtime_error(
        folly::to<std::string>("moduleId ", moduleId,
                               " out of range [0..", modules_.size(), ")"));
    }

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

  modules_[moduleId]->invoke(token, methodId, std::move(params));
}

moduleId就是模塊在列表中的索引,modules_的類型是

std::vector<std::unique_ptr<NativeModule>> modules_;

也就是在創建ModuleRegistryHolder的時候會根據Java層的ModuleRegistryHolder創建c++的NativeModule。來看一下它的invoke方法

  void invoke(ExecutorToken token, unsigned int reactMethodId, folly::dynamic&& params) override {
    static auto invokeMethod =
      wrapper_->getClass()->getMethod<void(JExecutorToken::javaobject, jint, ReadableNativeArray::javaobject)>("invoke");
    invokeMethod(wrapper_, JExecutorToken::extractJavaPartFromToken(token).get(), static_cast<jint>(reactMethodId),
                 ReadableNativeArray::newObjectCxxArgs(std::move(params)).get());
  }

這里主要是通過反射,調用JavaModuleWrapper的invoke方法,同時把methodId和參數傳過去。

/* package */ class JavaModuleWrapper {
  ...
  private final ArrayList<BaseJavaModule.JavaMethod> mMethods;

  ...

  @DoNotStrip
  public void invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters) {
    if (mMethods == null || methodId >= mMethods.size()) {
      return;
    }

    mMethods.get(methodId).invoke(mCatalystInstance, token, parameters);
  }
}

在JavaModuleWrapper中有一個List,包含了這個module中所有JS可以調用的方法,methodId就是方法的索引和MessageQueue里獲取的模塊方法id是一致的。JavaMethod的invoke就是通過反射調用相關的方法。至此JS調用Native的流程就完成了。

歡迎評論,多多交流

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容