【鸿蒙优选三方库】@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))

四、为什么值得选它?

  1. 303 个 Issue 已解答:跨线程、空指针、符号表等 Native 核心问题都有记录。
  2. 代码量减半:相比裸写 NAPI,同样功能代码量减少 50%+。
  3. 异步模型完整:Promise / AsyncWorker / TaskRunner 覆盖所有并发场景。
  4. 维护者响应明确:像 #310 这类问题,维护者会直接说明底层机制与正确用法。
  5. 混合开发友好:已有 NAPI 代码可与 AKI 共存,渐进式迁移。

完整 Issue 列表见 仓库 Issues

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐