React Native JSI 深入剖析 — 第 7 部分中文技术整理:把 C++ 能力接到 iOS 和 Android

文章主旨

前面几篇已经回答了:

  • JSI 为什么快;
  • host function 和 HostObject 怎么写;
  • 内存所有权怎么理解。

但这些代码都还停留在"概念上可运行"。Part 7 解决的是更现实的问题:

你的 C++ 代码怎么真的接进 React Native 的 iOS 和 Android 工程里?

原文把它叫作 plumbing,很贴切。因为这一篇主要不是算法,也不是架构抽象,而是"平台装配"。

这篇要解决的不是 C++,而是两套平台语言边界

一个很关键的判断是:

  • 你的业务核心可以写成跨平台共享 C++;
  • 但 iOS 和 Android 并不会原生"直接理解"这段 C++;
  • 你必须分别做一层平台侧 wiring。

所以真正的结构是:

  • 中间是一套共享 C++ engine;
  • 左边用 Objective-C++ 接 iOS;
  • 右边用 JNI + CMake 接 Android。

这也是原文"一个发动机,两条装配线"的类比含义。

一个标准 JSI 模块的目录结构应该怎么想

原文先给出了典型文件布局,这其实非常有参考价值。一个生产级 JSI 模块通常会有三层:

1. 共享 C++ 层

放:

  • install() 入口;
  • host function / HostObject 实现;
  • 共享数据结构;
  • 通用算法与内存管理逻辑。

2. iOS wiring 层

放:

  • .mm Objective-C++ 文件;
  • 与 RN iOS runtime 对接的安装逻辑;
  • podspec / CocoaPods 配置。

3. Android wiring 层

放:

  • Kotlin / Java 的 module 壳;
  • JNI glue code;
  • CMakeLists.txt。

这个结构很重要,因为它说明一件事:

真正应尽量跨平台共享的是 C++ 核心,而不是整套平台集成代码。

为什么 iOS 要用 Objective-C++

原文在 iOS 部分讲得很清楚:问题不只是"语言喜好",而是调用链兼容性。

iOS 侧的现实是:

  • React Native 的 iOS 内部层大量是 Objective-C / Objective-C++;
  • 你的 JSI 实现是 C++;
  • 普通 Objective-C 不会直接调用 C++;
  • Swift 虽然近年有 C++ interop,但在 RN 生态里还不够成熟和主流。

所以最务实的桥梁语言就是 .mm:

Objective-C++ 可以在同一个文件里同时理解 Objective-C 和 C++。

这也是为什么很多 RN JSI 库的 iOS 入口文件都是 .mm 而不是 .m 或 .swift。

iOS 侧真正做了什么

iOS wiring 的任务可以压缩成一句话:

在正确的时机拿到 jsi::Runtime,然后调用你的 install(runtime)。

围绕这句话,iOS 侧通常要做的事包括:

  • 在原生模块初始化时接入 RN bridge;
  • 获取底层 runtime;
  • 进入 Objective-C++ 层;
  • 调你的共享 C++ 安装函数;
  • 把能力注入到 JS runtime。

Podspec 的作用则不是"注册 JSI 功能本身",而是确保:

  • 这些源文件被编译;
  • C++ 标准配置正确;
  • 头文件路径和依赖关系正确。

所以 podspec 更像 build plumbing,而不是逻辑 plumbing。

Android 比 iOS 更分层:Kotlin、JNI、CMake 三层配合

Android 部分是整篇最值得整理的地方。原文把它拆成三层,我觉得这个拆法非常清楚。

第 1 层:Kotlin / Java 模块声明

这一层负责:

  • 让 React Native 知道有这个模块;
  • 在合适时机暴露原生入口;
  • 把 runtime 指针或上下文继续传下去。

它是 RN 世界和 Android App 世界的连接点。

第 2 层:JNI glue

这一层负责:

  • 让 JVM 世界和 C++ 世界对接;
  • 把 Java/Kotlin 调用映射到 C++ 函数;
  • 处理 jlong 等桥接参数;
  • 最终触达 install()。

这一步的本质不是"业务逻辑",而是 ABI 级别的语言边界适配。

第 3 层:CMake

这一层负责:

  • 告诉 Android NDK 如何编译你的 C++;
  • 链接 RN / JSI 相关依赖;
  • 产出 .so。

所以 Android wiring 的完整心智模型不是"写一个 native module 就完了",而是:

Kotlin 宣告入口,JNI 负责语言桥接,CMake 负责把 C++ 真正编出来。

为什么 Android 经常把 runtime 当作 jlong 传

原文 FAQ 里提到一个很常见的疑问:为什么不是传"一个 runtime 对象",而是把 runtime 指针当 long / jlong 传递。

本质原因很简单:

  • JVM 不理解 C++ 对象模型;
  • JNI 边界上最现实的通道通常是原始地址值;
  • 所以你传的不是"高层语义对象",而是"底层指针句柄"。

这虽然不优雅,但很常见,也符合 JNI 这类边界的真实工作方式。

平台 wiring 的关键不是"怎么接",而是"什么时候接"

原文专门讲了 initialization timeline,这一点很重要。

JSI 安装不是随便找个模块构造时机就行,而是必须满足:

  • runtime 已经存在;
  • bridge / engine 已经初始化;
  • 你注入的函数或对象不会错过 JS 侧使用时机;
  • 也不会早到访问空 runtime。

所以 platform wiring 里最容易出的问题不是代码写不出来,而是:

  • 装得太早;
  • 装得太晚;
  • runtime 生命周期判断错误;
  • 热重载、重建 bridge 后没有重新安装。

这也是为什么"能编过"和"能稳定运行"之间还有一大段距离。

手工 wiring 的主要代价

原文没有回避这个方案的成本,主要有三类。

1. 依赖内部实现细节

无论 iOS 还是 Android,很多 runtime 获取路径都不是特别稳定、公开、长期承诺不变的高层 API。它们能用,但会受 RN 内部演进影响。

2. 双平台构建维护成本

你不只是维护一份 C++ 代码,还要维护:

  • Podspec
  • Objective-C++
  • Kotlin / Java 壳
  • JNI glue
  • CMake

3. 调试链路变长

出问题时,可能卡在:

  • JS 没调用到;
  • 调到了原生模块但没进 JNI;
  • JNI 进了但 .so 没正确导出;
  • iOS / Android 某一侧运行时没正确初始化。

这意味着平台 wiring 不是"写一次就忘",而是生产维护成本的一部分。

我的补充理解

1. Part 7 本质上是在教你把"JSI demo"变成"可交付模块"

前几篇你学到的是机制;这一篇你才真正开始进入"库作者"视角。因为到了这里,你面对的已不是 createFromHostFunction 本身,而是:

  • 包结构怎么分;
  • 双平台怎么编;
  • runtime 何时拿;
  • 安装时机如何稳定。

2. 共享 C++ 是价值,平台 wiring 是成本

这是所有 JSI 模块都绕不开的基本面。你之所以愿意承担 iOS / Android 各自的一层集成成本,是因为:

  • 核心算法只写一份;
  • 生命周期和性能策略也能共享;
  • 高性能能力不必双写。

如果共享层很薄,这套成本未必划算;如果共享层很厚,这套成本就很值得。

3. 手工 wiring 也解释了为什么后面会讨论 TurboModules

Part 11 会回头比较 TurboModules、pure JSI、pure C++。Part 7 提前暴露了 pure JSI 的一个现实代价:

你拥有最大控制权,同时也承担最多平台装配细节。

关键结论

  • JSI 模块的核心逻辑可以写成共享 C++,但 iOS 和 Android 都需要各自平台 wiring。
  • iOS 的现实桥梁语言是 Objective-C++,Android 的现实路径是 Kotlin/Java + JNI + CMake。
  • 平台集成的目标都是一样的:在正确时机拿到 jsi::Runtime,然后调用共享的 install()。
  • build 配置、初始化时机、runtime 生命周期判断,是平台 wiring 里最常见的稳定性来源。
  • pure JSI 的高控制权,直接对应更高的平台维护成本。

下一步适合看什么

紧接着看的应该是 Part 8,因为到这里虽然"能接进来了",但所有调用仍然是同步阻塞 JS 线程的。

下一篇真正回答的是:

重活怎么放到后台线程,还能安全地把结果送回 JS?

相关推荐
song5013 小时前
React Native for OpenHarmony 实战:三方库 react-native-css-transformer 的鸿蒙化适配指南
css·人工智能·深度学习·react native·transformer
enjoywindstorm2 天前
移动端原生框架对比评测
react native
传奇开心果编程3 天前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
传奇开心果编程4 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程4 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
500845 天前
React Native for OpenHarmony 实战:三方库 react-native-url-polyfill 的鸿蒙化适配指南
javascript·react native·react.js·性能优化·electron·harmonyos
传奇开心果编程5 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
500845 天前
React Native for OpenHarmony 实战:三方库 react-native-volume-control 的鸿蒙化适配指南
javascript·react native·react.js·electron·harmonyos
5008410 天前
React Native for OpenHarmony 实战:三方库 react-native-crypto-js 的鸿蒙化适配指南
javascript·react native·性能优化·electron·harmonyos
5008410 天前
React Native for OpenHarmony 实战:三方库 react-native-device-name 的鸿蒙化适配指南
深度学习·react native·react.js·机器学习·harmonyos