本文由 简悦 SimpRead转码, 原文地址 ospfranco.com
JSI是JavaScript和C++之间的一个新的翻译层,它是在JS引擎上实现的......。
JSI 是 JavaScript 和 C++ 之间的一个新翻译层,它在 JS 引擎本身上实现,比 React-Native 桥接快得多。这是创建基本 JSI 模块的分步指南。
我创建了一个 SQLite react-native 库,它速度超快,这也是我能写这篇文章的原因,如果你能在上面留下一颗星,那就再好不过了!
如果你对其他 react-native 内容感兴趣,可以查看我的课程 react-native for macOS,如果你买了它,它真的能帮我写出很棒的内容!
创建基础模块
我们将使用 react-native-builder-bob 来搭建一个新的(独立的)模块,builder-bob 已经支持通过旧桥创建 cpp 模块。
从初始化开始:
npx react-native-builder-bob create react-native-sequel
在一堆问题之后,它还会问你想要哪种类型的项目,选择C++ 选项,这不会创建 JSI 模块,但会为 C++ 设置必要的编译。
进入 iOS 文件夹,修改已创建的头文件(.h)和obj-c 文件(.mm)。
注意:无论你在哪里看到 "react-native-sequel",只需替换你的软件包名称。
你应该有这样的文件
头文件,react-native-sequel.h:
#import <React/RCTBridgeModule.h>
#import "react-native-sequel.h"
@interface Sequel : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL setBridgeOnMainQueue;
@end
实现文件,react-native-sequel.mm:
#import "Sequel.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import "react-native-sequel.h"
@implementation Sequel
@synthesize bridge=_bridge;
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
+ (BOOL)requiresMainQueueSetup {
return YES;
}
- (void)setBridge:(RCTBridge *)bridge {
_bridge = bridge;
_setBridgeOnMainQueue = RCTIsMainQueue();
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
if (!cxxBridge.runtime) {
return;
}
installSequel(*(facebook::jsi::Runtime *)cxxBridge.runtime);
}
- (void)invalidate {
cleanUpSequel();
}
@end
无需赘述,只需注意以下几点:
- 我们导入了React/RCTBridge+Private头文件,该文件公开了 jsi 绑定。
- 重要的工作是在setBridge函数中完成的,在这里我们得到了一个对
cxxBridge.runtime的引用,这是一个runtime对象,是在C++代码中进行所有操作以创建JavaScript值所必需的。我们将此运行时传递到一个installSequel(稍后可以重命名)函数中,在此创建 JSI 绑定。
实际绑定
现在你可以进入项目根目录下的 cpp 文件夹,builder-bob 应该已经创建了一些基本的 c++ 供你使用,你可以删除它,然后创建一个头文件(在我的例子中是 react-native-sequel.h)和它的实现(react-native-sequel.cpp)。
我们的头文件
#include <jsi/jsilib.h>
#include <jsi/jsi.h>
void installSequel(facebook::jsi::Runtime& jsiRuntime);
void cleanUpSequel();
我们基本上是在 iOS 文件夹中公开桥接代码中使用的两个函数
对于我们的实现:
// Import our header file to implement the `installSequel` and `cleanUpSequel` functions
#include "react-native-sequel.h"
// sstream contains functions to manipulate strings in C++
#include <sstream>
// The namespace allows for syntactic sugar around the JSI objects. ex. call: jsi::Function instead of facebook::jsi::Function
using namespace facebook;
// We get the runtime from the obj-c code and we create our native functions here
void installSequel(jsi::Runtime& jsiRuntime) {
// jsi::Function::createFromHostFunction will create a JavaScript function based on a "host" (read C++) function
auto multiply = jsi::Function::createFromHostFunction(
jsiRuntime, // JSI runtime instance
jsi::PropNameID::forAscii(jsiRuntime, "multiply"), // Internal function name
1, // Number of arguments in function
// This is a C++ lambda function, the empty [] at the beginning is used to capture pointer/references so that they don't get de-allocated
// Then you get another instance of the runtime to use inside the function, a "this" value from the javascript world, a pointer to the arguments (you can treat it as an array) and finally a count for the number of arguments
// Finally the function needs to return a jsi::Value (read JavaScript value)
[](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value {
// the jsi::Value has a lot of helper methods for you to manipulate the data
if(!arguments[0].isNumber() || !arguments[1].isNumber()) {
jsi::detail::throwJSError(runtime, "Non number arguments passed to sequel");
}
double res = 42;
return jsi::Value(res);
}
);
// Registers the function on the global object
jsiRuntime.global().setProperty(jsiRuntime, "multiply", std::move(multiply));
}
void cleanUpSequel() {
// intentionally left blank
}
截至本文撰写时,JSI 桥接器及其绑定的文档很少,JSI 源代码 是最好的信息来源,但也有一些说明:
jsi::Value
是 javascript 值的封装器,有些值只需调用它就能直接创建,例如布尔值和数字,其他值(如字符串)则比较复杂,它们需要编码(如 UTF8)来解码/编码(这里有一个 示例)。
jsi::detail::throwJSError
向 javascript 代码抛出 JS 错误。请注意,我们创建的函数是同步函数,由于它可能会抛出错误,因此在 JavaScript 端调用该函数时,需要用 try/catch 将其封装起来。
注意 C++ 和内存管理
还有其他一些处理 JSIValues 的便捷方法,如 isNumber 和 isString(请注意,javascript 中的数字总是双倍的)。一旦开始处理对象,事情就变得复杂了,你需要能够移动(std::move)值,这样一旦函数结束,它们就不会从内存中被清除。
Exposing a sensible API
最后,我们可以在 index.ts 文件中为该函数创建绑定(我不确定这些绑定是否也会暴露给封装应用程序,现在我创建了一个哑封装),有了 typescript 还可以在 JS 层面进行类型检查
// /src/index.tsx
declare function multiply(a: number, b: number): number;
export function multiplyA(): number {
return multiply(2, 2);
}
最后,在使用此模块的 react-native 应用程序上:
import * as React from "react";
import { StyleSheet, View, Text } from "react-native";
import { multiplyA } from "react-native-sequel";
export default function App() {
const [result, setResult] = React.useState<number | undefined>();
React.useEffect(() => {
setResult(multiplyA());
}, []);
return (
<View style={styles.container}>
<Text>Result: {result}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
box: {
width: 60,
height: 60,
marginVertical: 20,
},
});
现在我们有了一个可用的 iOS 实现,我们可以看看 android 的情况了。
我们首先要修改 android/CMakeLists.txt 中的文件,该文件告诉 android 编译过程要编译哪些 c++ 文件,你应该有这样的文件:
cmake_minimum_required(VERSION 3.4.1)
set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 11)
include_directories(
../cpp
../../../node_modules/react-native/React
../../../node_modules/react-native/React/Base
../../../node_modules/react-native/ReactCommon/jsi
)
add_library(sequel
SHARED
../../../node_modules/react-native/ReactCommon/jsi/jsi/jsi.cpp
../cpp/sequel.cpp
../cpp/sequel.h
../cpp/react-native-sequel.cpp
../cpp/react-native-sequel.h
cpp-adapter.cpp
)
target_link_libraries(sequel)
基本上,我将 C++ 升级到了 14 版本,include 目录需要包含 .cpp 文件所在的文件夹,然后声明一个包含需要编译的确切文件的 "库",最后链接该库。
然后,我们就可以转到 android/cpp-adapter.cpp 文件,它与我们为 iOS 创建的 react-native-sequel.mm 文件类似,是注册绑定的入口点。修改该文件,使其包含 react-native-sequel.h 头文件(或任何你要调用的软件包),然后你就会得到类似这样的文件:
#include <jni.h>
#include "react-native-quick-sqlite.h"
extern "C" JNIEXPORT void JNICALL
Java_com_reactnativesequel_SequelModule_initialize(JNIEnv *env, jclass clazz, jlong jsiPtr, jstring docPath)
{
jboolean isCopy;
const char *docPathString = (env)->GetStringUTFChars(docPath, &isCopy); // This is might not be necessary, but my library moves files in the android file system, so this is just how to pass an android variable to the C++ size
installSequel(*reinterpret_cast<facebook::jsi::Runtime *>(jsiPtr), docPathString);
}
extern "C" JNIEXPORT void JNICALL
Java_com_reactnativesequel_SequelModule_destruct(JNIEnv *env, jclass clazz)
{
cleanUpSequel();
}
你可以看到我们得到了一个 JSI 桥接器实例,同样我们也得到了两个安装和清理绑定的函数,我不会用细节来烦你,唯一需要注意的细节是函数名将被转换成 Java 包名,以便稍后导入(Java_com_reactnativesequel_SequelModule_initialize -> com.reactnativesequel)
初始化 C++
前面的文件将 C++ 初始化为可调用的 Java 模块,但与 iOS 不同的是,它不会自动注册,因此需要创建一个新文件 android/src/main/java/com/reactnativesequel/SequelModule.java ,并在其中加入以下内容:
package com.reactnativesequel;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
class SequelModule extends ReactContextBaseJavaModule {
static {
System.loadLibrary("sequel");
}
private static native void initialize(long jsiPtr, String docDir);
private static native void destruct();
public SequelModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return "Sequel";
}
@NonNull
@Override
public void initialize() {
super.initialize();
SequelModule.initialize(
this.getReactApplicationContext().getJavaScriptContextHolder().get(),
this.getReactApplicationContext().getFilesDir().getAbsolutePath()
);
}
@Override
public void onCatalystInstanceDestroy() {
SequelModule.destruct();
}
}
builder-bob 可能创建了该文件的 kotlin 版本,你可以删除它(如果你喜欢 kotlin,也可以让它工作)。
Android 作为安卓系统,还需要一个额外的 Package 文件,即 android/src/main/java/com/reactnativesequel/SequelPackage.java:
package com.reactnativesequel;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class SequelPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new SequelModule(reactContext));
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
就这样
完成后,iOS 和 Android 绑定现在都可以正常工作了!
部分代码(仅 iOS)github,如果你想自己探索的话。
JSI 小抄
我为 JSI/C++ 创建了一个新的小抄,如果你对开发自己的 JSI 代码感兴趣,可以在 twitter 上与我联系,我将与你分享该文档(以及我所有的 RN 笔记),只需支付少量费用。
它包含了能让你掌握 80% 问题的 20% C++,以及大量 JSI api 参考资料,因此你不必学习所有源代码就能创建自己的 JSI 模块。
面向 JS 开发人员的 JSI/C++ cheatsheet/guide 已经完成!
第一部分:面向 JS 开发人员的 C++
第二部分:工具(CMake、Android JNI、XCode 等)
第三部分:JSI 参考(如何使用 JSI)给我发送 DM,付费后您就能获得访问权限和我的所有其他 RN 笔记 pic.twitter.com/NYvFllED09
- Oscar (@ospfranco) August 15, 2021