• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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接口在进程退出时处理未释放资源,在Node-API模块注册清理钩子,一旦当前环境退出,这些钩子就会运行,使所有资源都被正确释放。
12
13## 基本概念
14
15Node-API提供了注册和取消注册清理钩子函数的功能,以下是相关概念:
16
17- **资源管理**:在ArkTS中,通常需要管理一些系统资源,比如内存、文件句柄、网络连接等。这些资源必须在Node-API模块的生命周期中正确地创建、使用和释放,以避免资源泄漏和程序崩溃。资源管理通常包括初始化资源、在合适的时候清理资源,以及在清理资源时执行必要的操作,比如关闭文件或断开网络连接。
18- **钩子函数(Hook)**:钩子函数是一种在特定事件或时间点自动执行的回调函数。在Node-API模块的上下文中,清理钩子函数通常用于在环境或进程退出时执行资源清理任务。这是因为环境或进程退出时,操作系统可能不会立即回收所有资源,因此需要通过清理钩子函数来确保所有资源都被正确释放。
19
20以上这些基本概念是理解和使用Node-API接口注册环境清理钩子的基础,下面将介绍具体的接口和使用示例。
21
22## 场景和功能介绍
23
24以下Node-API接口用于注册和取消不同类型的清理钩子。他们的使用场景如下:
25
26| 接口 | 描述 |
27| -------- | -------- |
28| napi_add_env_cleanup_hook | 注册一个环境清理钩子函数,该函数将在Node-API环境退出时被调用。 |
29| napi_remove_env_cleanup_hook | 取消之前注册的环境清理钩子函数,避免其在环境清理时执行。 |
30| napi_add_async_cleanup_hook | 注册一个异步清理钩子函数,该函数将在Node-API进程退出时异步执行。 |
31| napi_remove_async_cleanup_hook | 取消之前注册的异步清理钩子函数,确保在不需要时不会执行相关的清理工作。 |
32
33## 使用示例
34
35Node-API接口开发流程参考[使用Node-API实现跨语言交互开发流程](use-napi-process.md),本文仅对接口对应C++及ArkTS相关代码进行展示。
36
37### napi_add_env_cleanup_hook
38
39用于注册一个环境清理钩子函数,该函数将在环境退出时执行。这是确保资源在环境销毁前得到清理的重要机制。
40
41需要注意的是,napi_add_env_cleanup_hook接口并不支持对同一arg绑定多个回调。若出现env已销毁,但cleanup回调未被执行的情况,可以在启用ArkTS运行时[多线程检测](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-multi-thread-check)功能的前提下,查看hilog流水日志`AddCleanupHook Failed, data cannot register multiple times.`来查找发生注册失败的调用。
42
43### napi_remove_env_cleanup_hook
44
45用于取消之前注册的环境清理钩子函数。在某些情况下,需要在插件卸载或资源被重新分配时取消钩子函数。
46
47cpp部分代码
48
49```cpp
50#include <hilog/log.h>
51#include <string>
52#include "napi/native_api.h"
53// 定义内存结构,包含指向数据的指针和数据的大小
54typedef struct {
55    char *data;
56    size_t size;
57} Memory;
58// 外部缓冲区清理回调函数,用于释放分配的内存
59void ExternalFinalize(napi_env env, void *finalize_data, void *finalize_hint)
60{
61    Memory *wrapper = (Memory *)finalize_hint;
62    free(wrapper->data);
63    free(wrapper);
64    OH_LOG_INFO(LOG_APP, "Node-API napi_add_env_cleanup_hook ExternalFinalize");
65}
66// 在环境关闭时执行一些清理操作,如清理全局变量或其他需要在环境关闭时处理的资源
67static void Cleanup(void *arg)
68{
69    // 执行清理操作
70    OH_LOG_INFO(LOG_APP, "Node-API napi_add_env_cleanup_hook cleanuped: %{public}d", *(int *)(arg));
71}
72// 创建外部缓冲区并注册环境清理钩子函数
73static napi_value NapiEnvCleanUpHook(napi_env env, napi_callback_info info)
74{
75    // 分配内存并复制字符串数据到内存中
76    std::string str("Hello from Node-API!");
77    Memory *wrapper = (Memory *)malloc(sizeof(Memory));
78    if (wrapper == nullptr) {
79        OH_LOG_ERROR(LOG_APP, "malloc for wrapper failed");
80        return nullptr;
81    }
82    wrapper->data = static_cast<char *>(malloc(str.size() + 1));
83    if (wrapper->data == nullptr) {
84        free(wrapper);
85        OH_LOG_ERROR(LOG_APP, "malloc for wrapper->data failed");
86        return nullptr;
87    }
88    memset(wrapper->data, 0, str.size() + 1);
89    strcpy(wrapper->data, str.c_str());
90    wrapper->size = str.size();
91    // 创建外部缓冲区对象,并指定清理回调函数
92    // 注意:wrapper->data 的内存释放依赖于 ExternalFinalize 回调,只有 buffer 被正确持有并最终被 GC 回收时,ExternalFinalize 才会被调用,否则会导致内存泄漏。
93    napi_value buffer = nullptr;
94    napi_status status = napi_create_external_buffer(env, wrapper->size, (void *)wrapper->data, ExternalFinalize, wrapper, &buffer);
95    if (status != napi_ok) {
96        // 创建失败时需主动释放内存,避免泄漏
97        free(wrapper->data);
98        free(wrapper);
99        OH_LOG_ERROR(LOG_APP, "napi_create_external_buffer failed");
100        return nullptr;
101    }
102    // 静态变量作为钩子函数参数
103    static int hookArg = 42;
104    static int hookParameter = 1;
105    // 注册环境清理钩子函数
106    status = napi_add_env_cleanup_hook(env, Cleanup, &hookArg);
107    if (status != napi_ok) {
108        OH_LOG_ERROR(LOG_APP, "Test Node-API napi_add_env_cleanup_hook failed.");
109        return nullptr;
110    }
111    // 注册环境清理钩子函数,此处不移除环境清理钩子,为了在Java环境被销毁时,这个钩子函数被调用,用来模拟执行一些清理操作,例如释放资源、关闭文件等。
112    status = napi_add_env_cleanup_hook(env, Cleanup, &hookParameter);
113    if (status != napi_ok) {
114        OH_LOG_ERROR(LOG_APP, "Test Node-API napi_add_env_cleanup_hook failed.");
115        return nullptr;
116    }
117    // 立即移除环境清理钩子函数,确保不会在后续环境清理时被调用
118    // 通常,当为其添加此钩子的资源无论如何都被拆除时调用这个接口
119    napi_remove_env_cleanup_hook(env, Cleanup, &hookArg);
120    napi_remove_env_cleanup_hook(env, Cleanup, &hookParameter);
121    // 返回创建的外部缓冲区对象
122    return buffer;
123}
124```
125<!-- @[napi_remove_add_env_cleanup_hook](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/cpp/napi_init.cpp) -->
126
127接口声明
128
129```ts
130// index.d.ts
131export const napiEnvCleanUpHook: () => Object | undefined;
132```
133<!-- @[napi_remove_add_env_cleanup_hook_api](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/cpp/types/libentry/Index.d.ts) -->
134
135ArkTS侧示例代码
136
137```ts
138// index.ets
139import { hilog } from '@kit.PerformanceAnalysisKit';
140import worker from '@ohos.worker';
141
142let wk = new worker.ThreadWorker("entry/ets/workers/worker.ts");
143// 发送消息到worker线程
144wk.postMessage("test NapiEnvCleanUpHook");
145// 处理来自worker线程的消息
146wk.onmessage = (message) => {
147  hilog.info(0x0000, 'testTag', 'Test Node-API message from worker: %{public}s', JSON.stringify(message));
148  wk.terminate();
149};
150```
151<!-- @[connect_with_worker](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/ets/pages/Index.ets) -->
152
153```ts
154// worker.ts
155import { hilog } from '@kit.PerformanceAnalysisKit';
156import worker from '@ohos.worker';
157import testNapi from 'libentry.so';
158
159let parent = worker.workerPort;
160// 处理来自主线程的消息
161parent.onmessage = (message) => {
162  hilog.info(0x0000, 'testTag', 'Test Node-API message from main thread: %{public}s', JSON.stringify(message));
163  // 发送消息到主线程
164  parent.postMessage('Test Node-API worker:' + testNapi.napiEnvCleanUpHook());
165};
166```
167<!-- @[connect_with_main_thread](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/ets/workers/worker.ts) -->
168
169worker相关开发配置和流程参考以下链接:
170[使用Worker进行线程间通信](../arkts-utils/worker-introduction.md)
171
172### napi_add_async_cleanup_hook
173
174这个接口用于注册一个异步清理钩子函数,该函数将在环境退出时异步执行。与同步钩子不同,异步钩子允许在进程退出时进行更长时间的操作,而不会阻塞进程退出。
175
176### napi_remove_async_cleanup_hook
177
178这个接口用于取消之前注册的异步清理钩子函数。与取消同步钩子类似,这通常是在不再需要钩子函数时进行的操作。
179
180cpp部分代码
181
182```cpp
183#include <cstdlib>
184#include <string.h>
185#include "napi/native_api.h"
186#include "uv.h"
187
188// 包含异步操作内容
189typedef struct {
190    napi_env env;
191    void *testData;
192    uv_async_s asyncUv;
193    napi_async_cleanup_hook_handle cleanupHandle;
194} AsyncContent;
195// 删除异步工作对象并注销钩子函数
196static void FinalizeWork(uv_handle_s *handle)
197{
198    AsyncContent *asyncData = reinterpret_cast<AsyncContent *>(handle->data);
199    // 不再需要异步清理钩子函数的情况下,尝试将其从环境中移除
200    napi_status result = napi_remove_async_cleanup_hook(asyncData->cleanupHandle);
201    if (result != napi_ok) {
202        napi_throw_error(asyncData->env, nullptr, "Test Node-API napi_remove_async_cleanup_hook failed");
203    }
204    // 释放AsyncContent
205    free(asyncData);
206}
207// 异步执行环境清理工作
208static void AsyncWork(uv_async_s *async)
209{
210    // 执行一些清理工作,比如释放动态分配的内存
211    AsyncContent *asyncData = reinterpret_cast<AsyncContent *>(async->data);
212
213    if (asyncData != nullptr && asyncData->testData != nullptr) {
214        free(asyncData->testData);
215        asyncData->testData = nullptr;
216    }
217    // 关闭libuv句柄,并触发FinalizeWork回调清理
218    uv_close((uv_handle_s *)async, FinalizeWork);
219}
220// 异步清理钩子函数,创建异步工作对象并执行
221static void AsyncCleanup(napi_async_cleanup_hook_handle handle, void *info)
222{
223    AsyncContent *data = reinterpret_cast<AsyncContent *>(info);
224    // 获取libUv循环实例并初始化一个异步句柄,以便后续执行异步工作
225    uv_loop_s *uvLoop;
226    napi_get_uv_event_loop(data->env, &uvLoop);
227    uv_async_init(uvLoop, &data->asyncUv, AsyncWork);
228
229    data->asyncUv.data = data;
230    data->cleanupHandle = handle;
231    // 发送异步信号触发AsyncWork函数执行清理工作
232    uv_async_send(&data->asyncUv);
233}
234
235static napi_value NapiAsyncCleanUpHook(napi_env env, napi_callback_info info)
236{
237    // 分配AsyncContent内存
238    AsyncContent *data = reinterpret_cast<AsyncContent *>(malloc(sizeof(AsyncContent)));
239    if (data == nullptr) {
240        napi_throw_error(env, nullptr, "Test Node-API malloc AsyncContent failed");
241        return nullptr;
242    }
243    data->env = env;
244    data->cleanupHandle = nullptr;
245    // 分配内存并复制字符串数据
246    const char *testDataStr = "TestNapiAsyncCleanUpHook";
247    data->testData = strdup(testDataStr);
248    if (data->testData == nullptr) {
249        free(data);
250        napi_throw_error(env, nullptr, "Test Node-API data->testData is nullptr");
251        return nullptr;
252    }
253    // 添加异步清理钩子函数
254    napi_status status = napi_add_async_cleanup_hook(env, AsyncCleanup, data, &data->cleanupHandle);
255    if (status != napi_ok) {
256        free(data->testData);
257        free(data);
258        napi_throw_error(env, nullptr, "Test Node-API napi_add_async_cleanup_hook failed");
259        return nullptr;
260    }
261    napi_value result = nullptr;
262    napi_get_boolean(env, true, &result);
263    return result;
264}
265```
266<!-- @[napi_add_remove_async_cleanup_hook](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/cpp/napi_init.cpp) -->
267
268由于需要包含“uv.h”库,所以需要在CMakeLists文件中添加配置:
269```text
270// CMakeLists.txt
271target_link_libraries(entry PUBLIC libace_napi.z.so libuv.so)
272```
273
274接口声明
275
276```ts
277// index.d.ts
278export const napiAsyncCleanUpHook: () => boolean | undefined;
279```
280<!-- @[napi_remove_add_env_cleanup_hook_api](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/cpp/types/libentry/Index.d.ts) -->
281
282ArkTS侧示例代码
283
284```ts
285import { hilog } from '@kit.PerformanceAnalysisKit';
286import testNapi from 'libentry.so';
287try {
288  hilog.info(0x0000, 'testTag', 'Test Node-API napi_add_async_cleanup_hook: %{public}s', testNapi.napiAsyncCleanUpHook());
289} catch (error) {
290  hilog.error(0x0000, 'testTag', 'Test Node-API napi_add_async_cleanup_hook error.message: %{public}s', error.message);
291}
292```
293<!-- @[ark_napi_remove_add_env_cleanup_hook](https://gitcode.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/NodeAPI/NodeAPIUse/NodeAPICleanuphook/entry/src/main/ets/pages/Index.ets) -->
294
295以上代码如果要在native cpp中打印日志,需在CMakeLists.txt文件中添加以下配置信息(并添加头文件:#include "hilog/log.h"):
296
297```text
298// CMakeLists.txt
299add_definitions( "-DLOG_DOMAIN=0xd0d0" )
300add_definitions( "-DLOG_TAG=\"testTag\"" )
301target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so)
302```
303