1# 使用Node-API接口进行生命周期相关开发 2<!--Kit: NDK--> 3<!--Subsystem: arkcompiler--> 4<!--Owner: @xliu-huanwei; @shilei123; @huanghello--> 5<!--Designer: @shilei123--> 6<!--Tester: @kirl75; @zsw_zhushiwei--> 7<!--Adviser: @fang-jinxu--> 8 9## 简介 10 11在Node-API中,napi_value是一个表示ArkTS值的抽象类型,它可以表示任何ArkTS值,包括基本类型(如数字、字符串、布尔值)和复杂对象类型(如数组、函数、对象等)。 12 13napi_value的生命周期与其在ArkTS中的对应值的生命周期紧密相关。当ArkTS值被垃圾回收时,与之关联的napi_value也将不再有效。重要的是不要在ArkTS值不再存在时尝试使用napi_value。 14 15框架层的scope通常用于管理napi_value的生命周期。在Node-API中,可以使用napi_open_handle_scope和napi_close_handle_scope函数来创建和销毁scope。通过在scope内创建napi_value,可以确保在scope结束时自动释放napi_value,避免内存泄漏。 16 17napi_ref是一个Node-API类型,用于管理napi_value的生命周期。napi_ref允许您在napi_value的生命周期内保持对其的引用,即使它已经超出了其原始上下文的范围。这使得您可以在不同的上下文中共享napi_value,并确保在不再需要时正确释放其内存。 18 19## 基本概念 20 21Node-API提供了一组功能,使开发人员能够在Node-API模块中创建和操作ArkTS对象,管理引用和生命周期,并注册垃圾回收回调函数等。下面是一些基本概念: 22 23- **作用域**:用于管理ArkTS对象的生命周期。在某个作用域中创建的对象句柄,默认情况下只能在该作用域内使用。当作用域被关闭后,其中创建的对象将无法再被访问,除非显式地将它们逃逸出当前作用域。 24- **引用管理**:Node-API提供函数来创建、删除和管理对象的引用,以延长对象的生命周期,避免出现对象use-after-free的问题。同时也通过引用管理去避免发生内存泄漏的问题。 25- **可逃逸的作用域**:允许在创建的作用域中声明的对象返回到父作用域,通过napi_open_escapable_handle_scope和napi_close_escapable_handle_scope进行管理。 26- **垃圾回收回调**:允许注册回调函数,以便在ArkTS对象被垃圾回收时执行特定的清理操作。 27 28这些基本概念使开发人员能够在Node-API模块中安全且有效地操作ArkTS对象,并确保正确管理对象的生命周期。 29 30## 场景和功能介绍 31 32以下Node-API接口主要用于ArkTS对象的引用管理,并确保在Node-API模块代码中正确地处理ArkTS对象的生命周期。使用场景如下: 33| 接口 | 描述 | 34| -------- | -------- | 35| napi_open_handle_scope、napi_close_handle_scope | 主要用于管理ArkTS对象的生命周期,确保在Node-API模块代码中使用ArkTS对象时能够正确地进行内存管理。当在Node-API模块中处理ArkTS对象时,需要创建一个临时的作用域来存储对象的引用,以便在执行期间正确访问这些对象,并在执行结束后关闭这个handle scope。 | 36| napi_open_escapable_handle_scope、napi_close_escapable_handle_scope | 用于创建一个可逃逸的作用域,使得在原生函数中创建的ArkTS对象可以被正确返回到调用该函数的外部ArkTS环境中。 | 37| napi_escape_handle | 将ArkTS对象的生命周期提升到父作用域中,避免对象被意外释放。 | 38| napi_create_reference、napi_delete_reference | 主要用于在Node-API模块代码中管理ArkTS对象的引用,以确保对象的生命周期符合插件的需求。 | 39| napi_reference_ref、napi_reference_unref | 主要用于管理ArkTS对象引用的引用计数,以确保在多个地方共享引用时引用计数能够正确地增加和减少。 | 40| napi_get_reference_value | 主要用于在Node-API模块代码中获取与引用相关联的ArkTS对象,以便在Node-API模块中对其进行操作。 | 41| napi_add_finalizer | 在需要在ArkTS对象被垃圾回收前执行一些清理或释放资源的情况下,确保资源的正确释放和管理。 | 42 43## 使用示例 44 45Node-API接口开发流程参考[使用Node-API实现跨语言交互开发流程](use-napi-process.md),本文仅对接口对应C++及ArkTS相关代码进行展示。 46 47### napi_open_handle_scope、napi_close_handle_scope 48 49通过接口napi_open_handle_scope创建一个上下文环境,并使用napi_close_handle_scope进行关闭。这组接口用于管理ArkTS对象的生命周期,确保在Node-API模块代码处理ArkTS对象时能够正确地管理其句柄,以避免出现对象错误回收的问题。 50需要注意的是合理使用napi_open_handle_scope和napi_close_handle_scope管理napi_value的生命周期,做到生命周期最小化,避免发生内存泄漏问题。 51 52代码部分也可参考下面链接: 53[生命周期管理](napi-guidelines.md#生命周期管理) 54 55cpp部分代码 56 57```cpp 58#include "napi/native_api.h" 59 60static napi_value HandleScopeTest(napi_env env, napi_callback_info info) 61{ 62 // 通过调用napi_open_handle_scope来创建一个句柄作用域 63 napi_handle_scope scope; 64 napi_open_handle_scope(env, &scope); 65 // 在句柄作用域内创建一个obj 66 napi_value obj = nullptr; 67 napi_create_object(env, &obj); 68 // 在对象中添加属性 69 napi_value value = nullptr; 70 napi_create_string_utf8(env, "handleScope", NAPI_AUTO_LENGTH, &value); 71 napi_set_named_property(env, obj, "key", value); 72 // 在作用域内获取obj的属性并返回 73 napi_value result = nullptr; 74 napi_get_named_property(env, obj, "key", &result); 75 // 关闭句柄作用域,自动释放在该作用域内创建的对象句柄 76 napi_close_handle_scope(env, scope); 77 // 此处的result能够得到值“handleScope” 78 return result; 79} 80 81static napi_value HandleScope(napi_env env, napi_callback_info info) 82{ 83 // 通过调用napi_open_handle_scope来创建一个句柄作用域 84 napi_handle_scope scope; 85 napi_open_handle_scope(env, &scope); 86 // 在句柄作用域内创建一个obj 87 napi_value obj = nullptr; 88 napi_create_object(env, &obj); 89 // 在对象中添加属性 90 napi_value value = nullptr; 91 napi_create_string_utf8(env, "handleScope", NAPI_AUTO_LENGTH, &value); 92 napi_set_named_property(env, obj, "key", value); 93 // 关闭句柄作用域,自动释放在该作用域内创建的对象句柄 94 napi_close_handle_scope(env, scope); 95 // 在作用域外获取obj的属性并返回,此处只能得到“undefined” 96 napi_value result = nullptr; 97 napi_get_named_property(env, obj, "key", &result); 98 return result; 99} 100``` 101<!-- @[napi_open_close_handle_scope](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/cpp/napi_init.cpp) --> 102 103接口声明 104 105```ts 106// index.d.ts 107export const handleScopeTest: () => string; 108export const handleScope: () => string; 109``` 110<!-- @[napi_open_close_handle_scope_api](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/cpp/types/libentry/Index.d.ts) --> 111 112ArkTS侧示例代码 113 114```ts 115import { hilog } from '@kit.PerformanceAnalysisKit'; 116import testNapi from 'libentry.so'; 117try { 118 hilog.info(0x0000, 'testTag', 'Test Node-API handleScopeTest: %{public}s', testNapi.handleScopeTest()); 119 hilog.info(0x0000, 'testTag', 'Test Node-API handleScope: %{public}s', testNapi.handleScope()); 120} catch (error) { 121 hilog.error(0x0000, 'testTag', 'Test Node-API handleScopeTest errorCode: %{public}s, errorMessage: %{public}s', error.code, error.message); 122} 123``` 124<!-- @[ark_napi_open_close_handle_scope](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/ets/pages/Index.ets) --> 125 126### napi_open_escapable_handle_scope、napi_close_escapable_handle_scope、napi_escape_handle 127 128通过接口napi_open_escapable_handle_scope创建出一个可逃逸的handle scope,可将范围内声明的值返回到父作用域。该作用域需要使用napi_close_escapable_handle_scope进行关闭。napi_escape_handle用于提升传入的ArkTS对象的生命周期到其父作用域。 129通过上述接口可以更灵活的使用管理传入的ArkTS对象,特别是在处理跨作用域的值传递时非常有用。 130 131cpp部分代码 132 133```cpp 134#include "napi/native_api.h" 135 136static napi_value EscapableHandleScopeTest(napi_env env, napi_callback_info info) 137{ 138 // 创建一个可逃逸的句柄作用域 139 napi_escapable_handle_scope scope; 140 napi_open_escapable_handle_scope(env, &scope); 141 // 在可逃逸的句柄作用域内创建一个obj 142 napi_value obj = nullptr; 143 napi_create_object(env, &obj); 144 // 在对象中添加属性 145 napi_value value = nullptr; 146 napi_create_string_utf8(env, "Test napi_escapable_handle_scope", NAPI_AUTO_LENGTH, &value); 147 napi_set_named_property(env, obj, "key", value); 148 // 调用napi_escape_handle将对象逃逸到作用域之外 149 napi_value escapedObj = nullptr; 150 napi_escape_handle(env, scope, obj, &escapedObj); 151 // 关闭可逃逸的句柄作用域,清理资源 152 napi_close_escapable_handle_scope(env, scope); 153 // 在获取逃逸后的obj:escapedObj的属性并返回,此处也能够得到“napi_escapable_handle_scope” 154 napi_value result = nullptr; 155 // 为了验证逃逸的实现,可以在此处获取obj的属性,此处会得到“undefined” 156 napi_get_named_property(env, escapedObj, "key", &result); 157 return result; 158} 159``` 160<!-- @[napi_open_close_escapable_handle_scope](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/cpp/napi_init.cpp) --> 161 162接口声明 163 164```ts 165// index.d.ts 166export const escapableHandleScopeTest: () => string; 167``` 168<!-- @[napi_open_close_escapable_handle_scope_api](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/cpp/types/libentry/Index.d.ts) --> 169 170ArkTS侧示例代码 171 172```ts 173import { hilog } from '@kit.PerformanceAnalysisKit'; 174import testNapi from 'libentry.so'; 175try { 176 hilog.info(0x0000, 'testTag', 'Test Node-API EscapableHandleScopeTest: %{public}s', testNapi.escapableHandleScopeTest()); 177} catch (error) { 178 hilog.error(0x0000, 'testTag', 'Test Node-API EscapableHandleScopeTest errorCode: %{public}s, errorMessage: %{public}s', error.code, error.message); 179} 180``` 181<!-- @[ark_napi_open_close_escapable_handle_scope](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/ets/pages/Index.ets) --> 182 183### napi_create_reference、napi_delete_reference 184 185为Object创建一个reference,以延长其生命周期。调用者需要自己管理reference生命周期。可以调用napi_delete_reference删除传入的reference。 186 187### napi_reference_ref、napi_reference_unref 188 189增加/减少传入的reference的引用计数,并获取新的计数。 190 191### napi_get_reference_value 192 193获取与reference相关联的ArkTS Object。 194 195> **说明** 196> 197> 由于弱引用(引用计数为0的napi_ref)的释放与gc回收js对象并非同时发生。 198> 199> 因此可能在弱引用被释放前,js对象已经被回收。 200> 201> 这意味着你可能在napi_ref有效的情况下,通过本接口获取到一个空指针。 202 203### napi_add_finalizer 204 205当ArkTS Object中的对象被垃圾回收时调用注册的napi_add_finalizer回调。 206 207cpp部分代码 208 209```cpp 210// log.h用于C++中日志打印 211#include "hilog/log.h" 212#include "napi/native_api.h" 213// 创建一个指向napi_ref类型的指针,用于存储创建的引用。在调用napi_add_finalizer函数之前,你需要分配一个napi_ref类型的变量,并将其地址传递给result位置的参数 214napi_ref g_ref; 215 216void Finalizer(napi_env env, void *data, void *hint) 217{ 218 // 执行资源清理操作 219 OH_LOG_INFO(LOG_APP, "Node-API: Use terminators to release resources."); 220} 221 222static napi_value CreateReference(napi_env env, napi_callback_info info) 223{ 224 napi_value obj = nullptr; 225 napi_create_object(env, &obj); 226 napi_value value = nullptr; 227 napi_create_string_utf8(env, "CreateReference", NAPI_AUTO_LENGTH, &value); 228 // 将键值对添加到对象中 229 napi_set_named_property(env, obj, "key", value); 230 231 // 添加终结器 232 void *data = {}; 233 napi_add_finalizer(env, obj, data, Finalizer, nullptr, &g_ref); 234 // 增加传入引用的引用计数并返回生成的引用计数 235 uint32_t result = 0; 236 napi_reference_ref(env, g_ref, &result); 237 OH_LOG_INFO(LOG_APP, "napi_reference_ref, count = %{public}d.", result); 238 if (result != 2) { 239 // 若传入引用的引用计数未增加,则抛出错误 240 napi_throw_error(env, nullptr, "napi_reference_ref fail"); 241 return nullptr; 242 } 243 return obj; 244} 245 246static napi_value UseReference(napi_env env, napi_callback_info info) 247{ 248 napi_value obj = nullptr; 249 // 通过调用napi_get_reference_value获取引用的ArkTS对象 250 napi_status status = napi_get_reference_value(env, g_ref, &obj); 251 if (status != napi_ok) { 252 napi_throw_error(env, nullptr, "napi_get_reference_value fail"); 253 return nullptr; 254 } 255 // 将获取到的对象返回 256 return obj; 257} 258 259static napi_value DeleteReference(napi_env env, napi_callback_info info) 260{ 261 // 减少传入引用的引用计数并返回生成的引用计数 262 uint32_t result = 0; 263 napi_value count = nullptr; 264 napi_reference_unref(env, g_ref, &result); 265 OH_LOG_INFO(LOG_APP, "napi_reference_unref, count = %{public}d.", result); 266 if (result != 1) { 267 // 若传入引用的引用计数未减少,则抛出错误 268 napi_throw_error(env, nullptr, "napi_reference_unref fail"); 269 return nullptr; 270 } 271 // 通过调用napi_delete_reference删除对ArkTS对象的引用 272 napi_status status = napi_delete_reference(env, g_ref); 273 if (status != napi_ok) { 274 napi_throw_error(env, nullptr, "napi_delete_reference fail"); 275 return nullptr; 276 } 277 napi_value returnResult = nullptr; 278 napi_create_string_utf8(env, "napi_delete_reference success", NAPI_AUTO_LENGTH, &returnResult); 279 return returnResult; 280} 281``` 282<!-- @[napi_create_delete_reference](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/cpp/napi_init.cpp) --> 283 284接口声明 285 286```ts 287// index.d.ts 288export const createReference: () => Object | undefined; 289export const useReference: () => Object | undefined; 290export const deleteReference: () => string | undefined; 291``` 292<!-- @[napi_create_delete_reference_api](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/cpp/types/libentry/Index.d.ts) --> 293 294ArkTS侧示例代码 295 296```ts 297import { hilog } from '@kit.PerformanceAnalysisKit'; 298import testNapi from 'libentry.so'; 299try { 300 hilog.info(0x0000, 'testTag', 'Test Node-API createReference: %{public}s', JSON.stringify(testNapi.createReference())); 301 hilog.info(0x0000, 'testTag', 'Test Node-API useReference: %{public}s', JSON.stringify(testNapi.useReference())); 302 hilog.info(0x0000, 'testTag', 'Test Node-API deleteReference: %{public}s', testNapi.deleteReference()); 303} catch (error) { 304 hilog.error(0x0000, 'testTag', 'Test Node-API ReferenceTest errorCode: %{public}s, errorMessage: %{public}s', error.code, error.message); 305} 306``` 307<!-- @[ark_napi_create_delete_reference](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPILifeCycle/entry/src/main/ets/pages/Index.ets) --> 308 309以上代码如果要在native cpp中打印日志,需在CMakeLists.txt文件中添加以下配置信息(并添加头文件:#include "hilog/log.h"): 310 311```text 312// CMakeLists.txt 313add_definitions( "-DLOG_DOMAIN=0xd0d0" ) 314add_definitions( "-DLOG_TAG=\"testTag\"" ) 315target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so) 316``` 317