RN新架构 JSI 介绍

RN新架构 JSI 介绍JSI 是 RN 新架构实现 JS 与 Native 通信的基石 Turbomodules 也是基于 JSI 实现的

大家好,欢迎来到IT知识分享网。

        JSI是RN新架构实现JS与Native通信的基石,Turbomodules 也是基于 JSI 实现的。 对于了解RN新架构来说,先搞明白 JSI 是至关重要的,那下面就让我们来聊一聊 JSI。

一、什么是 JSI ?

        JSI 的全称是 JavaScript Interface,即 JS Interface 接口,它是对 JS引擎 与 Native (C++) 之间相互调用的封装,通过 HostObject 接口实现双边映射,官方也称它为映射框架

有了这层封装,在 ReactNative 中有了两方面的提升:

  • 可以自由切换引擎,比如: JavaScriptCore、V8、Hermes等。
  • 在 JS 中调用 C++ 注入到 JS 引擎中的方法,数据载体格式是通过 HostObject 接口规范化后的,摒弃了旧架构中以 JSON 作为数据载体的异步机制,从而使得 JS 与 Native 之间的调用可以实现同步感知。

二、JS引擎注入原理

        由于JSI 的实现是基于 JS 引擎提供的 API 来实现的,为此我们先来了解下 JS 引擎的注入原理。说到向引擎注入方法,相信大家都用过 console.log()、setTimeout()、setInterval() 等方法,像这些方法就是通过 polyfill 的方式注入到 JS 引擎里的,JS引擎内部本身是没有这些方法的 (这些方法的实现,我们会在后面进行讲解)。沿着这个思路,我们可以通过向引擎注入方法和变量的方式,来实现在 JS 中调用注入的 Native(C++) 方法。

        这里我们会以 V8 引擎为例,了解一下 JS 引擎的注入原理。(C++ 嵌入 v8 引擎的教程,感兴趣的同学可以看这里 C++嵌入JS引擎)

1. 向v8引擎中注入方法

向引擎注入额外方法,并实现 JS 调用,基本思想都是一致的,都是要将 JS 所需的内容设法绑定到 JS 执行环境下的 全局对象 global 上。这样在 JS 侧就可以通过 global 来获取注入的内容了,当然我们也可以不直接在 global 中来获取,而是写个 js module,然后重新导出并用 declare 声明,用法类似直接使用 console.log()。

实现步骤如下:

1.1 我们用 C++ 实现一个打印字符串的方法 Print()

void Print(const v8::FunctionCallbackInfo<v8::Value>& args) { for (int i = 0; i < args.Length(); i++) { v8::HandleScope handle_scope(args.GetIsolate()); v8::String::Utf8Value str(args.GetIsolate(), args[i]); const char* cstr = ToCString(str); printf("%s", cstr); } printf("\n"); fflush(stdout); }

1.2 将 Print() 方法加入到 Js global 中

// 根据 v8 实例 isolate 获取 JS 中的 global 对象 v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); // 将 global 关联到 JS 执行上下文 Context 中 v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, global); // 向 global 中 set 一个属性名为 print 的方法。可以理解为: global.print = Print context->Global()->Set(context, v8::String::NewFromUtf8(isolate, "print", v8::NewStringType::kNormal).ToLocalChecked(),v8::FunctionTemplate::New(isolate, Print));

ObjectTemplate 是一个 JS 引擎 提供的 JS 对象模版,通过它我们可以创建一个 JS 对象。通过 context.Global(),可以获取到 Js global 全局对象。

1.3 执行C++ 注入到 JS 的方法 print

// 转换成 v8::Local类型 -- (方便垃圾回收) v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "print('Hello World')"); // 将 Js 代码进行编译 v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked(); // 运行 Js 代码 v8::Local<v8::Value> result = script->Run(context).ToLocalChecked(); // 输出 result 为:Hello World

2. 向v8引擎中注入变量

实现步骤如下:

2.1 注入一个变量到 v8 引擎

// 首先通过 Js 引擎实例 isolate 获取 Js global 对象 v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate); // 将 global 绑定到 JS 执行上下文 Context 中 v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, global); // 创建一个临时对象 temp Local<v8::ObjectTemplate> temp = v8::ObjectTemplate::New(isolate); // temp 上加入 x 属性 temp->Set(isolate, "x", v8::Number::New(isolate, 12)); // temp 上加入 y 属性 temp->Set(isolate, "y",v8::Number::New(isolate, 10)); // 创建 temp 的实例 instance Local<v8::Object> instance = temp->NewInstance(context).ToLocalChecked(); // global 中加入属性 options,其值为 instance,可以理解为: global.options context->Global()->Set(context, String::NewFromUtf8Literal(isolate, "options"),instance).FromJust();

2.2 执行C++ 注入到 JS 的对象

// 类型转换 (方便垃圾回收) v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "options.x"); // 将 Js 代码进行编译 v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked(); // 运行 Js 代码 v8::Local<v8::Value> result = script->Run(context).ToLocalChecked(); // 输出 result 为:12

实现原理分析:v8 引擎中 方法和变量的注入,实际上是借助于 Js 执行上下文 Context 来实现的。首先需要获取这个 Context 上下文 和 JS global ,然后将 需要注入的 方法 和 变量 设置到 global 当中,最后再将 global 与 Context 进行关联,就完成了向引擎注入的操作。这样就可以在 Js 运行环境中,调用我们注入的方法和变量了。

三、JSI、JS、JS Runtime、Native(C++)的关系

RN新架构 JSI 介绍

        Js是运行在 Js Runtime 中的,所谓的方法注入,也就是将所需方法注入到 Js Runtime 中去,JSI则负责具体的注入工作,通过 Js 引擎提供的 API,完成 C++ 方法的注入。上图就是 JS 与 Native(C++) 在 JSI 新架构中实现通信的简易架构。

那么接下来,就让我们继续来了解一下 JSI 是如何实现 JS 与 Native 互调通信的吧。

四、举例说明JSI

        接下来我们通过一个实际的例子,来了解下 JSI 是如何实现 JS 与 Native (C++)通信的,首先我们先来看一下 JS 调用 Native(C++)的过程。

1. JS 调用 Native (C++)

步骤如下:

1.1 编写 .java 文件

package com.terrysahaidak.test.jsi; public class TestJSIInstaller { // native 方法 public native void installBinding(long javaScriptContextHolder); // stringField 会被 JS 调用 private String stringField = "Private field value"; static { //注册 .so 动态库 System.loadLibrary("test-jsi"); } // runTest 会被 JS 调用 public static String runTest() { return "Static field value"; } }

1.2 编写 .h 文件,实现 .java 中的 native 方法,并在此声明一个 SampleModule 对象,该对象就是 TurboModule的实现。SampleModule 需要继承 JSI 中的 I (即 HostObject 接口,它定义了注入操作的细节以及双边数据的交换的逻辑),并实现 install 方法以及 get 方法。

#pragma once #include <jni.h> #include "../../../../../../node_modules/react-native/ReactCommon/jsi/jsi/jsi.h" using namespace facebook; extern "C" { JNIEXPORT void JNICALL Java_com_terrysahaidak_test_jsi_TestJSIInstaller_installBinding(JNIEnv* env, jobject thiz, jlong runtimePtr); } // 声明 SampleModule 继承 HostObject,并实现 install 方法 class SampleModule : public jsi::HostObject { public: static void install( jsi::Runtime &runtime, const std::shared_ptr<SampleModule> sampleModule ); // 每一个 TurboModule -- SampleModule 中的所有方法和属性,都需要通过声明式注册,在 get 中进行声明 jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override; private: JNIEnv jniEnv_; };

1.3 编写 C++ 文件,实现 SampleModule 相关逻辑

#include <jsi/jsi.h> #include <jni.h> #include "TestJSIInstaller.h" // 虚拟机实例,用来获取 JNIenv 环境 JavaVM *jvm; // class 类实例 static jobject globalObjectRef; // class 类对象 static jclass globalClassRef; // native 方法 installBinding 的具体实现 extern "C" JNIEXPORT void JNICALL Java_com_terrysahaidak_test_jsi_TestJSIInstaller_installBinding(JNIEnv *env, jobject thiz, jlong runtimePtr){ // runtimePtr 为 long 类型的值,这里强转成 Runtime类型,也就是 代表的 JS 引擎 auto &runtime = *(jsi::Runtime *)runtimePtr; // 通过智能指针 实例化 SampleModule auto testBinding = std::make_shared<SampleModule>(); // 调用 SampleModule 的 install 方法, SampleModule::install(runtime, testBinding); // 获取并存储虚拟机实例 并存储到 &jvm env->GetJavaVM(&jvm); // 创建一个全局对象的实例引用 globalObjectRef = env->NewGlobalRef(thiz); //通过 class 类路径,创建一个 全局类对象引用 auto clazz = env->FindClass("com/terrysahaidak/test/jsi/TestJSIInstaller"); globalClassRef = (jclass)env->NewGlobalRef(clazz); } // install 方法的具体实现 void SampleModule::install(jsi::Runtime &runtime, const std::shared_ptr<SampleModule> sampleModule){ // 定义 TurboModule 名称,也就是 JS 侧调用时使用的名称。 auto testModuleName = "NativeSampleModule"; // 创建一个 HostObject 实例,即 SampleModule 实例 auto object = jsi::Object::createFromHostObject(runtime, sampleModule); // 通过 runtime 中的 global() 方法获取到 JS 世界的 global 对象, // runtime 是 JS 引擎的实例,通过 runtime.global() 获取到 JS 世界的 global 对象, // 进而调用 setProperty() 将 "NativeSampleModule" 注入到 global 中, // 从而完成 "NativeSampleModule" 的导出。 runtime.global().setProperty(runtime, testModuleName, std::move(object)); } // TurboModule 的 get 方法,当 JS 侧开始使用 "." 来调用某个方法时,会执行到这里。 jsi::Value SampleModule::get(jsi::Runtime &runtime,const jsi::PropNameID &name){ auto methodName = name.utf8(runtime); // 获取 需要调用的成员名称,并进行判断 if (methodName == "getStaticField"){ // 动态创建 HostFunction 对象 return jsi::Function::createFromHostFunction( runtime, name, 0, []( jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value { // 这里通过 反射 完成对 Java 侧 方法的调用 auto runTest = env->GetStaticMethodID(globalClassRef, "runTest", "()Ljava/lang/String;"); auto str = (jstring)env->CallStaticObjectMethod(globalClassRef, runTest); const char *cStr = env->GetStringUTFChars(str, nullptr); return jsi::String::createFromAscii(runtime, cStr); }); } if (methodName == "getStringPrivateField"){ return jsi::Function::createFromHostFunction( runtime, name, 0, []( jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value { auto valId = env->GetFieldID(globalClassRef, "stringField", "Ljava/lang/String;"); auto str = (jstring)env->GetObjectField(globalObjectRef, valId); const char *cStr = env->GetStringUTFChars(str, nullptr); return jsi::String::createFromAscii(runtime, cStr); }); } return jsi::Value::undefined(); }

1.4 在 JS 中调用注入的方法

<Text style={styles.sectionTitle}> { global.NativeSampleModule.getStaticField() } </Text> <Text style={styles.sectionTitle}> {/* this is from C++ JSI bindings */} { global.NativeSampleModule.getStringPrivateField() } </Text>

总结分析:TurboModule 需要注册 (注入) 到 JS 引擎才能够被 JS 调用,在执行静态方法 install 方法之后,最终通过 runtime.global() 将其注入到了 JS 引擎当中。JSI HostObject 向 JS 导出的方法并不是预先导出的,而是懒加载及时创建的。从 JSI 进入到 get 函数后,先是通过 methodName 判断之后,动态的创建一个 HostFunction ,作为 get 的返回结果。在 HostFunction 方法中 在通过反射的方式,实现对 Java 方法的调用,这样就完成了 JS 通过 JSI 调用 Java 的通信流程。

那么下面我们再来了解一下 Native (C++) 调用 JS 的通信方式。

2. Native (C++)调用 JS

        Native调用 JS 主要是通过 JSI 中的 Runtime.global().getPropertyAsFunction(jsiRuntime, “jsMethod”).call(jsiRuntime) 方法实现。那么接下来我们就来一起看下整个流程是怎么样的。

实现步骤如下:

2.1 在 JS module 中 增加一个 将被 Native 调用的 JS 方法 jsMethod()

import React from "react"; import type {Node} from "react"; import {Text, View, Button} from "react-native"; const App: () => Node = () => { // 等待被 Native 调用 global.jsMethod = (message) => { alert("hello jsMethod"); }; const press = () => { setResult(global.multiply(2, 2)); }; return ( <View style={ 
  {}}> </View> ); }; export default App;

2.2 Native 调用 JS 全局方法

runtime .global() .getPropertyAsFunction(*runtime, "jsMethod") .call(*runtime, "message内容!");

注意内容: 我们需要通过 JSI 中的 getPropertyAsFunction() 来获取 JS 中的方法,但需要注意,getPropertyAsFunction() 获取的是 global 全局变量下的某个属性或方法,因此,我们在 JS 中声明一个需要被 Native 调用的方法的时候,需要显式的指定它的作用域。

五、JSI 与 JSC(不是JavaScriptCore脚本引擎) 对比

相同点:

首先在底层实现上来说,JSI 与 JSC 都是通过向 JS 引擎中注入方法,来实现的 JS 与 Native 通信,同时 注入的方法也都是挂载到了 JS global 全局对象上面。

不同点:

旧架构中的 JSC 处理的注入对象是JSON 对象与C++ 对象,内部涉及复杂且频繁的类型转换。且在

JSBridge 这种异步传输的设计中存在三个线程之间的通信:UI线程、Layout线程、JS线程,在典型的列表快速滑动时出现空白页的例子中,效率低下得到明显的体现。

而对于 JSI 来讲,弃用了异步的bridge,传输的数据也不再依赖于 JSON 的数据格式,而是将HostObject 接口作为了双边通信的协议,实现了双边同步通信下的高效信息传输。

另外编写 NativeModule 的方式与旧架构中相比发生了改变,除了功能之外的逻辑,需要在一个 C++ 类中来完成。 因此,一个 TurboModule 的实现分为两部分: C++ & Java (OC)。

一句话概括 JSI 提效的本质JSI 实现了通信桥 Bridge 的自定义,并通过 HostObjec 接口协议的方式取代了 旧架构中基于异步 bridge 的JSON数据结构,从而实现了同步通信,并且避免了 JSON 序列化与反序列化的繁琐操作,大大提升了 JS 与 Native 的通信效率。

六、对 RN global 对象的分析

1. RN 中的 global 与 JS 引擎中的 global 的关系

在 JS 引擎中操作的 全局对象 global 是挂在到 JS 执行上下文环境 Context 上的。

该 global 对象 与 RN 里使用的 global 是同一个 global 对象,HouseRN只是扩展了 global 的声明,下面我们通过一个例子来讲解一下:

在RN的开发中,我们调用一个NativeModule是这样调用的:

import { NativeModules } from "react-native"; //调用获取数据 NativeModules.BrokerData.user()

但是通过 NativeModules.js 的源码可以知道,也可以通过 global 来对NativeModules 进行调用,当然RN官网已经帮我们封装好了调用的方式,这里我们只是为了验证一下 global 这个全局对象的作用域,下面我们首先看一下 NativeModules.js 源码实现:

let NativeModules : {[moduleName: string]: Object} = {}; if (global.nativeModuleProxy) { NativeModules = global.nativeModuleProxy; } else { const bridgeConfig = global.__fbBatchedBridgeConfig; invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules'); (bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => { // Initially this config will only contain the module name when running in JSC. The actual // configuration of the module will be lazily loaded. const info = genModule(config, moduleID); if (!info) { return; } if (info.module) { NativeModules[info.name] = info.module; } // If there's no module config, define a lazy getter else { defineLazyObjectProperty(NativeModules, info.name, { get: () => loadModule(info.name, moduleID) }); } }); }

阅读上面的源码可以知道,理论上我们也可以通过 global 来获取一个 NativeModule ,我们先来看一下,在调试模式下 global 中的内容,可以看到所有已注册的 NativeModule 都在里面。

RN新架构 JSI 介绍


RN新架构 JSI 介绍

那么我们可以通过 global 这样来获取一个已注册的 NativeModule:

// 索引 24 代表 BrokerData 的所在位置,索引 1 代表返回的数据体。 // 这种调用方式 与上面的调用方式是等价的 global.__fbBatchedBridgeConfig.remoteModuleConfig[24][1]

回过头来,我们看一下 __fbBatchedBridgeConfig 变量是在哪里被赋值的,来看下 C++ 侧的实现:

void ProxyExecutor::initializeRuntime() { // ....... { SystraceSection t("setGlobalVariable"); setGlobalVariable( "__fbBatchedBridgeConfig", std::make_unique<JSBigStdString>(folly::toJson(config))); } }
void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) { // .... auto valueToInject = Value::fromJSON(m_context, jsStringFromBigString(m_context, *jsonValue)); Object::getGlobalObject(m_context).setProperty(propName.c_str(), valueToInject); }

由代码可知,最终是通过 JS 引擎的

Object : : getGlobalObject(context).setProperty(“propertyName”, value) 方法,将 “__fbBatchedBridgeConfig” 注入到了 global 全局变量当中。 其中 Object : : getGlobalObject(context) 是 JSC 脚本引擎提供的 API (与 V8 引擎提供的类似),用于获取一个 JSGlobalObject (即 JS的 global 全局对象)。

2. RN 工程中的 global、window、globalThis 的关系

首先我们来引用一个官方一点的说法:

RN新架构 JSI 介绍听上去大家有什么感受呢,懂了而又模糊对吧。那么我们通过具体的例子感受一下:

global.window.alert("11111") // 调用成功 global.alert(222) // 调用成功 window.alert(333) // 调用成功 alert(444) // 调用成功 globalThis.window.alert(555); // 调用成功 globalThis.alert(666) // 调用成功 alert(global.globalThis) // 调用成功 global.setTimeout(() =>{ // 调用成功 console.log("aaaaaaa") }, 1000); window.setTimeout(() =>{ // 调用成功 console.log("bbbbbbb") }, 1000); window.global.alert(999) // 失败 (undefined) global.window.a = 122; alert(a); // 调用成功 alert("hhh " + window.a) // 调用成功 alert("xx " + global.a) // 调用成功

        RN中的 global 是一个顶级全局对象,windowglobalThis 以及常用的 API 包括:alert()、setTimeout、setInterval 等都挂在了 global 这个全局对象下面。而像 alert()、setTimeout、setInterval()、console、JSON 等这些顶层API,也可以无需倒入并直接调用,其原因则是在 RN core 包中通过 declare 做了相应类型的声明。

3. 聊聊 setTimeout 的实现

        在 RN 中,像 setTimeout 这样的顶层函数是如何实现的呢?这里我们先给出答案,再来进行分析。其实 setTimeout 这种顶层函数,并不是由 JS 来实现的,而是由 Native 来实现,并借助 JS 引擎中的 polyfill 方式从外部注入到 JS 执行环境当中。接下来我们简单看一下源码实现:

首先我们通过 RN 源码,找到 setupTimer.js 文件,核心代码如下:


'use strict'; const {polyfillGlobal} = require('../Utilities/PolyfillFunctions'); const {isNativeFunction} = require('../Utilities/FeatureDetection'); //........... if (global.RN$Bridgeless !== true) { const defineLazyTimer = ( name: | $TEMPORARY$string<'cancelAnimationFrame'> | $TEMPORARY$string<'cancelIdleCallback'> | $TEMPORARY$string<'clearInterval'> | $TEMPORARY$string<'clearTimeout'> | $TEMPORARY$string<'requestAnimationFrame'> | $TEMPORARY$string<'requestIdleCallback'> | $TEMPORARY$string<'setInterval'> | $TEMPORARY$string<'setTimeout'>, ) => { polyfillGlobal(name, () => require('./Timers/JSTimers')[name]); }; // 定义 'setTimeout' defineLazyTimer('setTimeout'); defineLazyTimer('clearTimeout'); defineLazyTimer('setInterval'); defineLazyTimer('clearInterval'); defineLazyTimer('requestAnimationFrame'); defineLazyTimer('cancelAnimationFrame'); defineLazyTimer('requestIdleCallback'); defineLazyTimer('cancelIdleCallback'); }

代码中我们可以找到定义 setTimeout 的地方:defineLazyTimer(‘setTimeout’),继而调用了 polyfillGlobal(), 它就是向 global 上注入成员的一个操作,同时我们看到与之对应的具体实现则交给了 JSTimers.js ,我们跟进去看一下代码

/ * JS implementation of timer functions. Must be completely driven by an * external clock signal, all that's stored here is timerID, timer type, and * callback. */ const JSTimers = { / * @param {function} func Callback to be invoked after `duration` ms. * @param {number} duration Number of milliseconds. */ setTimeout: function(func: Function, duration: number, ...args?: any): number { const id = _allocateCallback( () => func.apply(undefined, args), 'setTimeout' ); RCTTiming.createTimer( id, duration || 0, Date.now(), /* recurring */ false ); return id; }, //........... clearTimeout: function(timerID: number) { _freeCallback(timerID); }, //........... }; module.exports = JSTimers;

由源码可知,最终调用的是 RCTTiming 的 createTimer(),而 Timing 则是 Nativemodules 中获取的一个 NativeModule,也就是 Timing 是由 Native 来实现的。因此也就是说 setTimeout 是由Native实现的,并通过 polyfill 的方式注入到 global 全局对象中,至于为什么像 setTimeout 这样挂在 global 下的全局变量为什么可以直接使用,这个其实是 RN 对其进行了 declare 声明:

RN新架构 JSI 介绍 具体是在哪里执行的 global.setTimeout 呢? 看下面的文件:

RN新架构 JSI 介绍

 好了,详细的实现流程我们在这里就不深究了,感兴趣的同学下来可以跟着源码更深入的研究一下。

源码地址: react-native/setUpTimers.js at 8bd3edec88148d0ab1f225dfbbba33 · facebook/react-native

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128375.html

(0)
上一篇 2025-08-31 22:10
下一篇 2025-08-31 22:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信