React Native TurboModules 实战踩坑记 高性能原生模块集成方案
我的写法,亲测靠谱
搞TurboModules这玩意儿也有段时间了,说实话刚开始真是一脸懵逼。React Native那套架构看起来挺复杂,但其实摸清楚套路后也就那么回事。我一般把原生模块分几个层级:头文件定义、实现类、注册绑定,然后JS端调用。
先说说最基础的头文件声明吧,这个千万别偷懒。我是这么写的:
// MyTurboModule.h
#pragma once
#include <ReactCommon/TurboModule.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class JSI_EXPORT MyTurboModule : public TurboModule {
public:
MyTurboModule(const CallInvokerHolder::Shared& jsCallInvoker);
jsi::Value multiply(jsi::Runtime& runtime, jsi::Value value, jsi::Value multiplier) override;
jsi::Value asyncOperation(jsi::Runtime& runtime, jsi::Value options) override;
};
} // namespace react
} // namespace facebook
这种写法的好处是接口定义清晰,继承关系一目了然。记得那个JSI_EXPORT宏不能少,不然在某些平台上会有链接问题。
实现类里面我比较注重参数验证和异常处理:
// MyTurboModule.cpp
#include "MyTurboModule.h"
#include <stdexcept>
namespace facebook {
namespace react {
MyTurboModule::MyTurboModule(const CallInvokerHolder::Shared& jsCallInvoker)
: TurboModule("MyTurboModule", jsCallInvoker) {
methodMap_["multiply"] = MethodMetadata{
2,
[this](jsi::Runtime& rt, const jsi::Value* args, size_t count) -> jsi::Value {
if (count != 2 || !args[0].isNumber() || !args[1].isNumber()) {
throw std::runtime_error("Invalid arguments for multiply");
}
double result = args[0].asNumber() * args[1].asNumber();
return jsi::Value(result);
}
};
}
jsi::Value MyTurboModule::multiply(jsi::Runtime& runtime, jsi::Value value, jsi::Value multiplier) {
// 实现具体逻辑
double val1 = value.asNumber();
double val2 = multiplier.asNumber();
return jsi::Value(val1 * val2);
}
jsi::Value MyTurboModule::asyncOperation(jsi::Runtime& runtime, jsi::Value options) {
// 异步操作实现
auto promise = createPromiseAsJSIValue(
runtime,
[options](jsi::Runtime& rt, std::shared_ptr<Promise> promise) {
// 执行异步任务
try {
// 模拟异步处理
promise->resolve("success");
} catch (const std::exception& e) {
promise->reject(e.what());
}
});
return promise;
}
} // namespace react
} // namespace facebook
这里的关键是methodMap_的构建,函数签名必须匹配TurboModule的要求。参数验证这块我踩过不少坑,特别是类型检查,JS端传过来的数据类型可能跟预期不一致,不做校验很容易崩溃。
这几种错误写法,别再踩坑了
之前见过很多新手的写法,真的是让人头疼。最常见的一种就是完全不管内存管理,直接裸指针乱飞:
// 错误写法!千万别学
jsi::Value badFunction(jsi::Runtime& runtime, jsi::Value input) {
char* buffer = new char[1024]; // 没有对应的delete
// ... 一堆操作
return jsi::Value(runtime, jsi::String::createFromUtf8(runtime, buffer));
}
这种写法会导致严重的内存泄漏,而且在高频调用的场景下很快就会OOM。还有那种忘记检查null值的:
// 也是错误写法
jsi::Value getValue(jsi::Runtime& runtime, jsi::Value obj) {
// 没有检查obj是否为undefined或null
auto prop = obj.getObject(runtime).getProperty(runtime, "value");
return prop; // 如果obj为空对象,这里会崩溃
}
还有一个特别容易出问题的地方是异步回调处理不当:
// 危险的异步写法
void dangerousAsync(jsi::Runtime& runtime, jsi::Value callback) {
std::thread([callback, &runtime]() {
// 在子线程中直接使用callback,这是错的!
callback.call(runtime, "result"); // runtime在子线程中不可用
});
}
JSI的runtime不是线程安全的,必须通过callinvoker来调度回到JS线程执行回调。这些错误我在项目初期都遇到过,调试起来特别痛苦。
实际项目中的坑
真正集成到项目里才发现,光是理论知识完全不够用。最头疼的是平台差异处理,iOS和Android的ABI不一样,类型大小也可能不同。我在做加密模块的时候就遇到过这个问题:
// 原本以为这样就能跨平台,结果发现size_t在不同平台长度不一样
jsi::Value hashData(jsi::Runtime& runtime, jsi::Value data) {
std::string inputData = data.getString(runtime).utf8(runtime);
size_t hashValue = calculateHash(inputData); // size_t在32位设备上只有4字节
// 返回给JS时可能丢失精度
return jsi::Value(static_cast<double>(hashValue));
}
后来改成返回字符串形式的哈希值才解决精度丢失问题。还有个问题是热重载兼容性,TurboModule在开发模式下热重载经常出问题,主要是native代码重新编译后JS端的引用还在用旧的。
性能优化方面也需要注意,频繁的小对象创建会严重影响性能:
// 性能很差的写法
for (int i = 0; i < 1000; ++i) {
jsi::Object obj(runtime);
obj.setProperty(runtime, "index", jsi::Value(i));
// 每次循环都创建新对象
}
// 更好的做法是批量处理
jsi::Array array(runtime, 1000);
for (int i = 0; i < 1000; ++i) {
jsi::Object obj(runtime);
obj.setProperty(runtime, "index", jsi::Value(i));
array.setValueAtIndex(runtime, i, std::move(obj));
}
另外还要特别注意错误传播,JS端的try-catch可能捕获不到native层抛出的异常,需要统一的错误处理机制。我在网络请求模块里专门加了一个错误包装函数:
jsi::Value wrapNetworkResult(jsi::Runtime& runtime, const NetworkResult& result) {
jsi::Object obj(runtime);
if (result.success) {
obj.setProperty(runtime, "status", jsi::Value("success"));
obj.setProperty(runtime, "data", jsi::Value(result.data));
} else {
obj.setProperty(runtime, "status", jsi::Value("error"));
obj.setProperty(runtime, "message", jsi::Value(result.errorMsg));
obj.setProperty(runtime, "code", jsi::Value((double)result.errorCode));
}
return obj;
}
这样JS端就能统一处理各种网络状态,不用关心底层的具体异常情况。
调试也是一个大坑,Xcode和Android Studio的调试器对JSI对象支持都不太好,我一般在关键位置打日志,然后通过网络发送到本地服务器查看(比如发到 https://jztheme.com/debug 接收日志)。
以上是我踩坑后的总结,希望对你有帮助
TurboModules确实比老的Native Modules快不少,但复杂度也高了。实际项目中还是得根据具体需求权衡,不是所有模块都需要用TurboModules重写。这套东西最大的优势是性能,劣势是开发和维护成本高。我一般是那些对性能要求特别高的模块才考虑用这个,比如音视频处理、大量数据计算、实时通信等场景。
以上是我个人对TurboModules的完整讲解,有更优的实现方式欢迎评论区交流。

暂无评论