前言
N-API 调用时底层 VM 的堆中对象会返回 napi_value 句柄。函数代码运行时,这些句柄对象一直保持活跃;待函数结束时,才会被 GC 回收。 N-API 对象的生命周期管理本质上是控制对象什么时机被销毁。主要通过添加作用域和 create reference 方式。
场景一:执行的函数还没结束,出现大量 N-API Value 闲置句柄,消耗资源,影响函数后部的执行性能
解决思路
在函数执行结束前,提前销毁这个 NAPI 对象;使用 napi_open/close__handle_scope 在函数创建作用域,从该作用域跳出后,里面的变量被销毁。
JS 侧
Row() {
Text("handleScope")
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
hilog.info(0x0000,
'testTag',
'Test NAPI handleScope %{public}d',
// 调用 C++ 侧方法,传入数组
testNapi.handleScope([1,2,3,4,5,6])
);
})
}
C++侧
static napi_value handleScope(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value object;
// 接收 JS 侧传入的数组,赋值给 object。
napi_get_cb_info(env, info, &argc, &object, nullptr, nullptr);
napi_handle_scope scope;
// 创建一个新的作用域,赋值给 scope,这里是作用域的起点。
napi_open_handle_scope(env, &scope);
// 在作用域里定义 NAPI 对象
napi_value eleVal;
// 用数组的元素给对象赋值
napi_get_element(env, object, 2, &eleVal);
// 关闭 scope 作用域,此处为作用域终点
napi_close_handle_scope(env, scope);
// 作用域外,是访问不了作用域内的数据,比如 eleVal
int32_t res;
napi_get_value_int32(env, eleVal, &res);
// eleVal 打印结果是 0,而不是 2,说明 eleVal 在函数结束前被销毁。
// 去除作用域,打印结果是 2
return eleVal;
}
场景二:在场景一的基础上,需要作用域内指定变量能够在作用域外访问
解决思路
使用 napi_open/close_escapable_handle_scope,如其名 escapable 可逃逸的,这种作用域里的变量是可逃逸到外部作用域,但是需要 napi_escape_handle 指定哪个对象。
JS 侧
Row() {
Text("handleEscapableScope")
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
hilog.info(0x0000,
'testTag',
'Test NAPI handleEscapableScope %{public}d',
// 调用 C++ 侧方法,传入数组
testNapi.handleEscapableScope([1,2,3,4,5,6])
);
})
}
C++侧
static napi_value handleEscapableScope(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value object;
// 接收 JS 侧传入的数组,赋值给 object。
napi_get_cb_info(env, info, &argc, &object, nullptr, nullptr);
napi_value outerEleVal;
napi_escapable_handle_scope scope;
// 创建一个新的可逃逸作用域,赋值给 scope,这里是作用域的起点。
napi_open_escapable_handle_scope(env, &scope);
// 在作用域里定义将要逃逸的 NAPI 对象
napi_value eleVal;
// 用数组的元素给对象赋值
napi_get_element(env, object, 2, &eleVal);
// 将 eleVal 逃逸出作用域,外部用 outerEleVal 接收
napi_escape_handle(env, scope, eleVal, &outerEleVal);
// 关闭 scope 作用域,此处为作用域终点
napi_close_escapable_handle_scope(env, scope);
int32_t res;
napi_get_value_int32(env, outerEleVal, &res);
// outerEleVal 打印的结果是 2,说明指定的 NAPI 对象 eleVal,已逃逸出作用域
return outerEleVal;
}
场景三:需要函数里 NAPI 对象在函数执行结束后,仍然不被销毁
解决思路
引用计数垃圾收集策略 给函数里的 NAPI 对象创建 reference,通过控制 reference 的引用计数不为 0,让该对象不被回收销毁
JS 侧
Row() {
Text("handleReference")
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
hilog.info(0x0000,
'testTag',
'Test NAPI handleReference %{public}d',
// 调用 C++ 侧方法
testNapi.handleReference()
);
})
}
C++ 侧
// 定义引用
napi_ref ref;
// 使用异步 work,是为了模拟主函数结束后,在异步函数里去访问对象
// 验证主函数的 NAPI 对象是否随主函数结束而被销毁
struct AsyncWorkInfo {
napi_async_work work;
};
struct AsyncWorkInfo data = { nullptr };
static void AExecute(napi_env env, void *data) {
// 异步函数内
struct AsyncWorkInfo *arg = (struct AsyncWorkInfo *)data;
napi_value recv;
// 获取引用值,用 recv 接收
napi_get_reference_value(env, ref, &recv);
int32_t res;
napi_get_value_int32(env, recv, &res);
// 打印引用值为 333,说明该引用映射的主函数 NAPI 对象没有随着主函数结束被销毁
// 从而控制了 NAPI 对象的生命周期
OH_LOG_INFO(LOG_APP, "ref val is %{pubilc}d", res);
}
static napi_value handleReference(napi_env env, napi_callback_info info)
{
// 主函数内
napi_value resourceName;
napi_create_string_utf8(env, "asyncWork", NAPI_AUTO_LENGTH, &resourceName);
// 定义主函数的 NAPI 对象
napi_value test_data;
napi_create_int32(env, 333, &test_data);
// 给主函数 NAPI 创建绑定引用
napi_create_reference(env, test_data, 1, &ref);
struct AsyncWorkInfo *ptr = &data;
// 异步执行 AExecute
napi_create_async_work(env, nullptr, resourceName, AExecute, nullptr, ptr, &ptr->work);
napi_queue_async_work(env, ptr->work);
return resourceName;
}