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