一起学习React Native(一)

786 阅读8分钟

Native模块接入

有时候 App 需要访问平台 API,但React Native 可能还没有相应的模块包装;或者你需要复用一些 Java 代码,而不是用Javascript 重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。下面以官方Toast模块接入JS为例子进行分析。

1. 创建Module

首先创建一个ToastModule继承ReactContextBaseJavaModule,并重写getName方法。这个函数用于返回一个字符串名字,这个名字在 JavaScript 端标记这个模块。这里我们把这个模块叫做ToastExample,这样就可以在 JavaScript 中通过NativeModules.ToastExample访问到这个模块。

public class ToastModule extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public ToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @NonNull
    @Override
    public String getName() {
        return "ToastExample";
    }

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        return constants;
    }

    @ReactMethod
    public void show(String message, int duration) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
    }
}

一个可选的方法getContants返回了需要导出给JavaScript使用的常量。它并不一定需要实现,但在定义一些可以被 JavaScript 同步访问到的预定义的值时非常有用。

要导出一个方法给JavaScript 使用,Java 方法需要使用注解@ReactMethod。方法的返回类型必须为voidReact Native 的跨语言访问是异步进行的,所以想要给JavaScript 返回一个值的唯一办法是使用回调函数或者发送事件。

参数类型

下面的参数类型在@ReactMethod注明的方法中,会被直接映射到它们对应的 JavaScript 类型。

Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array

2. 注册Module

我们需要在应用的Package类的createNativeModules方法中添加这个模块。如果模块没有被注册,它也无法在JavaScript 中被访问到。

public class CustomToastPackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new ToastModule(reactContext));

        return modules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

在Application中注册

这个 package 需要在MainApplication.java文件的getPackages方法中提供。这个文件位于你的 react-native应用文件夹的 android 目录中。

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
            packages.add(new CustomToastPackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };

通过以上步骤就可以在Native注册一个Module提供给JS端进行使用了。

接入原理

在整个接入的流程中,有几个关键类。

  • ProxyJavaScriptExecutor
  • JavaJSExecutor

接入Native模块的第一个步骤是实现ReactContextBaseJavaModule类,一般实现这个类会重写2个方法。getName()方法返回一个名字,这个名字作为接入JS端Module的名字,后续可以使用NativeModules.ModuleName进行使用Module。

  @Override
  public String getName() {
    return "ToastExample";
  }

getConstants()方法返回的是一个Map,内部保存着相关的常量信息,在接入之后可以使用NativeModules.ModuleName.key通过key来查找value获取常量的值。

  @Override
  public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

在这个过程,可能需要实现一些方法封装在Module中提供给JS侧进行使用。需要使用@ReactMethod注解自定义的方法。

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }

在内部还有一些其他方法。有兴趣的同学可以自己去看看,注释都解释的比较清晰。

紧接着是实现ReactPackage接口。总共有2个方法,一个用来返回ViewManager列表,另一个返回NativeModule列表。Module接入自然就是在createNativeModules()方法中返回我们上面实现的Module类就好了。

public interface ReactPackage {
  @NonNull
  List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext);

  @NonNull
  List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext);
}

ViewManager的接入在后续的学习中会补充上的。

最终在Application中会创建一个ReactNativeHost并在这里进行注册Module。getPackages()方法中返回我们实现的ReactPackage子类实现注册。ReactNativeHost内部会维护一个ReactInstanceManager对象,并将信息都传递给ReactInstanceManager进行处理。在创建ReactInstanceManager对象的过程中添加注册的Package并保存。

/* package */ ReactInstanceManager(...) {
	...
  mPackages = new ArrayList<>();
  synchronized (mPackages) {
    PrinterHolder.getPrinter()
        .logMessage(ReactDebugOverlayTags.RN_CORE, "RNCore: Use Split Packages");
    mPackages.add(
        new CoreModulesPackage(
            this,
            new DefaultHardwareBackBtnHandler() {
              @Override
              public void invokeDefaultOnBackPressed() {
                ReactInstanceManager.this.invokeDefaultOnBackPressed();
              }
            },
            mUIImplementationProvider,
            lazyViewManagersEnabled,
            minTimeLeftInFrameForNonBatchedOperationMs));
    if (mUseDeveloperSupport) {
      mPackages.add(new DebugCorePackage());
    }
    mPackages.addAll(packages);
  }
	...
}

在方法中可以看出有两个额外的Package会被添加到list中进行保存。一个是CoreModulesPackage,里面是一些核心的Package。如果在ReactNativeHost中重写了getUseDeveloperSupport()方法返回true,则会增加DebugCorePackage

使用@ReactModuleList注解CoreModulesPackage@ReactModuleList注解传入module列表,这些modules会被处理成ReactModuleInfos进行保存。其他都比较简单,就是对一些module的处理过程。将这些module转化为ReactModuleInfosDebugCorePackage类似不做阐述。

在添加Package之后,处理在ReactInstanceManager中的processPackages()方法中进行处理。

private NativeModuleRegistry processPackages(
    ReactApplicationContext reactContext,
    List<ReactPackage> packages,
    boolean checkAndUpdatePackageMembership) {
  NativeModuleRegistryBuilder nativeModuleRegistryBuilder =
      new NativeModuleRegistryBuilder(reactContext, this);
	...
  synchronized (mPackages) {
    for (ReactPackage reactPackage : packages) {
      ...
        // 在这里处理Package
        processPackage(reactPackage, nativeModuleRegistryBuilder);
    }
  }
  ...
    nativeModuleRegistry = nativeModuleRegistryBuilder.build();
  ...
  return nativeModuleRegistry;
}

private void processPackage(
  ReactPackage reactPackage, NativeModuleRegistryBuilder nativeModuleRegistryBuilder) {
  ...
  // 处理的逻辑放到这里
  nativeModuleRegistryBuilder.processPackage(reactPackage);
	...
}

// 具体的处理
public void processPackage(ReactPackage reactPackage) {
	...
  for (ModuleHolder moduleHolder : moduleHolders) {
    String name = moduleHolder.getName();
    if (mModules.containsKey(name)) {
      ModuleHolder existingNativeModule = mModules.get(name);
      ...
      mModules.remove(existingNativeModule);
    }
    mModules.put(name, moduleHolder);
  }
}

这个处理就是一个比较简单的循环。遍历ReactPackage,将处理的结果保存到mModules中。在build()创建NativeModuleRegistry的时候会将mModules以参数的形式传入。在内部又将数据以getJavaModules()方法和getCxxModules()方法的形式暴露给外面进行调用。

/* package */ Collection<JavaModuleWrapper> getJavaModules(JSInstance jsInstance) {
  ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>();
  for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) {
    if (!entry.getValue().isCxxModule()) {
      javaModules.add(new JavaModuleWrapper(jsInstance, entry.getValue()));
    }
  }
  return javaModules;
}

/* package */ Collection<ModuleHolder> getCxxModules() {
    ArrayList<ModuleHolder> cxxModules = new ArrayList<>();
    for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) {
      if (entry.getValue().isCxxModule()) {
        cxxModules.add(entry.getValue());
      }
    }
    return cxxModules;
  }

这两个方法会在CatalystInstanceImpl被创建的时候调用。通过这两个方法来启动initializeBridge()。这个方法是一个native方法,可以在对应的cpp文件中可以找到代码。

void CatalystInstanceImpl::initializeBridge(
    jni::alias_ref<ReactCallback::javaobject> callback,
    // This executor is actually a factory holder.
    JavaScriptExecutorHolder *jseh,
    jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue,
    jni::alias_ref<JavaMessageQueueThread::javaobject> nativeModulesQueue,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject>
        javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject>
        cxxModules) {
  moduleMessageQueue_ =
      std::make_shared<JMessageQueueThread>(nativeModulesQueue);

  moduleRegistry_ = std::make_shared<ModuleRegistry>(buildNativeModuleList(
      std::weak_ptr<Instance>(instance_),
      javaModules,
      cxxModules,
      moduleMessageQueue_));

  instance_->initializeBridge(
      std::make_unique<JInstanceCallback>(callback, moduleMessageQueue_),
      jseh->getExecutorFactory(),
      std::make_unique<JMessageQueueThread>(jsQueue),
      moduleRegistry_);
}

//----------------------------------------------------------------
// 在创建的过程中会将module进行转化为对应的NativeModule对象进行保存
std::vector<std::unique_ptr<NativeModule>> buildNativeModuleList(
    std::weak_ptr<Instance> winstance,
    jni::alias_ref<jni::JCollection<JavaModuleWrapper::javaobject>::javaobject>
        javaModules,
    jni::alias_ref<jni::JCollection<ModuleHolder::javaobject>::javaobject>
        cxxModules,
    std::shared_ptr<MessageQueueThread> moduleMessageQueue) {
  std::vector<std::unique_ptr<NativeModule>> modules;
  if (javaModules) {
    for (const auto &jm : *javaModules) {
      modules.emplace_back(std::make_unique<JavaNativeModule>(
          winstance, jm, moduleMessageQueue));
    }
  }
  if (cxxModules) {
    for (const auto &cm : *cxxModules) {
      std::string moduleName = cm->getName();
      modules.emplace_back(std::make_unique<CxxNativeModule>(
          winstance,
          moduleName,
          cm->getProvider(moduleName),
          moduleMessageQueue));
    }
  }
  return modules;
}

在初始化Bridge过程中会创建一个ModuleRegistry对象,在这里保存所有注册的package信息和那些在Module中注册的常量信息。到这里注册的Module就算是被存储在了C++侧。

重点注意这里有一个非常关键的参数JavaScriptExecutorHolder *jseh,通过注释可以看出来这个是JavaScriptExecutorFactory的一个Holder对象,如果我们在创建ReactNativeHost的时候没有重写getJavaScriptExecutorFactory()方法,默认使用的是JSCExecutorFactory

接着跟踪instance_->initializeBridge()

void Instance::initializeBridge(
    std::unique_ptr<InstanceCallback> callback,
    std::shared_ptr<JSExecutorFactory> jsef,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::shared_ptr<ModuleRegistry> moduleRegistry) {
  callback_ = std::move(callback);
  moduleRegistry_ = std::move(moduleRegistry);
  jsQueue->runOnQueueSync([this, &jsef, jsQueue]() mutable {
    nativeToJsBridge_ = std::make_shared<NativeToJsBridge>(
        jsef.get(), moduleRegistry_, jsQueue, callback_);

    nativeToJsBridge_->initializeRuntime();

    jsCallInvoker_->setNativeToJsBridgeAndFlushCalls(nativeToJsBridge_);

    std::lock_guard<std::mutex> lock(m_syncMutex);
    m_syncReady = true;
    m_syncCV.notify_all();
  });

  CHECK(nativeToJsBridge_);
}

// ---------------------------------------------------------------------
void Instance::JSCallInvoker::setNativeToJsBridgeAndFlushCalls(
    std::weak_ptr<NativeToJsBridge> nativeToJsBridge) {
  std::lock_guard<std::mutex> guard(m_mutex);

  m_shouldBuffer = false;
  m_nativeToJsBridge = nativeToJsBridge;
  while (m_workBuffer.size() > 0) {
    scheduleAsync(std::move(m_workBuffer.front()));
    m_workBuffer.pop_front();
  }
}

这里先是创建了一个NativeToJsBridge对象,并且调用了对应的initializeRuntime()方法进行初始化。接着对m_workBuffer里面的任务逐一进行一步执行。

NativeToJsBridge::NativeToJsBridge(
    JSExecutorFactory *jsExecutorFactory,
    std::shared_ptr<ModuleRegistry> registry,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::shared_ptr<InstanceCallback> callback)
    : m_destroyed(std::make_shared<bool>(false)),
      m_delegate(std::make_shared<JsToNativeBridge>(registry, callback)),
      m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)),
      m_executorMessageQueueThread(std::move(jsQueue)),
      m_inspectable(m_executor->isInspectable()) {}

// ------------------------------------------------------------------------
// executor->initializeRuntime();
void NativeToJsBridge::initializeRuntime() {
  runOnExecutorQueue(
      [](JSExecutor *executor) mutable { executor->initializeRuntime(); });
}

// ------------------------------------------------------------------------
  JsToNativeBridge(
      std::shared_ptr<ModuleRegistry> registry,
      std::shared_ptr<InstanceCallback> callback)
      : m_registry(registry), m_callback(callback) {}

在创建NativeToJsBridge对象的时候,会使用ModuleRegistry对象去创建JsToNativeBridge对象,将之前保存的Module信息委托给JsToNativeBridge去管理,其中JsToNativeBridgeExecutorDelegate得实现类。接着调用jsExecutorFactory->createJSExecutor(m_delegate, jsQueue)方法来创建Executor。通过上面分析,就是调用JSCExecutorFactory->createJSExecutor()方法。

onLoad.cpp找到对应的C++代码。

class JSCExecutorFactory : public JSExecutorFactory {
 public:
  std::unique_ptr<JSExecutor> createJSExecutor(
      std::shared_ptr<ExecutorDelegate> delegate,
      std::shared_ptr<MessageQueueThread> jsQueue) override {
    auto installBindings = [](jsi::Runtime &runtime) {
      react::Logger androidLogger =
          static_cast<void (*)(const std::string &, unsigned int)>(
              &reactAndroidLoggingHook);
      react::bindNativeLogger(runtime, androidLogger);

      react::PerformanceNow androidNativePerformanceNow =
          static_cast<double (*)()>(&reactAndroidNativePerformanceNowHook);
      react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
    };
    return std::make_unique<JSIExecutor>(
        jsc::makeJSCRuntime(),
        delegate,
        JSIExecutor::defaultTimeoutInvoker,
        installBindings);
  }
};

最终会返回一个JSIExecutor对象。里面的initializeRuntime()方法会通过setProperty()方法创建全局变量。将数据暴露给JS端进行调用。其中有个重要的字段nativeModuleProxy

JSIExecutor::JSIExecutor(
    std::shared_ptr<jsi::Runtime> runtime,
    std::shared_ptr<ExecutorDelegate> delegate,
    const JSIScopedTimeoutInvoker &scopedTimeoutInvoker,
    RuntimeInstaller runtimeInstaller)
    : runtime_(runtime),
      delegate_(delegate),
      nativeModules_(std::make_shared<JSINativeModules>(
          delegate ? delegate->getModuleRegistry() : nullptr)),
      moduleRegistry_(delegate ? delegate->getModuleRegistry() : nullptr),
      scopedTimeoutInvoker_(scopedTimeoutInvoker),
      runtimeInstaller_(runtimeInstaller) {
  runtime_->global().setProperty(
      *runtime, "__jsiExecutorDescription", runtime->description());
}

// ---------------------------initializeRuntime()----------------------------
runtime_->global().setProperty(
      *runtime_,
      "nativeModuleProxy",
      Object::createFromHostObject(
          *runtime_, std::make_shared<NativeModuleProxy>(nativeModules_)));

结合构造器进行分析,nativeModuleProxy字段保存的其实就是之前保存Module信息的ModuleRegistry类的信息。JS侧通过这个字段就可以访问到注册的Native Module。

JS中使用Native Module

依照官方的示例,可以通过NativeModules.ToastExample来访问在Native注册的ToastExample模块。那么NativeModules是怎么通过访问到Native的模块信息的,是因为内部有个NativeModules的变量保存了之前注册的所有Module信息内容。

let NativeModules: {[moduleName: string]: Object, ...} = {};
if (global.nativeModuleProxy) {
  NativeModules = global.nativeModuleProxy;
} else if (!global.nativeExtensions) {
  ...
}

可以非常直观的看出来,这里将变量nativeModuleProxy赋值给了NativeModules,而nativeModuleProxy中保存着所有Module相关的信息内容。这样在JS侧就可以直接调用Native注册的Module了。

小结

这是记录自己学习RN的成果,并不是专业的分析。如果有不对的地方,欢迎指正。