本文会详细分析React Native 新架构的TurboModules的通信过程,基于JSI的通信方式,除不会涉及Hemers引擎部分,其余代码都会详细分析,但比较简单的,不会很啰嗦,可以说是网上最完整详细的分析文章,代码通过断点截图,可以更方便查看运行的过程
1、React Native 源码分析(一)—— 启动流程
2、React Native 源码分析(二)—— Native Modules桥通信机制
3、React Native 源码分析(三)—— Native View创建流程(桥通信)
4、React Native 源码分析(四)—— TurboModules JSI通信机制
5、React Native 源码分析(五)—— Fabric创建View的过程
官方文档详细介绍了TurboModules的使用方式,本文的源码分析是基于0.72-stable分支,新版React Native的架构如下:
首先来了解一些JSI的最基本实现,蜘蛛建网的第一根丝是怎么来的,掌握了最核心的原理,能更容易理解整体过程
一、JSI 原理与实现
JSI(JavaScript Interface) 它允许开发人员使用C++语言,根据jsi的规则创建一个可以让js端直接调用的函数。JS运行时提供了JSI API,来实现在JS运行时中添加函数
下面看一下,如何通过jsi添加一个函数:
js端创建add函数
// JS
const add = (first, second) => {
return first + second
}
使用jsi在C++层创建add函数
// JSI (C++)
auto add = jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "add"), // add function name
2, // first, second variables (2 variables)
[](
jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments, // function arguments
size_t count
) -> jsi::Value {
double result = arguments[0].asNumber() + arguments[1].asNumber();
return jsi::Value(result);
}
);
之后就可以在js端直接使用了
// JavaScript
const result = add(5, 8)
同理,使用jsi::Function::createFromUtf8(详细介绍)创建一个变量,查看hermes中jsi.h了解更多api
接着,来分析React Native源码的0.72-stable分支,以源码目录packages/rn-tester项目为载体,打包运行到手机上,以此来分析React Native,首次从启动入口看看 TurboModules 的加载过程
二、TurboModules 的加载
在React Native 源码分析(一)—— 启动流程一文中,知道了RN启动的过程,会依次创建 ReactNativeHost -> ReactInstanceManagerBuilder -> ReactInstanceManager -> ReactContext
1、一般在Application中重新ReactNativeHost时,会继承DefaultReactNativeHost(React Native 提供的),它重新了函数getReactPackageTurboModuleManagerDelegateBuilder(),用于创建 DefaultTurboModuleManagerDelegate
2、使用builder.build创建ReactInstanceManger,把getReactPackageTurboModuleManagerDelegateBuilder()返回的值存在ReactInstanceManger的类变量 mTMMDelegateBuilder = tmmDelegateBuilder;
3、在ReactInstanceManger的函数中createReactContext,会使用到mTMMDelegateBuilder来解析,一般是在使用具体某个turboModule时,才会通过jsi创建对应的c++函数在js运行时,当然也可以根据配置提前创建,例如下图断点处,
4、在上图中创建TurboModuleManager 时,构造函数会执行 installJSIBindings()
,它会在c++层通过jsi创建出一个__turboModuleProxy 变量供js使用,后续js端需要使用native的任何turboModule的方法,都是通过__turboModuleProxy 变量来 创建(jsi)和使用 native的代码
在TurboModuleManager中,调用流程是 TurboModuleManager::installJSIBindings() -> TurboModuleBinding::install, 其中参数moduleProvider,是installJSIBindings里的一个lambda表达式,在下一章通信过程会详细分析
三、JS调用Java代码的过程
下面以React Native源码中的Toast为例,来介绍TurboModules 的加载,自动生成Toast jsi相关的C++代码,运行 packages/rn-tester,手机上成功启动app后
3.1、js端调用Toast
使用TurboModules ,需要在js端创建如下这些代码,通过TurboModuleRegistry.getEnforcing
来向外提供方法,
调用到TurboModuleRegistry.js 文件中,可以看到turboModuleProxy就是之前通过jsi添加到js运行时的全局变量__turboModuleProxy(下图红框2)
就会调用到c++中jsi创建的 __turboModuleProxy变量的对应的函数,如下:
接下来,就会去创建或者去缓存中拿 对应的module(c++ 和 java 各自都有缓存)
3.2、c++端创建TurboModule
接上图代码,binding.getModule(rt, moduleName) -> module = moduleProvider_(moduleName); 此处的moduleProvider_ 就是在执行installJSIBindings(文章2.4节)时,创建的内部lambda函数
void TurboModuleManager::installJSIBindings() {
if (!jsCallInvoker_) {
return; // Runtime doesn't exist when attached to Chrome debugger.
}
runtimeExecutor_([this](jsi::Runtime &runtime) {
auto turboModuleProvider =
[turboModuleCache_ = std::weak_ptr<TurboModuleCache>(turboModuleCache_),
jsCallInvoker_ = std::weak_ptr<CallInvoker>(jsCallInvoker_),
nativeCallInvoker_ = std::weak_ptr<CallInvoker>(nativeCallInvoker_),
delegate_ = jni::make_weak(delegate_),
javaPart_ = jni::make_weak(javaPart_)](
const std::string &name) -> std::shared_ptr<TurboModule> {
auto turboModuleCache = turboModuleCache_.lock();
auto jsCallInvoker = jsCallInvoker_.lock();
auto nativeCallInvoker = nativeCallInvoker_.lock();
auto delegate = delegate_.lockLocal();
auto javaPart = javaPart_.lockLocal();
...
const char *moduleName = name.c_str();
...
// c++ 端的,turboModule缓存策略
auto turboModuleLookup = turboModuleCache->find(name);
if (turboModuleLookup != turboModuleCache->end()) {
TurboModulePerfLogger::moduleJSRequireBeginningCacheHit(moduleName);
TurboModulePerfLogger::moduleJSRequireBeginningEnd(moduleName);
return turboModuleLookup->second;
}
...代码实际运行,其实是在这里被省略的代码,进入java端的,但是在java端的处理和下面一样,有没有这段代码,不影响本流程分析
// 从这里进入TurboModuleManager,去调用getJavaModule 创建或从缓存获取java端的ToastModule对象
static auto getJavaModule =
javaPart->getClass()
->getMethod<jni::alias_ref<JTurboModule>(const std::string &)>(
"getJavaModule");
// 3.3节 分析从这里进入的逻辑
auto moduleInstance = getJavaModule(javaPart.get(), name);
if (moduleInstance) {
TurboModulePerfLogger::moduleJSRequireEndingStart(moduleName);
JavaTurboModule::InitParams params = {
.moduleName = name,
.instance = moduleInstance,
.jsInvoker = jsCallInvoker,
.nativeInvoker = nativeCallInvoker};
// 3.4 分析这里进入的逻辑,最终使用codeGen 创建的JavaTurboModule 创建对象 并返回
auto turboModule = delegate->cthis()->getTurboModule(name, params);
// 建立缓存
turboModuleCache->insert({name, turboModule});
...
//返回到 binding.getModule 函数里,也就是赋值给module = moduleProvider_(moduleName);
// 接着会把这个对象添加到js运行时,代码3.5分析
return turboModule;
}
return nullptr;
};
TurboModuleBindingMode bindingMode = static_cast<TurboModuleBindingMode>(
getFeatureFlagValue("turboModuleBindingMode"));
TurboModuleBinding::install(
runtime, bindingMode, std::move(turboModuleProvider));
});
}
3.3、 java端创建TurboModule
先总体来看下 创建TurboModule 的整个调用过程,会调用到java端,去创建ToastModule,java的调用栈如下图:
接着来分析几个主要流程:
TurboModuleManager#getOrCreateModule,其中moduleHolder主要是为了保存module的创建状态、保存实例、以及用该对象作为同步锁,保证多线程安全的单例
经过几次简单的调用,来到 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageTurboModuleManagerDelegate.java # resolveModule
自定义TurboModules和NativeModules时,需要在Application中的ReactNativeHost的getPackages()函数中,返回List<ReactPackage>,继承了ReactPackage的类的getModule函数中可以返回自定义的一个Modules(例如:本例中的class com.facebook.react.shell.MainReactPackage)。
下图中此时断点的循环中,moduleProvider.getModule调用的是MainReactPackage(ReactPackage子类)的getModule函数的
调用MainReactPackage 的getModule
MainReactPackage 的getModule 函数:
public @Nullable NativeModule getModule(String name, ReactApplicationContext context) {
switch (name) {
····
case ToastModule.NAME:
return new ToastModule(context);
···
default:
return null;
}
}
3.4、在C++端注册Toast TurboModule的方法
接着返回到,调用c++层继续处理,此时moduleInstance已经有值了,调用到下图2处的代码,
.instance = moduleInstance(ToastModule的对象), 参数instance就是在c++端持有的java对象,在文章3.7分析的文章,在C++端访问ToastModule的方法,就是通过instance去反射调用到java端
delegate是在什么被赋值的呢?
delegate是在 TurboModuleManager.java 调用initHybrid时传入的参数(DefaultTurboModuleManagerDelegate对象),在c++层,对该java对象的添加了方法getTurboModule,所以上图的红框2处可以调用到
解释完delegate的赋值问题,下面就接着delegate->cthis()->getTurboModule(name, params)
分析,经过 getTurboModule -> javaModuleProvider
rncore_ModuleProvider会调用到 CodeGen 自动生成的代码,在那里会创建一个JavaTurboModule对象,并被ToastModule中的每个方法及对应的处理函数,存储在一个map中,见 3.5分析。
DefaultTurboModuleManagerDelegate::javaModuleProvider 这个变量是在哪里,什么时候被赋值的呢?
-
上图中的代码是文件OnLoad.cpp,通过查找packages/rn-tester/android/app/src/main/jni/CMakeLists.txt 可知,打包出来的是libappmodules.so
-
在packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.java 文件中,调用 DefaultNewArchitectureEntryPoint.load();
-
其中 SoLoader.loadLibrary(dynamicLibraryName) ,dynamicLibraryName的值是"appmodules",
-
加载libappmodules.so,会自动执行JNI_OnLoad的方法,initialize函数中的lambda会被执行,
facebook::react::DefaultTurboModuleManagerDelegate::javaModuleProvider
就被赋值了
3.5 使用CodeGen生成的代码,创建JavaTurboModule
std::shared_ptr<TurboModule> rncore_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams ¶ms) {
...
if (moduleName == "ToastAndroid") {
return std::make_shared<NativeToastAndroidSpecJSI>(params);
}
.....
return nullptr;
}
- 此处创建的 NativeToastAndroidSpecJSI 继承 JavaTurboModule 继承TurboModule 继承HostObject,这个对象会返回到代码3.2流程 并通过jsi创建js运行时的对象(代码如下),这样在js端 使用 ToastAndroid 就是在调用 NativeToastAndroidSpecJSI对象
代码中的bindingMode_是从TurboModuleManager::installJSIBindings() 传入的,该参数的实现是在java端,也很简单,动手跟一下就明白,不再啰嗦
- ToastModules注册完成一半,ToastModules的jsi注册已经完成,但是ToastModules的方法还没有实现jsi,在具体调用某个方法时,才会对那个方法通过jsi在js运行时创建,
此时如果在js代码中调用 ToastAndroid.show('Copied to clipboard!', ToastAndroid.SHORT)
就是调用到 HostObject的get(Runtime&, const PropNameID& name);方法, 因为js端的 ToastAndroid 就是c++端的 NativeToastAndroidSpecJSI对象,后者的间接继承了HostObject
3.6、通过 JSI 创建和调用方法
调用到TurboModule 的get方法,它是重写了HostObject的get,可以看到下图,是对
-
获取methodMap_ 中的 show对应的方法(在上上一张代码图中建立的)
-
在js运行时,创建方法show ,它对应的执行方法就是参数p->second(步骤一获取的),并返回moduleMethod
文章来源:https://www.toymoban.com/news/detail-836104.html
后续就可以在js中调用ToastAndroid.show,就是调用到
接下来就是解析参数,jsi参数转为jni参数,调用对应的java方法。逻辑很简单,就啰嗦了。到这里整个TurboModule的通信过程就分析完了,点个赞再走吧文章来源地址https://www.toymoban.com/news/detail-836104.html
到了这里,关于React Native 源码分析(四)—— TurboModules JSI通信机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!