开发者技术支持-NAPI 常见问题实践总结
一、问题说明
NAPI使用过程中主要面临六类核心问题:
-
调用失败与崩溃:应用调用NAPI接口时出现致命错误(如Fatal: ecma_vm cannot run in multi-thread)或直接崩溃
-
执行结果异常:接口执行结果与预期不符,控制台打印"occur exception need return"等异常日志
-
内存泄漏:应用内存持续增长,特别是在使用多线程功能时
-
模块加载失败:ArkTS侧import模块后得到undefined或not callable错误
-
JS线程卡死:界面无响应,JS线程阻塞导致应用无法操作
-
数据传递异常:ArkTS与C++间传递字符串、Buffer等数据时出现内容丢失或创建失败
二、原因分析
这些问题主要源于四个方面的根源:
-
线程上下文误用:在非JS主线程中调用线程敏感的NAPI接口(如napi_call_function)
资源生命周期管理缺失:
-
创建napi_threadsafe_function后未调用napi_delete_threadsafe_function释放
-
异步传递napi_create_external_arraybuffer内存时,Native内存过早释放
接口使用不规范:
-
参数传递错误(数量、类型不匹配)
-
忽略异常处理(未使用napi_get_and_clear_last_exception)
-
超过接口数据限制(如napi_create_buffer_copy的2MB限制)
模块配置错误:
-
模块注册名称(nm_modname)与so文件名不一致
-
CMakeLists.txt未正确包含源文件或依赖库
-
模块存放路径与系统加载路径不匹配
三、解决思路
针对上述问题需要采取系统性解决方案:
严格遵守线程安全规范:
-
JS对象操作必须在主线程完成
-
多线程通信使用napi_threadsafe_function派发到主线程
完善生命周期管理:
-
遵循"谁创建谁释放"原则,及时调用napi_delete_*接口
-
确保异步共享内存的生命周期长于ArkTS使用时间
规范接口使用与异常处理:
-
调用前检查参数数量和类型
-
使用napi_get_and_clear_last_exception清除异常或抛到ArkTS层
-
传输大数据时使用napi_create_arraybuffer替代有限制接口
系统化模块问题排查:
-
确保模块名、so文件名、import语句三者完全一致
-
通过hilog搜索"dlopen"和"Fatal"关键字定位加载失败原因
-
复查CMakeLists.txt确保正确包含所有依赖项
四、解决方案
HarmonyOS Node-API 是基于 Node.js 12.x LTS 的 Node-API 规范扩展开发的机制,为开发者提供了 ArkTS/JS 与 C/C++ 模块之间的交互能力。在使用过程中,开发者可能会遇到各种问题,以下是对一些常见问题的实践总结。
1、NAPI 调用失败
场景一:跨线程使用错误
在进行 NAPI 开发时,跨线程使用不当是一个常见的问题。例如,通过 napi_call_function 调用 ArkTS 函数时,如果在非主线程中进行,就会出现问题。因为 napi_call_function 需要在主线程(即 js 线程)执行,且参数 env 信息也是主线程的信息,不能跨线程使用。常见报错信息如:Fatal: ecma_vm cannot run in multi-thread。
排查方法:
-
通过 hilog 日志检索关键字 “Fatal”,分析错误日志判断报错类型。
-
排查异步调用流程,确保不能通过 napi_call_function 在非主线程调用 ArkTS 函数。
解决方案:
回调函数必须运行在 js 的主线程中,其他线程发起调用会抛出异常,可以参考线程安全函数。异步调用需要在主线程中进行。使用 napi_call_function 方法在 Node-API 模块中对 ArkTS 侧函数进行调用时,确保传入的 argv 的长度必须大于等于 argc 声明的数量,且被初始化成 nullptr。
场景二:函数调用错误
函数调用错误通常涉及到参数传递、函数导出以及回调函数实现等方面的问题。
排查方法:
-
排查 ArkTS 侧调用 Native 侧函数时的参数传递,确保传递的参数类型和数量与函数定义一致。
-
排查 ArkTS 侧被调用的函数是否使用 export 关键字导出。
-
排查 Native 侧回调函数实现,确保在 ArkTS 端注册的回调函数实现正确,并且在需要时能够正确调用。可以使用 napi_get_cb_info 接口获取有关函数调用的参数信息和 this 指针,确保参数正确。
解决方案:
调用 ArkTS 侧函数时,ArkTS 侧函数需要使用 export 关键字导出。确保在调用 NAPI 函数时,传递的参数正确无误。
场景三:文件引用错误
文件引用错误可能是由于 CMakeLists.txt 脚本中遗漏了编译所需的源代码、头文件以及三方库等。
排查方法:仔细检查 CMakeLists.txt 脚本,确认是否包含了所有必要的文件和库。
解决方案:在 CMakeLists.txt 脚本中添加遗漏的文件和库,确保编译过程能够正确引用所需资源。
2、接口执行结果非预期
部分 Node-API 接口在调用结束前会进行检查,检查虚拟机中是否存在 JS 异常。如果存在异常,则会打印出 occur exception need return 日志,并打印出检查点所在的行号,以及对应的 Node-API 接口名称。
解决方案:
-
若该异常开发者不关心,可以选择直接清除。可直接使用 napi 接口 napi_get_and_clear_last_exception,清理异常。调用时机:在打印 occur exception need return 日志的接口之前调用。
-
将该异常继续向上抛到 ArkTS 层,在 ArkTS 层进行捕获。发生异常时,可以选择走异常分支,确保不再走多余的 Native 逻辑,直接返回到 ArkTS 层。
3、napi_threadsafe_function 内存泄漏
napi_threadsafe_function 内存泄漏是一个需要关注的问题。在使用 napi_threadsafe_function 时,如果没有正确管理其生命周期,可能会导致内存泄漏。
排查方法:检查代码中 napi_threadsafe_function 的创建和释放逻辑,确保在不再使用时及时释放相关资源。
解决方案:遵循 napi_threadsafe_function 的使用规范,在合适的时机调用相应的释放函数,避免内存泄漏。例如,在使用完 napi_threadsafe_function 后,调用 napi_delete_threadsafe_function 释放资源。
4、ArkTS/JS 侧 import 报错
ArkTS/JS 侧 import xxx from libxxx.so 后,使用 xxx 报错显示 undefined/not callable 或明确的 Error message,可能由以下原因导致。
原因一:模块名称不匹配
排查.cpp 文件在注册模块时的模块名称与 so 的名称是否匹配一致。如模块名为 entry,则 so 的名字为 libentry.so,napi_module 中 nm_modname 字段应为 entry,大小写与模块名保持一致。
原因二:so 加载失败
-
应用启动时过滤模块加载相关日志,重点搜索 "dlopen" 关键字,确认是否有相关报错信息。常见加载失败原因有权限不足、so 文件不存在以及 so 已拉入黑名单等,可根据关键错误日志确认问题。其中,多线程场景 (worker、taskpool 等) 下优先检查模块实现中 nm_modname 是否与模块名一致,区分大小写。
-
确定所依赖的其它 so 是否打包到应用中以及是否有权限打开。常见加载失败原因有权限不足、so 文件不存在等,可根据关键错误日志确认问题。
原因三:模块导入方式与 so 路径不对应
若 JS 侧导入模块的形式为:import xxx from '@ohos.yyy.zzz',则该 so 将在 /system/lib/module/yyy 中找 libzzz.z.so 或 libzzz_napi.z.so,若 so 不存在或名称无法对应,则报错日志中会出现 dlopen 相关日志。注意,32 位系统路径为 /system/lib,64 位系统路径为 /system/lib64。
5、NAPI JS 卡死
NAPI JS 卡死是指在使用 NAPI 时,JavaScript 代码出现无响应的情况。常见原因如下:
-
无限循环:如果在 NAPI 函数中有一个无限循环,它将导致 JavaScript 线程无法继续执行,从而使应用程序无响应。
-
阻塞调用:在 NAPI 函数中进行了耗时的操作,比如网络请求或文件操作,这可能会导致 JavaScript 线程阻塞,使应用程序无响应。
-
内存泄漏:在 NAPI 函数中没有正确释放资源或内存,可能会导致内存泄漏,最终导致应用程序卡死。
解决方案:
-
避免无限循环:在编写 NAPI 函数时,确保避免无限循环。如果必须要有循环,要确保在循环中加入一些条件,以便能够中断循环。
-
使用异步操作:如果需要进行耗时的操作,如网络请求或文件操作,可以考虑将其改为异步操作,以避免阻塞 JavaScript 线程。
-
释放资源和内存:在编写 NAPI 函数时,确保正确释放资源和内存,以避免内存泄漏。
6、ArkTS 与 Native C++ 间数据传递异常
场景一:ArkTS 向 C++ 传递数据
通过 napi_get_value_string_utf8 传递长 string 时,C++ 获取不到字符串内容。可能原因如下:
-
参数传入的 string 的内容是否为空。
-
string 的长度过长。
解决方案:传输长 string 时,建议以 Buffer 传递,通过 napi_get_buffer_info 来获取从 TS 层传来的 Buffer,再转成 string。
场景二:C++ 向 ArkTS 传递数据
-
通过 napi_create_external_arraybuffer 异步传递 Buffer,在 ArkTS 侧获取不到内容。原因可能是 external_arraybuffer 不会拷贝内存,而是复用 Node-API 模块内存块,通过异步回调方式传递数据时,若 C++ 侧数据释放了,ArkTS 将获取不到数据。
解决方案:使用 external_arraybuffer 复用 Node-API 内存时,确保在结果回调前内存不释放,或者使用线程安全函数。
-
通过 napi_create_buffer_copy 创建并复制数据到 Buffer 对象时报错。如 Creat failed, current size: 2.969184 MiB, limit size: 2.000000 MiB,原因是 napi_create_buffer_copy 最大支持 2M 数据(2097152 字节),超出报错。
解决方案:传递 buffer 数据控制数据在 2M 内,超出时,推荐使用 napi_create_arraybuffer 接口创建的 ArrayBuffer 对象,该接口没有数据大小限制。
-
通过 napi_create_typedarray 创建并赋值 Unicode 字符串数据时报错,如 C03F00/ArkCompiler com.examp...lication E RangeError: The newByteLength is out of range。原因是 Unicode 字符占用 2 字节,napi_create_typedarray 以类型 napi_int16_array 传递 Unicode 字符时,2*lengthch 超过数据长度时,出错。
解决方案:创建 ArrayBuffer 对象时,计算检查数据类型长度,避免数据内存越界。
在进行 NAPI 开发时,遇到问题需要仔细排查,根据不同的问题场景采取相应的解决方案。通过对常见问题的总结和分析,可以提高开发效率,减少开发过程中的错误。
- 点赞
- 收藏
- 关注作者
评论(0)