[ReactNative笔记]my-mod适配legecy TurboCxxModule

231 阅读3分钟

准备环境

参考[ReactNative笔记]my-mod适配CxxModule

创建my-mod

参考[ReactNative笔记]my-mod适配CxxModule

origin-nojsi.png

基于CxxModule适配legacy TurboCxxModule

适配JS/TS

  • 新增NativeMyMod.ts到src文件夹
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  multiply(a: number, b: number, callback: (result: number) => void): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('MyMod');
  • 修改index.tsx
const MyMod = require('./NativeMyMod').default;

export function multiply(a: number, b: number): Promise<number> {
  return new Promise(resolve => MyMod.multiply(a, b, resolve));
}
  • 添加codegenConfig到package.json
+  },
+  "codegenConfig": {
+    "name": "RNMyModSpec",
+    "type": "modules",
+    "jsSrcsDir": "src"
   }
 }

适配Android

  • 修改MyModPackage.java
package com.mymod;

import androidx.annotation.Nullable;

import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.CxxModuleWrapper;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;

import java.util.HashMap;
import java.util.Map;

public class MyModPackage extends TurboReactPackage {
  @Nullable
  @Override
  public NativeModule getModule(String name, ReactApplicationContext reactApplicationContext) {
    if (name.equals(MyModModule.NAME)) {
      return MyModModule.makeDso("cpp", "createMyModCxxModule");
    } else {
      return null;
    }
  }

  @Override
  public ReactModuleInfoProvider getReactModuleInfoProvider() {
    return () -> {
      final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
      moduleInfos.put(
        MyModModule.NAME,
        new ReactModuleInfo(
          MyModModule.NAME,
          "MyModModule",
          false,
          false,
          false,
          CxxModuleWrapper.class.isAssignableFrom(MyModModule.class),
          TurboModule.class.isAssignableFrom(MyModModule.class))
      );
      return moduleInfos;
    };
  }
}

  • 重写MyModModule.java
package com.mymod;

import com.facebook.jni.HybridData;
import com.facebook.react.bridge.CxxModuleWrapper;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import com.facebook.soloader.SoLoader;

public class MyModModule extends CxxModuleWrapper implements TurboModule {
  public static final String NAME = "MyMod";

  protected MyModModule(HybridData hd) {
    super(hd);
  }

  private static native MyModModule makeDsoNative(String var0, String var1);

  public static MyModModule makeDso(String library, String factory) {
    SoLoader.loadLibrary(library);
    String soPath = SoLoader.unpackLibraryAndDependencies(library).getAbsolutePath();
    return makeDsoNative(soPath, factory);
  }
}
  • 修改CMakeLists.txt
 add_library(${CMAKE_PROJECT_NAME}
     SHARED
         ../cpp/react-native-my-mod.cpp
+        cpp-adapter.cpp
 )

 # Specifies a path to native header files.
@@ -20,9 +21,11 @@ target_include_directories(${CMAKE_PROJECT_NAME}
 )

 target_link_libraries(${CMAKE_PROJECT_NAME}
+        fbjni
         folly_runtime
-        common_flags
+        glog
         react_nativemodule_core
+        common_flags
 )
  • 重写cpp-adapter.cpp
#include <react/jni/CxxModuleWrapper.h>

#include <glog/logging.h>

#include <dlfcn.h>

using namespace facebook::jni;
using namespace facebook::xplat::module;
using namespace facebook::react;

namespace mymodule {

class MyModModule
: public HybridClass<MyModModule, CxxModuleWrapper> {
 public:
  constexpr static const char *const kJavaDescriptor =
      "Lcom/mymod/MyModModule;";

  static void registerNatives() {
    registerHybrid(
        {makeNativeMethod("makeDsoNative", MyModModule::makeDsoNative)});
  }

  static local_ref<MyModModule::javaobject> makeDsoNative(
      alias_ref<jclass>,
      const std::string& soPath,
      const std::string& fname) {
    // soPath is the path of a library which has already been loaded by
    // java SoLoader.loadLibrary().  So this returns the same handle,
    // and increments the reference counter.  We can't just use
    // dlsym(RTLD_DEFAULT, ...), because that crashes on 4.4.2 and
    // earlier: https://code.google.com/p/android/issues/detail?id=61799
    void* handle = dlopen(soPath.c_str(), RTLD_NOW);
    if (!handle) {
      throwNewJavaException(
          gJavaLangIllegalArgumentException,
          "module shared library %s is not found",
          soPath.c_str());
    }
    // Now, arrange to close the handle so the counter is decremented.
    // The handle will remain valid until java closes it.  There's no
    // way to do this on Android, but that's no reason to be sloppy
    // here.
    auto guard = folly::makeGuard([&] { CHECK(dlclose(handle) == 0); });

    void* sym = dlsym(handle, fname.c_str());
    if (!sym) {
      throwNewJavaException(
          gJavaLangIllegalArgumentException,
          "module function %s in shared library %s is not found",
          fname.c_str(),
          soPath.c_str());
    }
    auto factory = reinterpret_cast<CxxModule* (*)()>(sym);

    return MyModModule::newObjectCxxArgs(
        std::unique_ptr<CxxModule>((*factory)()));
  }

 protected:
  friend HybridBase;
  using HybridBase::HybridBase;
};

}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
  return initialize(
      vm, [] { mymodule::MyModModule::registerNatives(); });
}
  • 配置example/android/gradle.properties
-newArchEnabled=false
+newArchEnabled=true

适配iOS

  • 修改MyMod.h
 #import <React/RCTCxxModule.h>
+#import <ReactCommon/RCTTurboModule.h>

-@interface MyMod : RCTCxxModule
+@interface MyMod : RCTCxxModule <RCTTurboModule>

 - (std::unique_ptr<facebook::xplat::module::CxxModule>)createModule;
  • 修改MyMod.mm
   return std::make_unique<mymod::MyModCxxModule>();
 }

+- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
+    return nullptr;
+}
+
 @end
  • 配置example/ios/Podfile
+ENV['RCT_NEW_ARCH_ENABLED'] = '1'
+
 # Resolve react_native_pods.rb with node to allow for hoisting

测试适配结果

  • Android运行my-mod
$ yarn example start &
$ yarn example android
  • iOS运行my-mod
$ pushd example/ios && pod install && popd
$ yarn example start &
$ yarn example ios

JS/C++的类型/方法转换

std::vector<CxxModule::Method> SampleTurboCxxModuleLegacyImpl::getMethods() {
  return {
      CxxModule::Method(
          "voidFunc", [this](folly::dynamic args) { voidFunc(); }),
      CxxModule::Method(
          "getBool",
          [this](folly::dynamic args) {
            return getBool(xplat::jsArgAsBool(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getEnum",
          [this](folly::dynamic args) {
            return getEnum(xplat::jsArgAsDouble(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getNumber",
          [this](folly::dynamic args) {
            return getNumber(xplat::jsArgAsDouble(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getString",
          [this](folly::dynamic args) {
            return getString(xplat::jsArgAsString(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getString",
          [this](folly::dynamic args) {
            return getString(xplat::jsArgAsString(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getArray",
          [this](folly::dynamic args) {
            return getArray(xplat::jsArgAsArray(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getObject",
          [this](folly::dynamic args) {
            return getObject(xplat::jsArgAsObject(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getUnsafeObject",
          [this](folly::dynamic args) {
            return getUnsafeObject(xplat::jsArgAsObject(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getRootTag",
          [this](folly::dynamic args) {
            return getNumber(xplat::jsArgAsDouble(args, 0));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getValue",
          [this](folly::dynamic args) {
            return getValue(
                xplat::jsArgAsDouble(args, 0),
                xplat::jsArgAsString(args, 1),
                xplat::jsArgAsObject(args, 2));
          },
          CxxModule::SyncTag),
      CxxModule::Method(
          "getValueWithCallback",
          [this](folly::dynamic args, CxxModule::Callback callback) {
            getValueWithCallback(callback);
          }),
      CxxModule::Method(
          "getValueWithPromise",
          [this](
              folly::dynamic args,
              CxxModule::Callback resolve,
              CxxModule::Callback reject) {
            getValueWithPromise(xplat::jsArgAsBool(args, 0), resolve, reject);
          }),
  };
};

folly::dynamic和jsi::Value类型转换

参考TurboCxxModule.hTurboCxxModule.cpp

jsi::Value TurboCxxModule::invokeMethod(
    jsi::Runtime &runtime,
    const std::string &methodName,
    const jsi::Value *args,
    size_t count) {
  auto it = cxxMethods_.begin();
  for (; it != cxxMethods_.end(); it++) {
    auto method = *it;
    if (method.name == methodName) {
      break;
    }
  }

  if (it == cxxMethods_.end()) {
    throw std::runtime_error(
        "Function '" + methodName + "' cannot be found on cxxmodule: " + name_);
  }

  auto method = *it;

  if (method.syncFunc) {
    auto innerArgs = folly::dynamic::array();
    for (size_t i = 0; i < count; i++) {
      innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
    }
    return jsi::valueFromDynamic(
        runtime, method.syncFunc(std::move(innerArgs)));
  } else if (method.func && !method.isPromise) {
    // Async method.
    CxxModule::Callback first;
    CxxModule::Callback second;

    if (count < method.callbacks) {
      throw std::invalid_argument(folly::to<std::string>(
          "Expected ",
          method.callbacks,
          " callbacks, but only ",
          count,
          " parameters provided"));
    }

    if (method.callbacks == 1) {
      auto wrapper = CallbackWrapper::createWeak(
          args[count - 1].getObject(runtime).getFunction(runtime),
          runtime,
          jsInvoker_);
      first = makeTurboCxxModuleCallback(runtime, wrapper);
    } else if (method.callbacks == 2) {
      auto wrapper1 = CallbackWrapper::createWeak(
          args[count - 2].getObject(runtime).getFunction(runtime),
          runtime,
          jsInvoker_);
      auto wrapper2 = CallbackWrapper::createWeak(
          args[count - 1].getObject(runtime).getFunction(runtime),
          runtime,
          jsInvoker_);
      first = makeTurboCxxModuleCallback(runtime, wrapper1);
      second = makeTurboCxxModuleCallback(runtime, wrapper2);
    }

    auto innerArgs = folly::dynamic::array();
    for (size_t i = 0; i < count - method.callbacks; i++) {
      innerArgs.push_back(jsi::dynamicFromValue(runtime, args[i]));
    }

    method.func(std::move(innerArgs), first, second);
  } else if (method.isPromise) {
    return createPromiseAsJSIValue(
        runtime,
        [method, args, count, this](
            jsi::Runtime &rt, std::shared_ptr<Promise> promise) {
          auto resolveWrapper = CallbackWrapper::createWeak(
              promise->resolve_.getFunction(rt), rt, jsInvoker_);
          auto rejectWrapper = CallbackWrapper::createWeak(
              promise->reject_.getFunction(rt), rt, jsInvoker_);
          CxxModule::Callback resolve =
              makeTurboCxxModuleCallback(rt, resolveWrapper);
          CxxModule::Callback reject =
              makeTurboCxxModuleCallback(rt, rejectWrapper);

          auto innerArgs = folly::dynamic::array();
          for (size_t i = 0; i < count; i++) {
            innerArgs.push_back(jsi::dynamicFromValue(rt, args[i]));
          }

          method.func(std::move(innerArgs), resolve, reject);
        });
  }

  return jsi::Value::undefined();
}