本文由 简悦SimpRead 转码,原文地址 blog.notesnook.com
React Native JSI(Javascript Interface)是帮助Javascri...... 之间进行通信的新层。
React Native JSI(Javascript Interface)是帮助Javascript和Native平台之间更容易、更快速沟通的新层。它是React Native与Fabric UI层和Turbo模块重新架构的核心元素。
JSI有什么不同?
JSI消除了在Native(Java/ObjC)和Javascript代码之间建立 "桥梁 "的需要。它还消除了将所有信息序列化/反序列化为JSON的要求,以便在两个世界之间进行通信。JSI通过将Javascript和本地世界紧密联系在一起,为新的可能性打开了大门。基于我的理解,我将帮助你了解更多关于JSI接口的知识。
- 1.Javascript接口,它允许我们在Javascript运行时注册方法。这些方法可以通过Javascript世界中的 "global "对象获得。
- 这些方法可以完全用C++编写,也可以是与iOS上的Objective C代码和Android上的Java代码沟通的方式。
- 任何目前使用传统 "桥梁 "在Javascript和本地世界之间进行通信的本地模块,都可以通过用C++编写一个简单的层来转换为JSI模块。
- 4.在iOS上编写这个层很简单,因为C++可以直接在Objective C中运行,因此所有的iOS框架和代码都可以直接使用。
- 5.在安卓系统上,我们必须通过JNI来完成这个任务。
- 6.这些方法可以是完全同步的,这意味着使用 "async/await "并不是强制性的。
现在我们要创建一个简单的JSI模块,这将帮助我们更好地理解一切。
设置我们的JSI模块
在你想要创建库的目录下打开终端,并运行以下程序。
npx create-react-native-library react-native-simple-jsi
它将会问你一些问题。
重要的部分是在它问到 "你想使用哪种语言 "时选择 "C++ for iOS and Android"。
这将为我们设置一个使用C++代码的基本模块。但是请注意,这不是一个JSI模块。我们需要在Android和iOS上改变一些部分的代码,使其成为一个JSI模块。
导航到刚刚创建的react-native-simple-jsi文件夹,删除example文件夹,然后在其位置创建一个新的例子。
npx react-native init example。
它也会解决所有其他的依赖性。
在Android上配置
让我们为Android配置我们的库。
安卓的前提条件:。
- 安装NDK。
- 安装CMake。
你可以从Android Studio的SDK管理器中安装这两样东西。
CMakeLists.txt.
cmake_minimum_required(VERSION 3.9.0)
add_library(cpp
SHARED
../cpp/example.cpp
./cpp-adapter.cpp
)
include_directories(
../../react-native/React
../../react-native/React/Base
../../react-native/ReactCommon/jsi
../cpp
)
set_target_properties(
cpp PROPERTIES
CXX_STANDARD 17
CXX_EXTENSIONS OFF
POSITION_INDEPENDENT_CODE ON
)
target_link_libraries(
cpp
android
)
好的,让我们把它变成可消耗的。我们正在连接我们的jsi模块所需的所有不同的库和文件。我们告诉CMake(C++的编译器)如何编译我们的代码,以及在哪些目录中寻找依赖关系。
cmake_minimum_required。编译我们的库所需的CMake的最小版本。
add_library: 我们要告诉编译器,要添加哪些库。
cpp是我们库的名字。SHARED库是动态链接的,在运行时加载。- 我们包括不同的文件,这些文件是我们的代码运行所需要的。
example.cpp是我们的c++代码所在的地方。
include_directories。这里我们告诉编译器去搜索包含文件。
记得在这里把cpp改成你想要的库名。
build.gradle: 指定CMake的最低版本。
指定编译c++代码时要使用的CMake的最小版本。
externalNativeBuild {
cmake {
path "./CMakeLists.txt"
version "3.8.0+"
}
}
第三步:安装JSI绑定物
在example文件夹内运行yarn add ../,将我们的库添加到example项目中。
在Android Studio中打开example/android文件夹,等待gradle完成构建你的项目。
如果一切按计划进行,你现在应该在Android的侧边栏中看到这个信息
SimpleJsiModule.java。
从侧边栏导航到react-native-simple-jsi/android/java/com.reactnativesimplejsi/SimpleJsiModule.java,用以下代码替换它。
package com.reactnativesimplejsi;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = SimpleJsiModule.NAME)
public class SimpleJsiModule extends ReactContextBaseJavaModule {
public static final String NAME = "SimpleJsi";
static {
try {
// Used to load the 'native-lib' library on application startup.
System.loadLibrary("cpp");
} catch (Exception ignored) {
}
}
public SimpleJsiModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
@NonNull
public String getName() {
return NAME;
}
private native void nativeInstall(long jsi);
public void installLib(JavaScriptContextHolder reactContext) {
if (reactContext.get() != 0) {
this.nativeInstall(
reactContext.get()
);
} else {
Log.e("SimpleJsiModule", "JSI Runtime is not available in debug mode");
}
}
}
如你所见,这里没有@ReactMethod等。在这个类中,有两件事正在发生。
- 我们正在使用
System.loadLibrary加载我们的c++库。 - 我们有一个
installLib函数,它正在寻找javascript运行时的内存参考。get函数基本上返回一个long值。这个值被传递给JNI,在那里我们将安装我们的绑定。
但是我们有一个错误,nativeInstall函数在JNI中不存在。
只要点击创建JNI函数的nativeInstall,当你把光标移到该方法上时,工具提示就会显示出来。
现在如果你打开cpp-adapter.cpp文件。你会看到添加了一个Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall函数。
SimpleJsiModulePackage.java。
这个文件并不存在。你必须创建这个java类。
创建一个新的java类并命名为SimpleJsiModulePackage。
用以下代码替换。
package com.reactnativesimplejsi;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import java.util.Collections;
import java.util.List;
public class SimpleJsiModulePackage implements JSIModulePackage {
@Override
public List<JSIModuleSpec> getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {
reactApplicationContext.getNativeModule(SimpleJsiModule.class).installLib(jsContext);
return Collections.emptyList();
}
}
在这个类中,我们覆盖了getJSIModules方法并安装了我们的jsi绑定。
在这一点上,我们的模块已经注册并运行。所以我们从react context获取模块,然后调用installLib函数来安装我们的库。
虽然我们可以在本地模块加载时直接这样做,但这并不安全,因为有可能在本地模块准备好时,运行时还没有加载。这个包给了我们更多的控制权,确保当我们调用`installLib'时,运行时是可用的。
为了调用这个方法并安装库,我们必须修改我们应用程序的MainApplication.java。
....
import com.facebook.react.bridge.JSIModulePackage;
import com.reactnativesimplejsi.SimpleJsiModulePackage;
public class MainApplication extends Application implements ReactApplication {
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 SimpleJsiExample:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected JSIModulePackage getJSIModulePackage() {
return new SimpleJsiModulePackage();
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
.....
- 我们正在导入
JSIModulePackage。 - 我们将
SimpleJsiModulePackage注册为JSI模块,这样当JS Runtime加载时,我们的jsi绑定也被安装。在ReactNativeHost的实例中,我们重写getJSIModulePackage方法并返回SimpleJsiModulePackage的新实例。
cpp-adapter.cpp.
这是我们的Java本地接口(JNI)适配器,它允许在java和本地c++代码之间进行双向通信。我们可以从java调用c++代码,从c++调用java代码。
下面是我们的适配器的样子。
#include <jni.h>
#include "example.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) {
// TODO: implement nativeInstall()
}
假设example包括我们的install函数,我们现在来添加JSI绑定,我将在后面解释。
#include <jni.h>
#include "example.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) {
auto runtime = reinterpret_cast<facebook::jsi::Runtime *>(jsi);
if (runtime) {
example::install(*runtime);
}
}
我们从我们的nativeInstall函数中调用example::install,该函数是由java代码调用的。
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi)
JNIEnv: 一个JNI接口指针jobject: 调用该函数的java类。- 我们的运行时内存引用的
long值。
我们用auto runtime = reinterpret_cast<jsi::Runtime *>(jsi);重新解释运行时类,然后调用install(*runtime);来安装我们的绑定。
在iOS上配置
在iOS上的配置比android容易,包括几个简单的步骤。
在example/ios中运行pod安装,在xcode中打开example.xcworkspace。
SimpleJsi.mm
导航到Pods > Development Pods > react-native-simple-jsi > ios并打开SimpleJsi.mm。
用以下代码替换它。
#import "SimpleJsi.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>
#import "example.h"
@implementation SimpleJsi
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup {
return YES;
}
- (void)setBridge:(RCTBridge *)bridge {
_bridge = bridge;
_setBridgeOnMainQueue = RCTIsMainQueue();
[self installLibrary];
}
- (void)installLibrary {
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
if (cxxBridge.runtime) {
example::install(*(facebook::jsi::Runtime *)cxxBridge.runtime);
}
}
@end
- 我们正在告诉React,我们的模块需要在主队列上进行设置。
- 我们得到一个
bridge的实例,我们将用它来获取运行时间并安装我们的jsi绑定。在这个实例中,我们要检查bridge.runtime是否存在。如果不存在,我们将等待一段时间,然后再次尝试,直到 "bridge.runtime "变得可用。
SimpleJsi.h
#import <React/RCTBridgeModule.h>;
@interface SimpleJsi : NSObject <RCTBridgeModule>;
@property (nonatomic, assign) BOOL setBridgeOnMainQueue;
@end
我们在这里添加一个属性,setBridgeOnMainQueue,它告诉React将桥设置在主队列上。这导致setBridge在我们的模块中与bridge一起被调用。
所以这就是我们如何为android和iOS配置JSI。现在让我们看看在example.cpp中发生了什么,那里有我们的install功能。
#include "example.h";
#include <jsi/jsi.h>;
using namespace facebook::jsi;
using namespace std;
namespace example {
void install(Runtime & jsiRuntime) {
auto helloWorld = Function::createFromHostFunction(jsiRuntime, PropNameID::forAscii(jsiRuntime, "helloWorld"), 0, [](Runtime & runtime,
const Value & thisValue,
const Value * arguments, size_t count) -> Value {
string helloworld = "helloworld";
return Value(runtime, String::createFromUtf8(runtime, helloworld));
});
jsiRuntime.global().setProperty(jsiRuntime, "helloWorld", move(helloWorld));
}
}
好了,让我们来做这个可消耗的东西。
- 在顶部,你看到我们已经包含了
jsi包含文件。 using namespace facebook::jsi;等有助于我们不重复写 "facebook::jsi"。install函数需要一个参数,那就是我们的JS运行时间。在这个函数中,我们注册了一个名为 "helloWorld "的方法,当我们从javascript代码中调用它时,将返回一个 "hello world "字符串。Function::createFromHostFunction是一个创建函数的方法,当被调用时,将调用C++代码。jsiRuntime.global().setProperty是我们将函数与javascript运行时全局对象绑定的地方。
Function::createFromHostFunction(Runtime, PropNameID, paramCount, function)
Runtime: 代表一个JS运行时,我们的javascript代码正在那里运行。PropNameID: 一个用于查找我们的函数的标识符。它是一个简单的字符串。paramCount: 这个函数将有的参数数量。在我们的例子中,它是0。function: 当我们从javascript中调用global.helloWorld()时,将调用的函数。
我们的 "函数 "也有4个参数。
- 运行时间"。代表一个JS运行时间,我们的javascript代码正在那里运行。
Value &thisValue: 它是对Value类实例的引用,用于在javascript代码中传递JS值。Value *arguments: 这个函数的参数来自Javascript。size_t count: 参数的总数。
在这个函数中,我们正在创建一个简单的字符串hello world。
然后我们将返回Value。String::createFromUtf8函数帮助我们将c++字符串(std::string)转换成Javascript字符串(jsi::String)值。
在Javascript中调用我们的函数
现在我们可以在javascript代码中调用我们的函数helloWorld。这应该会在屏幕中央显示helloworld。
export default function App() {
const [result, setResult] = React.useState<number | undefined>();
React.useEffect(() => {
setResult(global.helloWorld())
}, []);
return (
<View style={styles.container}>
<Text>Result: {result}</Text>
</View>
);
}
从这里开始,你可以做的事情有无限可能。
调用有多个参数的函数
在example.cpp中添加这个新函数。这是一个简单的函数,做两个数字的乘法运算
auto multiply = Function::createFromHostFunction(
jsiRuntime, PropNameID::forAscii(jsiRuntime, "multiply"), 2,
[](Runtime &runtime, const Value &thisValue, const Value *arguments,
size_t count) -> Value {
int x = arguments[0].getNumber();
int y = arguments[1].getNumber();
return Value(x * y);
});
jsiRuntime.global().setProperty(jsiRuntime, "multiply", move(multiply));
注意,现在我们将paramCount设置为2,因为我们有两个参数。
在Javascript中,我们可以调用
global.multiply(2, 4); // 8
从C++调用一个JS回调
在这里,我们正在做同样的乘法,但不返回其值。相反,我们正在调用一个JS函数。
auto multiplyWithCallback = Function::createFromHostFunction(
jsiRuntime, PropNameID::forAscii(jsiRuntime, "multiplyWithCallback"), 3,
[](Runtime &runtime, const Value &thisValue, const Value *arguments,
size_t count) -> Value {
int x = arguments[0].getNumber();
int y = arguments[1].getNumber();
arguments[2].getObject(runtime).getFunction(runtime).call(runtime, x * y);
return Value();
});
jsiRuntime.global().setProperty(jsiRuntime, "multiplyWithCallback",
move(multiplyWithCallback));
While in javascript, we can call the function like this:
global.multiplyWithCallback(2,4,(a) => {
console.log(a); // 8
})
Value
一个Value可以是undefined, null, boolean, number, symbol, string, 或 object。
结论
JSI是React Native的一个游戏改变者,它正在改变React Native的工作方式。今天我们已经学会了如何构建一个简单的JSI模块。在下一篇博客中,我将解释如何通过一些简单的步骤将任何Native模块转换成React Native JSI模块。
你可以在Github上找到该库的完整代码+例子](github.com/ammarahm-ed… )。React Native JSI正被积极用于Notesnook Android和iOS应用程序的加密和存储。你可以从这里获得这些应用程序。