【鸿蒙优选三方库】@ohos/aki:从 Issue 看 ArkTS FFI 框架的跨线程与崩溃治理
【鸿蒙优选三方库】@ohos/aki:从 Issue 看 ArkTS FFI 框架的跨线程与崩溃治理
303 个已关闭 Issue,是一部 Native 跨语言开发的踩坑史。
@ohos/aki用极简语法糖让 ArkTS 调 C/C++ 像调本地方法,但跨线程的aki::Value、release 包的符号表、空指针兼容——这些 Native 特有的坑,Issue 里都有答案。
📦 仓库地址:https://gitcode.com/CPF-ApplicationTPC/aki | 安装:
ohpm install @ohos/aki
一、库的核心能力
| 特性 | 说明 |
|---|---|
| 极简语法糖 | JSBIND_FUNCTION 一行绑定全局函数 |
| 类绑定 | JSBIND_CLASS + JSBIND_METHOD 绑定 C++ 类 |
| 自动类型转换 | 基本类型、字符串、ArrayBuffer、对象、回调 |
| Promise 桥接 | aki::Promise 返回 Promise,支持 Then/Catch(#306) |
| AsyncWorker | 耗时任务不阻塞 UI |
| TaskRunner | 跨线程调度 |
| 线程安全函数 | 多线程回调 ArkTS |
aki::Value | 通用 JS 值包装 |
Persistent | 跨线程持有 JS 引用防 GC |
| 混合开发 | 与现有 NAPI 代码共存 |
二、社区实战:典型 Issue 与避坑指南
1. aki::Value 跨线程使用出现问题(#310)
问题:升级到 master 最新版后,aki::Value 跨线程使用触发多线程检测报错,而 1.2.25 版本没有这个问题。
原因与结论:1.2.25 用的是全局线程本地 env,跨线程访问时绕过了系统的多线程检查;新版改为存储创建线程的 env,跨线程使用就会触发检测。维护者明确回复——napi_value 在哪个线程产生就只能在该线程使用,旧版能跑通其实是 AKI 的漏洞,实际运行存在稳定性隐患(只是概率极小)。
避坑建议:aki::Value 包装的是 JS 对象,而 JS 对象是线程绑定的——在 A 线程创建的 Value 拿到 B 线程用,就是跨线程访问 JS 堆,会出问题。
// ❌ 危险:把 aki::Value 跨线程传递
JSBIND_FUNCTION(processAsync) {
aki::Value jsObj = args[0];
std::thread([jsObj]() { // jsObj 被带到子线程
auto data = jsObj.As<int>(); // ❌ 跨线程访问 JS 堆
}).detach();
}
// ✅ 安全:在 JS 线程取出数据,只把纯 C++ 数据带到子线程
JSBIND_FUNCTION(processAsync) {
int data = args[0].As<int>(); // 在 JS 线程完成转换
aki::Promise promise;
std::thread([data, promise]() mutable {
int result = heavyCompute(data); // 纯 C++ 计算
promise.Resolve(result); // Promise 内部处理线程安全
}).detach();
return promise;
}
// ✅ 需要跨线程持有 JS 引用时,用 Persistent
aki::Persistent persistent(jsObj); // 正确的跨线程持有方式
核心原则:跨线程传数据,不传 aki::Value。在 JS 线程把数据取成纯 C++ 类型,再带到子线程。
2. release 包 crash 无法定位,缺符号表(#316)
问题:线上包是 release 编译的,出现 crash 后无法定位,因为没有对应可查的符号表来回溯堆栈。
解决:仓库已提出符号表备份需求,用于回溯崩溃堆栈。
避坑建议:这是 Native 开发的经典痛点。发版时必须自己归档符号表:
# CMake 构建时保留符号信息
# 在 CMakeLists.txt 中,release 构建也要生成 .so.debug 或保留 unstripped 版本
# 归档产物(每次发版都做)
cp build/default/intermediates/libs/default/arm64-v8a/libhello.so \
symbols/arm64-v8a/libhello.so.$(git rev-parse HEAD)
崩溃后用符号表还原堆栈:
# 用 llvm-symbolizer 或 addr2line 还原
llvm-symbolizer --obj=libhello.so --functions --demangle 0x12345
3. 空指针崩溃兼容(#307)
问题:用户遇到空指针崩溃,询问修复进展与发版时间。
解决:master 分支已对空指针场景做兼容处理,避免因传入空值直接崩溃,并随新版本发布。
避坑建议:即便如此,C++ 侧仍应做空值校验——框架兼容是兜底,不是免责:
JSBIND_FUNCTION(safeProcess) {
// ✅ 主动校验,不依赖框架兜底
if (args[0].IsUndefined() || args[0].IsNull()) {
return aki::Value(); // 返回空值而非崩溃
}
auto ptr = args[0].As<MyClass*>();
if (!ptr) return aki::Value();
return ptr->DoSomething();
}
4. aki::Promise 支持 Then/Catch(#306)
问题:C++ 侧调用 JS 的异步函数后,无法处理其返回的 Promise。
解决:aki::Promise 增加 Then / Catch 支持,C++ 侧可以等待 JS 的异步结果,实现真正的双向异步互调:
JSBIND_FUNCTION(callJsAsync) {
// C++ 调用 JS 的异步函数,并处理其 Promise
aki::Promise jsPromise = CallJsFunctionReturningPromise();
jsPromise.Then([](aki::Value result) {
// JS Promise resolve 后回到这里
}).Catch([](aki::Value error) {
// JS Promise reject 后回到这里
});
}
三、快速上手
ohpm install @ohos/aki
#include <aki/jsbind.h>
JSBIND_FUNCTION(add) {
return args[0].As<int>() + args[1].As<int>();
}
JSBIND_ADDON(add)
import { add } from 'libentry.so'
console.info('3 + 5 = ' + add(3, 5))
四、为什么值得选它?
- 303 个 Issue 已解答:跨线程、空指针、符号表等 Native 核心问题都有记录。
- 代码量减半:相比裸写 NAPI,同样功能代码量减少 50%+。
- 异步模型完整:Promise / AsyncWorker / TaskRunner 覆盖所有并发场景。
- 维护者响应明确:像 #310 这类问题,维护者会直接说明底层机制与正确用法。
- 混合开发友好:已有 NAPI 代码可与 AKI 共存,渐进式迁移。
完整 Issue 列表见 仓库 Issues。
更多推荐



所有评论(0)