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
。方法的返回类型必须为void
。React 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转化为ReactModuleInfos
。DebugCorePackage
类似不做阐述。
在添加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
去管理,其中JsToNativeBridge
是ExecutorDelegate
得实现类。接着调用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的成果,并不是专业的分析。如果有不对的地方,欢迎指正。