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