当前位置: 首页 > news >正文

React native 使用 JSI 库 实现 C++和JS互通

一、可选方案:

1JSI 直接通信

‌原理‌:

通过 JavaScript Interface (JSI) 实现 C++ 对象与 JS 对象的直接内存引用映射,支持同步函数调用与数据共享。JS 可直接持有 C++ 对象的引用,绕过序列化步骤,调用时直接操作内存数据。

‌技术实现‌:

‌Host Object 绑定‌:C++ 继承 jsi::HostObject 暴露方法,JS 通过全局对象直接访问

‌同步执行‌:JS 线程与原生线程直接交互,无异步队列

‌RN版本支持

RN 0.64+(新架构默认启用(React native 0.64+)

原生环境依赖

Hermes引擎必选,需C++支持

典型应用场景

高频同步调用(如实时手势处理)、内存密集型操作(图像/音视频处理)

实现难度

需深入掌握 C++ 内存操作与线程同步,直接操作 jsi::HostObject 易引发崩溃

性能对比

≤1ms (内存直读)、10万+ ops/sec、共享 ArrayBuffer、实时音视频处理

优点:

零序列化开销:JavaScript与C++直接共享内存,同步调用延迟≤1ms,性能碾压异步方案

细粒度控制:支持直接操作原生对象(如jsi::ArrayBuffer),适合高频计算(如物理引擎)

线程灵活性:可绕过UI线程限制,实现多线程并行渲染(如Fabric架构)

缺点

开发门槛高:需精通C++内存管理及线程同步,调试崩溃风险显著增加

维护成本:脱离RN框架约束,需自行处理平台差异(如Android/iOS指针对齐)

兼容性风险:Hermes引擎版本升级可能导致JSI接口变更

2TurboModules

原理‌:

基于 JSI 的模块化封装,通过 Codegen 生成类型安全的 JS 接口,实现按需加载原生模块(C++/Java/OC)。模块首次调用时初始化,减少启动开销

‌技术实现‌:

‌类型安全绑定‌:自动生成 JS 与原生端的接口文件,确保参数类型匹配

‌按需加载‌:原生模块延迟初始化,优化性能

‌RN版本支持

RN 0.68+(需手动启用新架构)

原生环境依赖

Codegen工具链,类型声明文件

典型应用场景

跨平台原生模块开发(如蓝牙、传感器)、需类型安全的商业级模块

实现难度

Codegen 配置复杂,需维护类型声明文件,跨平台适配需处理 JNI/OC 桥接

性能对比

≤5ms (JSI 封装)、5万+ ops/sec、轻量封装对象、跨平台蓝牙模块

优点

类型安全:Codegen自动生成TS/Flow类型定义,减少跨语言调用错误

按需加载:模块延迟初始化使应用启动时间优化30%-50%

官方支持:Facebook主力维护,与Fabric渲染器深度集成

缺点

迁移成本:旧模块需重写Spec文件并适配Codegen工具链

灵活性受限:强依赖RN框架,无法实现底层硬件直连(如USB协议栈)

调试复杂性: 跨语言调用栈难以追踪(需结合LLDB+Chrome DevTools)

3引擎扩展注入

原理‌:

直接向 JavaScript 引擎(如 Hermes/V8)注入 C++ 函数,绕过 React Native 框架层。JS 全局对象可直接调用注入的原生方法

技术实现‌:

‌引擎 API 调用‌:通过 jsi::Runtime 在引擎初始化时注册全局函数

‌独立通信通道‌:适用于非 React Native 绑定的底层功能扩展

RN版本支持

无RN版本限制(依赖引擎版本)

原生环境依赖

需定制JS引擎(如Hermes/V8)

典型应用场景

底层性能优化(如游戏引擎集成)、非RN框架的原生功能扩展(如嵌入式设备控制)

实现难度

需定制 JS 引擎(Hermes/V8),深入理解引擎 ABI,脱离 RN 框架维护成本高

性能对比

≤1ms (引擎级优化)、10万+ ops/sec、零拷贝内存引用、游戏引擎集成

优点

极致性能:过RN框架层,直接调用引擎API(如Hermes/V8),吞吐量达10万+ ops/sec

场景扩展性:支持非RN生态的原生功能接入(如嵌入式设备控制)

内存高效‌:零拷贝数据传输(如共享WebAssembly内存)

缺点

技术垄断:需深度定制JS引擎,绑定特定厂商(如Facebook Hermes团队)

升级风险:引擎ABI变更可能导致注入接口失效

生态孤立‌:无法复用RN社区模块(如React Navigation)

4旧版 Bridge

‌原理‌:

通过异步消息队列和 JSON 序列化实现 JS 与原生通信。JS 调用经 Bridge 序列化为 JSON 传递到原生端,结果通过回调返回

‌技术实现‌:

‌异步队列‌:JS 与原生线程通过 Bridge 线程通信,存在序列化开销25。

‌性能瓶颈‌:大量数据传递时延迟显著,最高达 100ms+

‌RN版本支持

RN 0.67及以下(旧架构默认)

      

原生环境依赖

无特殊要求,JSON序列化

典型应用场景

旧项目维护、简单功能调用(如弹窗/Toast)、低性能要求的工具类模块

实现难度

只需基础 JSON 序列化知识,原生模块注册标准化,文档完善

性能对比

50-100ms (异步队列)、1千 ops/sec、2倍序列化缓存、简单 Toast 提示

优点

开发简单:JSON序列化机制学习成本低,文档完备

生态兼容:支持RN 0.67及以下版本,存量项目维护成本低

调试友好:MessageQueue可监控通信流量

缺点

性能瓶颈:万次调用延迟≥1200ms,列表滚动易卡顿

内存浪费:JSON序列化导致2倍内存占用(如Base64图片传输)

淘汰风险:Facebook已停止功能更新,新架构特性不可用

二、选型经过

第一阶段:

想选个简单的 旧版 Bridge,遇到风险:依赖停止维护相互冲突,没有办法找到稳定的版本。被迫升级版本。

第二阶段:

按照难度选了一个 TurboModules,但是它配置有点复杂,在原先React native 项目上 不能直接集成,需要先升级到新的框架,换一波依赖(有些依赖包国内没有 镜像源,下载速度很慢,或者直接失败),被配置和依赖包升级打败了,最主要的一点 需要Xcode 16以上,在使用的macBook 上现有安装的是15.4. 更新需要先更新macbook系统,要15.4G. 劝退。

第三阶段:

只剩下两个性能高的了,一个直连,一个用引擎,稍微了解了下技术使用,因为是示例 所以没有太设计底层的业务,直连也就只要实现一个桥接就行。引擎的那个是要学习引擎的,劝退。

最后:

选型为 JSL直连。

三、JSL直连技术研究

1、大概原理了解

首先了解到JSL 直连的话是通过一个objective C++ 混合C++来实现 C++桥接到底层内存,访问到JS主线程,和公共对象。

然后就能实现 C++的值和方法 通过指针的暴露复杂一份指针到 js共用对象上实现了JS可调用C++共享的内存内容。

当然这个内存共享的过程 是RN 包中集成的JSI库Hosobject 对象 监听 JS调用 自动实现的。

2、JSL支持所需环境

动手之前先了解下JSL直连的,环境选择。

nodeJS必须得React native 项目,React 基础环境。这里我们之前选择的React native版本为0.72.5,是中间版本,依赖有问题很容易找到相近的版本无论升级降级。node的话只要 16以上就行了,20以下,太新的node环境会出现依赖冲突和毁损,毕竟底层的编译逻辑有变化。这里我们用nvm 管理工具先选择一个18 稳定版本,nvm也方便后续切换node版本。

这里我们用macBook 和 Xcode 调试IOS版本的程序,这里React native 就需要cacapods依赖管理库,这是个自动管理依赖的工具,可以为每个项目当度创建一个Pods文件存放依赖文件。工具下载可以用macBook自带的ruby下载。也可以下载一个Homebrew 开源软件包管理器来下载。

当然 很多开源包都是从github上下载的,可以把源地址切换成国内的镜像源地址。

3、JSL直连实现路径

JSL直连实现可以选择两个路径 新架构的TurboModules 实现,和旧架构的内置集成实现。之前说过了TurboModules 的配置,那就跳过,选旧架构。直接 C++ ->objective-C++->jsi 对象(内存)->JS (虽然是直连,但是React native不是天然支持C++,需要objective-C++ 混编为C语言体才能跨语言调用)

这里选择旧的架构 Bridge(桥模式),JSI 也有两种选择 引擎选择JavaScriptCore 和 Hermes,0.68以上版本 默认开启 Hermes 引擎加速,所以 JSI也是和其内核走要引用 Hermes的一些包,但是现在不支持自动切换,还需要配置头文件地址之类的。引擎加配置,那就,没多大的选择了,虽然JavaScriptCore  内核构建的时候比较慢,但谁让它在老版本是在React native 包内呢?只需要和React native 包路径一起配置就行。关闭Hermes配置。

4、创建RN项目

选择好了方向,那就开始建项目了。先下一个公共的react native 构建包

 @react-native-community/cli 然后cd到要创建项目的目录下,

npx @react-native-community/cli init XXX(项目名称)  --0.72.5 创建一个版本为0.72.5 的React native 项目。

等一段时间其中要选一些配置比如问你是否要下cacapods,如果本地存在这个工具就不用下了。

5、拉起依赖包

等项目下后了后,用npm 或者 yarn 拉取依赖包,有些包0.72.5找不到了,可以选临近的包。

依赖包拉取完了 我们cd到要调试的ios目录下,用cacapods 的命令 pod install 拉取依赖,这个命令用到的地址一般都是 github和国外的一些包管理网站,可能需要针对性加速和替换成国内的镜像源。(注意boost这个包下载地址包管理器需要换成官方的地址和SHA256 Hash)

6、配置Xcode

接下来就是 打开Xcode 进行一些配置了,比如C++语言支持0.72.5默认是c++17支持,还有一些C++文件的联调引入,默认只引入AppDelegate.mm和main.m.

点运行自动进行app生成和模拟器安装打开以及C语言。java、C++的预编译。(注意:相关版本的模拟器需要提前下载)

7、JSL直连示例实现

运行一个示例程序下面就要到 JSI 直连实现了。

(1)、jsi直连逻辑实现(头定义)

首先我们在项目根目录下建立cpp文件,存放我们实现文件和jsi注册头和jsi集成C++逻辑的C++实现类。

cpp/NativeModule.h

#pragma once 
#include <jsi/jsi.h>
using namespace facebook::jsi;extern "C" class NativeModule : public facebook::jsi::HostObject {
private:
facebook::jsi::PropNameID getSymbolToStringTagId(facebook::jsi::Runtime &rt);
public:
// 必须公开析构函数
virtual ~NativeModule() = default;
static void install(Runtime &runtime);
// 必须重写get方法
facebook::jsi::Value get(facebook::jsi::Runtime& runtime, 
const facebook::jsi::PropNameID& name) override;
// 新增toStringTag处理
std::vector<facebook::jsi::PropNameID> getPropertyNames(facebook::jsi::Runtime& rt) override;
};

这里把NativeModule 这个类暴露成C的头样 之后可能在桥接实现中引用。

公开构造函数方便HostObject 自动代理

install 静态方法就是实现注册公用HostObject对象到Js的global公用对像的方法。

get 函数 就是HostObject 自动代理到get中获取对应c++逻辑的实现。

getSymbolToStringTagId和 getPropertyNames

都是为了解决 JS的 Symbo.StringTagId 类型找不到的问题。

(2)、jsi直连实现

cpp/NativeModule.cpp

这里截取一些重要的片段

void NativeModule::install(Runtime &runtime) {
auto instance = std::make_shared<NativeModule>();

创建智能指针,自动实现一次性分配内存和内存自动释放。

//绑定方法
runtime.global().setProperty(
runtime,
"nativeModule",
Object::createFromHostObject(runtime, std::move(instance)) 

这里使用 facebook::jsi::Runtime 运行时对象 挂载NativeModule对象到JS的global 共用对象上。JS直接可以 global.nativeModule 访问挂载对象。

std::vector<facebook::jsi::PropNameID> NativeModule::getPropertyNames(facebook::jsi::Runtime& rt) {
std::vector<facebook::jsi::PropNameID> props;
props.reserve(4); // 预分配空间优化性能
props.push_back(facebook::jsi::PropNameID::forAscii(rt, "exampleMethod"));
props.push_back(facebook::jsi::PropNameID::forAscii(rt, "add")); 
props.push_back(facebook::jsi::PropNameID::forAscii(rt, "multiply"));
props.push_back(facebook::jsi::PropNameID::forAscii(rt, "Symbol.toStringTag"));
return props;
}

这个方法把字符串的方法名生成PropNameID,HostObject 对象监听方法名转化为PropNameID在转给get方法。

Value NativeModule::get(Runtime &runtime, const PropNameID &name) {
try {
auto methodName = name.utf8(runtime);
if (methodName == "exampleMethod") {
return Function::createFromHostFunction(
runtime,
name,
0, // 参数数量修正
[](Runtime &runtime, const Value &, const Value *, size_t) {
return String::createFromAscii(runtime, "Hello from C++");
}
);
}

这样get就把PropNameID 转化为字符串匹配方法名用 Function::creactFromHostFunction 把C++的逻辑和内存暴露给 JSI方向。

这里JSI把C++内存 和逻辑暴露给JS的实现就写好了,接下来就是桥接到入口文件进行注册了,这里我们只进行了IOS环境的调研。

这里使用了一个例子方法,你也可以导入你的C++实际业务类。调用实现的方法。

(3)用objective-C++ 的mm文件 进行桥接

ios/XXX(项目名称)/

和入口AppDelegate.mm 同一个文件夹下建立桥接头和文件

NativeModuleWrapper.h

#pragma once
#import <React/RCTBridgeModule.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>
#import <Foundation/Foundation.h>
#import <objc/runtime.h>@interface NativeModuleWrapper : NSObject <RCTBridgeModule>
+ (void)setupWithRuntime:(facebook::jsi::Runtime &)runtime;
@end

这里桥接的头定义要写C语言的头

桥接接口继承 NSObject <RCTBridgeModule>

定义了一个自定义的注册接口

(4)桥接的实现

ios/XXX(项目名称)/NativeModuleWrapper.mm

// React Native 核心
#import <React/RCTBridgeModule.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>// JSI 和 C++ 支持
#import <jsi/jsi.h>
#import <ReactCommon/CallInvoker.h>
#import <vector>
#import <string>// 桥接和系统
#import <Foundation/Foundation.h>
#import <objc/runtime.h>// 本地模块
#import "NativeModuleWrapper.h"
#import "../../cpp/NativeModule.h"@implementation NativeModuleWrapperRCT_EXPORT_MODULE(NativeModule);+ (void)setupWithRuntime:(facebook::jsi::Runtime &)runtime {
RCTLogInfo(@"Module registered");
NativeModule::install(runtime); // 调用 C++ 的 install 方法 
}@end

这里主要逻辑RCT_EXPORT_MODULE(NativeModule);暴露NativeModule头的内容。

在自定义注册方法中,添加进C++的注册逻辑

(5)入口文件改造

ios\XXX(项目名)\AppDelegate.mm(项目创建的时候自动生成的)

在这里补充一下监听以及注册的逻辑

#include "NativeModuleWrapper.h"

引入桥接的头

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge {
return @[[NativeModuleWrapper new]]; // 返回需注册的模块
}

扩展桥接模块。

- (void)setBridge:(RCTBridge *)bridge {
[super setBridge:bridge];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(bridgeDidFinishLoading:)
name:RCTJavaScriptDidLoadNotification
object:bridge];
}

这里在桥对象初始话的方法中重新 添加JS加载完成的监听,回调到bridgeDidFinishLoading 方法

保证注册是在桥对象和JS都初始化完成后

- (void)bridgeDidFinishLoading:(NSNotification *)notification {
RCTBridge *bridge = notification.object;
// 2. 安全地在JS线程执行
dispatch_async(dispatch_get_main_queue(), ^{
[self installJsiModule:bridge];
});
}

使用JS主线程安全执行,执行默认在主线程队列中进行。

下面是installJsiModule 部分逻辑

if (bridge && [bridge respondsToSelector:@selector(batchedBridge)]) {
id batchedBridge = [bridge batchedBridge];
if (batchedBridge && [batchedBridge respondsToSelector:@selector(runtime)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([batchedBridge respondsToSelector:@selector(runtime)]) {
auto *runtimePtr = (__bridge facebook::jsi::Runtime*)[batchedBridge performSelector:@selector(runtime)];
facebook::jsi::Runtime &runtime = *static_cast<facebook::jsi::Runtime*>(runtimePtr); 
auto global = runtime.global();
[batchedBridge 
dispatchBlock:^{ 
[NativeModuleWrapper setupWithRuntime:runtime]; 
} 
queue:RCTJSThread];
}
#pragma clang diagnostic pop
}
}

这里通过 batchedBridge 的runtime对象 降级获取,0.72.5版本 不加 降级参数 会编译报错,提示已废弃,私有化不能获取。

后面就是不同类型之间的强转。

最后在批量桥的对象的线程安全保证中 执行桥的初始话方法,传递运行时对象到桥注册方法。

到这里 C++业务实现类 和 objective-C++桥接类和主入口都改完了。

(6)、React前端的调用

seEffect(() => {
showMessage();
//回收
return () => {tiemRef?.current && clearTimeout(tiemRef.current);}
}, [])

初始话调用测试方法 返回 Hello from C++ 

const showMessage = () => {
console.log("JSI 模块可用:", 'nativeModule' in global);
if('nativeModule' in global) {
tiemRef?.current && clearTimeout(tiemRef.current);
const module = global.nativeModule as NativeModule | undefined ;
if(module) {
const _res = module?.exampleMethod();
setMessage(_res);
}
} else {
tiemRef.current = setTimeout(() => {
showMessage();
}, 100);
}
}

初始话调用的话可能 JSI 还没初始话好,重复调用。

这里重新用Xcode 联调的话就能看到从C++发过来的消息了

http://www.dtcms.com/a/337948.html

相关文章:

  • 设计模式之汇总
  • CNN-BiLSTM-Attention、CNN-BiLSTM、BiLSTM三模型多变量时序光伏功率预测
  • 物联网智能边缘架构:流数据处理与设备管理的协同优化
  • PHP如何利用GD库函数生成图片验证码?
  • 在Excel启动时直接打开多个Excel文件
  • golang读写锁和互斥锁的区别
  • 理解AQS的原理并学习源码
  • MongoDB新手教学
  • 2025 世界机器人大会:前沿科技闪耀,机器人板块未来可期
  • Android 圆形和圆角矩形总结
  • MyCAT完整实验报告
  • Unity作为库导入Android原生工程
  • AVB(Android Verified Boot)中vbmeta结构浅析
  • Unity2022打包安卓报错的奇葩问题
  • Java面试宝典:Redis 入门与应用
  • 【OpenAI】 GPT-4o-realtime-preview 多模态、实时交互模型介绍+API的使用教程!
  • 线程间同步机制与进程间通信
  • 数据处理和统计分析 —— Pandas 基础(附数据集)
  • SMTPman,smtp ssl助力安全高效邮件传输!
  • redhat9从github下拉软件包一直报错
  • petalinux2023.1编译pmu-rom-native...fetch error问题
  • 39-Linux下安装python
  • BPO(Business Process Optimization,业务流程优化)
  • FPGA驱动量子革命:微美全息(NASDAQ:WIMI)实现数字量子计算关键验证
  • 任务六 歌手页面功能开发
  • single cell ATAC(11)ArchR鉴定标记Peak
  • Spring AI RAG 检索增强 应用
  • RAG深入解读:文本分块、混合检索、重排序、bge微调(工程落地实践)
  • Android 流式布局实现方案全解析
  • Android输入框文字不垂直居中