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