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