ReactNative 🤝 JSI
JSI是ReactNative的新通讯架构, C++ <-> JavaScript ; 省略了旧Bridge通讯模块中的JSON序列和反序列化等步骤,让高性能同步调用成为了现实;
必备基础 🍲
- Gradle
- CMake
- NDK
必备语言 🍜
- JavaScript
- Java
- Objective-C
- C++
从零创建JSI库
文末有完整项目代码仓库地址、可直接拉下来运行。有任何问题可在评论区留言讨论。如果没有C++,Gradle和CMake基础,写起来会比较费劲。此时要么考虑都学、要么等ReactNative官方放出 TurboModuel的正式版文档。 TurboModule本质上是用到jsi中的一个核心类 HostObject 。
Android及JS部分
第一步
终端创建Library
> npx create-react-native-library react-native-ijsi
第二步
删除默认仓库下的 example 项目并初始化最新ReactNative项目
> cd react-native-ijsi && rm -rf example && npx react-native init example
第三步
链接库、编辑example文件夹下的package.json ,添加下面这行依赖并yarn install
"react-native-ijsi": "link:../"
> yarn install
第四步
用Android Studio 打开项目 example/android ; 此时侧边栏会有两个模块;
- app
- react-native-ijsi
点击上方项目结构、从 Android 切换至 Project Files 此时应该会变成三个模块
- react-native-ijsi/android
- react-native-ijsi/app
- react-native-ijsi/example/android
点开第一个模块,并打开build.gradle文件进行编辑, 替换为以下内容
buildscript {
if (project == rootProject) {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
}
}
}
apply plugin: 'com.android.library'
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
def nodemodulesDir = "${rootProject.projectDir}/../node_modules"
def reactnativeDir = "${nodemodulesDir}/react-native"
android {
compileSdkVersion safeExtGet('compileSdkVersion', 29)
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 29)
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
arguments "-DNODEMODULES_DIR=${nodemodulesDir}"
cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all"
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
tasks.register('prepareReactNativeAAR') {
def aarfile = null
fileTree("${reactnativeDir}/android").matching {
include "**/**/*.aar"
}.forEach {
aarfile = it
}
if (aarfile != null) {
copy {
from(zipTree(aarfile)) {
include "jni/**"
}
into "${buildDir}/react-native-libs"
}
} else {
throw new FileNotFoundException("react native aar file is not found")
}
}
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
google()
mavenCentral()
jcenter()
}
dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
}
tasks.whenTaskAdded { task ->
if (task.name.contains('generateJsonModelDebug')) {
task.dependsOn(prepareReactNativeAAR)
}
if (task.name.contains('generateJsonModelRelease')) {
task.dependsOn(prepareReactNativeAAR)
}
}
这里对build.gradle相较默认内容做出的变动进行解释说明;有gradle基础能看懂的、读完下面引用中的更改目的,就可以直接跳过gradle代码说明;
从ReactNative0.66 起、ReactNative的核心 aar 包中就提供了编译好的jsi的动态库。在低于0.66的版本中一般是直接在cmake中引入jsi的源码。这里有动态库了就不需要再引入源码了。而gradle这里要做的事情就是复制ReactNative的aar包并解压、拿到其中的jsi动态库 libjsi.so 等、复制到 react-native-ijsi/android/build 目录下,在CMake中find_library时使用。
在gradle中定义了两个变量 nodemodulesDir 和 reactnativeDir 。其中指向了node_modules路径的变量作为参数传递给了cmake
arguments "-DNODEMODULES_DIR=${nodemodulesDir}"
而reactnativeDir仅在gradle中使用,在gradle的最后使用了tasks的hook闭包 whenTaskAdded、找到generateJsonModelDebug/Release这个任务,并添加了依赖任务prepareReactNativeAAR。 这里 task.dependsOn() 的作用就是在执行当前任务前先执行依赖中指定的任务。
tasks.register('prepareReactNativeAAR') {
def aarfile = null
fileTree("${reactnativeDir}/android").matching {
include "**/**/*.aar"
}.forEach {
aarfile = it
}
if (aarfile != null) {
copy {
from(zipTree(aarfile)) {
include "jni/**"
}
into "${buildDir}/react-native-libs"
}
} else {
throw new FileNotFoundException("react native aar file is not found")
}
}
上面任务中首先是使用fileTree在 node_modules/react-native/android下递归正则匹配查找ReactNative的 aar包 -> react-native-0.67.2.aar 找到后使用copy预置方法和 zipTree 将aar包中的jni文件夹极其中文件拷贝到 build/react-native-libs 下。(这里 buildDir就是build.gradle同级目录下的build文件夹) 至此CMake中需要的 jsi动态库就准备好了,node_modules路径参数也准备好了。
第五步
编辑CMakeLists.txt 直接复制下面内容;
cmake_minimum_required(VERSION 3.10.2)
set(CMAKE_VERBOSE_MAKEFILE ON)
set(CMAKE_CXX_STANDARD 14)
set(PACKAGE_NAME ijsi)
set(BUILD_DIR ./build)
set(RNSO_DIR ${BUILD_DIR}/react-native-libs/jni/${ANDROID_ABI})
add_library(
${PACKAGE_NAME}
SHARED
../cpp/react-native-ijsi.cpp
cpp-adapter.cpp
)
target_include_directories(
${PACKAGE_NAME}
PRIVATE
../cpp
"${NODE_MODULES_DIR}/react-native/React"
"${NODE_MODULES_DIR}/react-native/React/Base"
"${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni"
"${NODEMODULES_DIR}/react-native/ReactCommon"
"${NODEMODULES_DIR}/react-native/ReactCommon/jsi"
)
find_library(
LOG
log
)
find_library(
JSI
jsi
PATHS ${RNSO_DIR}
NO_CMAKE_FIND_ROOT_PATH
)
target_link_libraries(
${PACKAGE_NAME}
${JSI}
${LOG}
)
这里 RNSO_DIR 就是在gralde中准备的aar解压后复制的文件内容所在路径。 find_library 这里搜索了两个库, log 和 jsi ,并在最后链接到了 ijsi库中。ijsi就是我们的动态库名。
第六步
打开cpp文件夹,将example.h 和example.cpp重命名为 react-native-ijsi.h 和 react-native-ijsi.cpp 并替换为下面代码
react-native-ijsi.h
#ifndef REACT_NATIVE_IJSI_H
#define REACT_NATIVE_IJSI_H
#include "jsi/jsi.h"
using namespace facebook::jsi;
using namespace std;
namespace ijsi {
void install(Runtime &jsiRuntime);
}
#endif
react-native-ijsi.cpp
#include "react-native-ijsi.h"
namespace ijsi {
void install(Runtime &jsiRuntime) {
auto greet = Function::createFromHostFunction(
jsiRuntime,
PropNameID::forAscii(jsiRuntime,"greet"),
0,
[](Runtime &runtime,const Value &,const Value *,size_t count) -> Value {
string words = "hello, this is value from jni";
return Value(runtime,String::createFromUtf8(runtime,words));
});
jsiRuntime.global().setProperty(jsiRuntime,"greet",move(greet));
}
}
接着替换 cpp-adapter.cpp 中内容为如下
#include <jni.h>
#include <android/log.h>
#include "react-native-ijsi.h"
using namespace std;
void nativeInstall(JNIEnv *jsienv, jobject module,jlong jenv) {
auto rt = reinterpret_cast<Runtime *>(jenv);
ijsi::install(*rt);
}
/**
* public native void nativeInstall(long jsi);
*/
JNINativeMethod methodTables[] = {
{"nativeInstall","(J)V",(void *)nativeInstall}
};
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = nullptr;
if(vm->GetEnv((void **) &env,JNI_VERSION_1_6) != JNI_OK){
return -1;
}
jclass jcls;
jcls = env->FindClass("com/reactnativeijsi/IjsiModule");
if(jcls == nullptr) return -1;
if(env->RegisterNatives(jcls,methodTables,sizeof(methodTables)/sizeof(methodTables[0])) < 0){
return -1;
}
env->DeleteLocalRef(jcls);
return JNI_VERSION_1_6;
}
注意、我这里采用了动态注册的方式注册了 IjsiModule.java中的native方法: nativeInstall
public native void nativeInstall(long jsi);
第七步
替换IjsiModule.java内容为如下
package com.reactnativeijsi;
import android.util.Log;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = IjsiModule.NAME)
public class IjsiModule extends ReactContextBaseJavaModule {
public static final String NAME = "Ijsi";
public static final String TAG = "Ijsi";
public IjsiModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
@NonNull
public String getName() {
return NAME;
}
/**
* Native Methods
*/
public native void nativeInstall(long jsi);
@ReactMethod(isBlockingSynchronousMethod = true)
public void init() {
try {
System.loadLibrary("ijsi");
JavaScriptContextHolder contextHolder = getReactApplicationContext().getJavaScriptContextHolder();
long jsi = contextHolder.get();
if(jsi != 0) {
nativeInstall(jsi);
}
} catch (Exception ignored) {}
}
}
第八步
此时在JS代码中调用完 IjsiModule 中的 init方法、global对象中就会出现我们在c++代码中注册的方法 greet
const { Ijsi } = NativeModules;
...
useEffect(() => {
Ijsi.init();
setTimeout(() => {
console.log(global.greet());
}, 1000);
},[])
...
iOS部分
ios部分非常简单,修改 Ijsi.mm文件内容如下;
主要在于一个采用同步调用方法宏定义 RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD; 另一个就是从 RCTCxxBridge中拿到 jsi的 runtime变量;这个变量相当于安卓端从Java中拿到的 getReactApplicationContext().getJavaScriptContextHolder() 值在 c++中经过 facebook::jsi::reinterpret_cast<Runtime *>() 类型转换后拿到的 runtime 指针变量
#import "Ijsi.h"
#import "react-native-ijsi.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <sys/utsname.h>
#import <jsi/jsi.h>
using namespace facebook::jsi;
using namespace std;
@implementation Ijsi
RCT_EXPORT_MODULE(Ijsi)
+ (BOOL) requiresMainQueueSetup {
return YES;
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(init){
RCTBridge* bridge = [RCTBridge currentBridge];
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge;
auto jsiRuntime = (Runtime*) cxxBridge.runtime;
ijsi::install(*(Runtime *) jsiRuntime);
return self;
}
@end
完整项目地址: github.com/yaaliuzhipe…