
在NDK开发中,Node-API是实现ArkTS/JS与C/C++交互的核心机制。遵循Node-API开发规范,可以写出更稳定、更高效、更安全的Native代码。
一、获取JS传入参数及其数量
当传入napi_get_cb_info的argv不为nullptr时,argv的长度必须大于等于传入argc声明的大小。
当argv不为nullptr时,napi_get_cb_info会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量,该接口仅会将声明的argc数量的参数写入argv;而当argc大于实际参数数量时,该接口会在argv的尾部填充undefined。
错误示例
// argc未正确初始化,值为随机值,可能导致数据越界
static napi_value IncorrectDemo1(napi_env env, napi_callback_info info) {
size_t argc;
napi_value argv[10] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
return nullptr;
}
// argc声明数量大于argv实际初始化的长度
static napi_value IncorrectDemo2(napi_env env, napi_callback_info info) {
size_t argc = 5;
napi_value argv[3] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
return nullptr;
}
正确示例
//先传入nullptr获取真实参数数量,再动态分配数组
static napi_value GetArgvDemo1(napi_env env, napi_callback_info info) {
size_t argc = 0;
napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
if (argc == 0) {
return nullptr;
}
napi_value* argv = new napi_value[argc];
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
// 业务处理…
delete[] argv;
return nullptr;
}
// 声明argv长度 >= argc
static napi_value GetArgvDemo2(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value argv[2] = {nullptr};
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
return nullptr;
}
二、生命周期管理
合理使用napi_open_handle_scope和napi_close_handle_scope管理napi_value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。
每个napi_value属于特定的HandleScope,HandleScope关闭后,所属的napi_value就会自动释放。
正确示例
// 在频繁创建JS对象的循环中,加handle_scope及时释放资源
for (int i = 0; i < 100000; i++) {
napi_handle_scope scope = nullptr;
napi_open_handle_scope(env, &scope);
if (scope == nullptr) {
return;
}
napi_value res;
napi_create_object(env, &res);
napi_close_handle_scope(env, scope);
}
三、上下文敏感
多引擎实例场景下,禁止通过Node-API跨引擎实例访问JS对象。
引擎实例是一个独立运行环境,JS对象创建访问等操作必须在同一个引擎实例中进行。引擎实例在接口中体现为napi_env。
错误示例
// 在env1创建string对象,在env2创建object并设置string属性
napi_create_string_utf8(env1, "bar", NAPI_AUTO_LENGTH, &string);
napi_create_object(env2, &object);
napi_set_named_property(env2, object, "foo", string);
// 在env2中访问env1的对象可能导致崩溃
说明:所有的JS对象都隶属于具体的某一napi_env,不可将env1的对象设置到env2的对象中。
四、异常处理
Node-API接口调用发生异常需要及时处理,不能遗漏异常到后续逻辑。
正确示例
//每步都检查返回值
napi_status status = napi_create_object(env, &object);
if (status != napi_ok) {
napi_throw_error(env, …);
return;
}
status = napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, &string);
if (status != napi_ok) {
napi_throw_error(env, …);
return;
}
status = napi_set_named_property(env, object, "foo", string);
if (status != napi_ok) {
napi_throw_error(env, …);
return;
}
只有当方法返回值是napi_ok时,才能继续正常运行;否则后续流程可能出现不可预期行为。
五、异步任务
使用uv_queue_work方法将任务抛到JS线程上执行时,对JS线程的回调方法需要加上napi_handle_scope管理napi_value生命周期。
说明:若只想往JS线程抛任务,不推荐使用uv_queue_work方法,请使用napi_threadsafe_function系列接口。
六、对象绑定
使用napi_wrap接口时,如果最后一个参数result传递不为nullptr,需要在合适时机调用napi_remove_wrap主动删除创建的napi_ref。
// 不需要接收napi_ref,传递nullptr
napi_wrap(env, jsobject, nativeObject, cb, nullptr, nullptr);
//需要接收napi_ref,返回强引用,需手动释放
napi_ref result;
napi_wrap(env, jsobject, nativeObject, cb, nullptr, &result);
// 后续及时调用napi_remove_wrap释放
void* nativeObjectResult = nullptr;
napi_remove_wrap(env, jsobject, &nativeObjectResult);
七、高性能数组
存储值类型数据时,使用ArrayBuffer代替JSArray来提高应用性能。
| JSArray | 1566.174 |
| ArrayBuffer | 3.609 |
//使用ArrayBuffer作为容器
static napi_value ArrayBufferDemo(napi_env env, napi_callback_info info) {
constexpr size_t arrSize = 1000;
napi_value arrBuffer = nullptr;
void* data = nullptr;
napi_create_arraybuffer(env, arrSize * sizeof(int32_t), &data, &arrBuffer);
if (data == nullptr) {
return arrBuffer;
}
int32_t* i32Buffer = reinterpret_cast<int32_t*>(data);
for (int i = 0; i < arrSize; i++) {
i32Buffer[i] = i; // 直接操作缓冲区
}
return arrBuffer;
}
八、数据转换
| 减少数据转换次数 | 批量处理数据或使用更高效的数据结构 |
| 避免不必要的数据复制 | 使用Node-API接口直接访问原始数据 |
| 使用缓存 | 多次转换中重复使用的数据可缓存 |
九、模块注册与模块命名
| nm_register_func | 需加static修饰,防止符号冲突 |
| 模块注册入口函数名 | 确保与其他模块不同 |
| nm_modname | 需与so文件名完全匹配,区分大小写 |
| 一个so文件 | 只能注册一个模块 |
正确示例
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// …
return exports;
}
EXTERN_C_END
static napi_module nativeModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "nativerender", // 与so文件名完全匹配
.nm_priv = nullptr,
.reserved = { 0 },
};
extern "C" __attribute__((constructor)) void RegisterNativeRenderModule() {
napi_module_register(&nativeModule);
}
十、dlopen与模块注册
如果注册的模块事先有被dlopen,需使用以下方式注册模块。
模块需对外导出固定名称为napi_onLoad的函数,在该函数内调用注册函数。
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// …
return exports;
}
EXTERN_C_END
static napi_module nativeModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "nativerender",
.nm_priv = nullptr,
.reserved = { 0 },
};
extern "C" void napi_onLoad() {
napi_module_register(&nativeModule);
}
十一、napi_create_external系列接口使用规范
napi_create_external系列接口创建出来的JS对象仅允许在当前线程传递和使用,跨线程传递将会导致应用crash。
若需跨线程传递绑定有Native对象的JS对象,请使用napi_coerce_to_native_binding_object接口。
十二、防止重复释放获取的buffer
使用napi_get_arraybuffer_info等接口,参数data资源开发者不允许释放,data的生命周期受引擎管理。
受此规则约束的接口:
-
napi_create_arraybuffer
-
napi_create_sendable_arraybuffer
-
napi_get_arraybuffer_info
-
napi_create_buffer
-
napi_get_buffer_info
-
napi_get_typedarray_info
-
napi_get_dataview_info
错误示例
//不允许delete arrayBufferPtr
void* arrayBufferPtr = nullptr;
napi_value arrayBuffer = nullptr;
napi_create_arraybuffer(env, ARRAY_BUFFER_SIZE, &arrayBufferPtr, &arrayBuffer);
size_t arrayBufferSize;
napi_get_arraybuffer_info(env, arrayBuffer, &arrayBufferPtr, &arrayBufferSize);
delete arrayBufferPtr; // 禁止!会导致double free
十三、其他
合理使用napi_object_freeze和napi_object_seal控制对象可变性。
| napi_object_freeze | 等同于Object.freeze,所有属性不可修改 |
| napi_object_seal | 等同于Object.seal,不可增删属性,但可改属性值 |
核心
| 参数获取 | argv长度 >= argc声明大小 |
| 生命周期 | HandleScope最小化,避免内存泄漏 |
| 上下文 | 禁止跨napi_env访问JS对象 |
| 异常处理 | 每步检查napi_ok,及时处理 |
| 异步任务 | 回调需加handle_scope |
| 对象绑定 | napi_wrap result非空需手动remove_wrap |
| 高性能数组 | 值类型数据用ArrayBuffer |
| 模块注册 | nm_modname与so文件名完全匹配,一个so只注册一个模块 |
| dlopen | 导出napi_onLoad函数注册 |
| buffer管理 | data由引擎管理,禁止开发者释放 |







