2022-ReactNative最新JSI教程

1,268 阅读4分钟

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…