本文记录 duks 接入 OpenHarmony 的完整过程,覆盖状态管理库盘点、鸿蒙 kotlinx 制品线选型、真实引擎编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

duks 是基于 kotlinx.coroutines 的 Kotlin Multiplatform Redux 风格状态管理库:createStore 一行建店,dispatch / dispatchAsync 走单向数据流,reducer 纯函数演化状态,组合式 middleware(exceptionHandling / logging / caching / async / sagas / persistence)扩展行为,AsyncAction 自带 Processing/Result/Error/Complete 生命周期,Saga 状态机编排长事务(含失败补偿),StateStorage / SagaStorage 抽象落盘。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取")有本质区别:社区 Maven 上已经出现完整的 HarmonyOS kotlinx 制品线(coroutines 1.11.0-0.1.0-07、datetime 0.8.0-0.1.0-07、serialization 1.11.0-0.1.0-07,全部带 ohosArm64 / ohosX64 Gradle 变体),因此不需要提取内核——真实的引擎(KStore 状态容器、reducer 组合、middleware 链、Saga 状态机、持久化中间件)被原样编译成 OpenHarmony KLIB,一个带异步生命周期、Saga 补偿事务、POSIX 持久化的完整状态引擎运行在真机上,ArkUI 页面作为验收宿主。

项目地址: AtomGit/oh-tpc/duks.git

上游仓库: crowded-libs/duks

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配

duks 为 Kotlin Multiplatform 应用提供"状态如何演化"这一件事的完整答案:createStore(initialState) { middleware {…}; reduceWith(…) } 一行建店,State + Action + Reducer 组成可预测的单向数据流,多个 reducer 顺序折叠,Redux 式 middleware 从右向左包裹 dispatch 链;async() 中间件让 AsyncAction.getResult 的挂起结果自动变成生命周期动作流回 reducer;sagas() 让长事务拥有独立状态机(startsOn / on / SagaTransition.Continue/Complete/Fail),失败时可以带反向补偿 effects;persistence() 中间件在 store 创建时自动恢复、按策略落盘。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面共享。OpenHarmony 应用同样需要这套跨平台共享的业务状态层,但上游构建(Kotlin 2.4.10 / Gradle 9.6.1 / AGP 9.2.0)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明公共状态引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证状态引擎、coroutines 运行时、Saga 状态机、持久化文件层、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.4.10 / Gradle 9.6.1 / AGP 9.2.0(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建;旧钉法 Kotlin 2.2.21-1.0.0 读不了 2.4 元数据。
依赖矩阵引擎依赖 kotlinx-coroutines、kotlinx-datetime,测试依赖 coroutines-test / serialization-json,主依赖都要有 ohos 变体才能解析。
Compose 挂钩上游唯一的 Compose 集成 mapToPropsAsState 依赖 org.jetbrains.compose.runtime,鸿蒙暂无对应制品,必须隔离而不能编译失败。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
异步语义跨不了桥dispatchAsync 只等父 action,async/saga 的嵌套 dispatch 是 Kotlin 侧概念,C ABI 只能同步一来一回。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:真实引擎的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。状态语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

Demo 提供一个真实的状态管理会话,对应桥接的全部入口:

  • 计数器 / 消息(dispatch + reducer):store.dispatchAsync(Increment) 走完整 middleware 链到达 reducer,counter / message 字段由纯函数演化;
  • 异步加载(AsyncAction 生命周期):load-user 触发 AsyncAction<DemoUser>.getResult(600ms 模拟网络),async() 中间件自动发射 AsyncProcessing(isLoading=true)→ AsyncResultAction / AsyncError → AsyncComplete,可注入失败观察错误通道;
  • Saga 工作流(含补偿):place-order 启动订单 saga:reserve → charge → ship 三步链式推进;failAt 指定失败点时,saga 先对已完成步骤逐个反向补偿(compensate1:reserve…)再 SagaTransition.Fail;
  • 持久化闭环:persistence() 中间件 + POSIX FileStateStorage 按 OnEveryChange 落盘;reset-store(close 并释放 store)→ 重新 init 后 counter/message 恢复——证明是落盘而不是内存缓存;
  • store 生命周期:reset-store 走 KStore.close()(lifecycle 中间件 teardown、store job 取消),再 init 重建;
  • 版本握手:{"cmd":"version"} 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
cmd回显本次命令。
state全量状态快照:counter / message / user / isLoading / lastError / orderStatus / sagaLog / ordersDone / ordersFailed / storeClosed。
storageinit 返回 "file" / "memory",标识持久化形态。

引擎侧完整能力(多 reducer 组合、exceptionHandling、缓存中间件、saga 持久化存储、版本化迁移存储)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 148 项 jvmTest 交叉验证用例,证明 ohos .so 里执行的状态逻辑没有漂移。

1.3 实现适配

维度要求
代码复用duks 模块全部 commonMain 文件原样编译,不提取、不重写;全仓零新增 Kotlin 源文件到库模块——唯一的源码动作是把上游 mapToPropsAsState(1 个 Compose 扩展函数)原样移动到未注册的 composeMain 源集。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 android / ios / wasmJs 平台源码留盘不进构建。
桥接稳定使用 1 个 C ABI(DuksCommand)和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律版本主体与上游完全一致,只把仓库换到 ohos 制品线(换线不换版),上游测试零改动全绿。
UI 完整单页覆盖全部桥命令:版本握手、状态快照、计数器、消息、异步加载、Saga 四路下单、生命周期重置与恢复、双日志区。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。duks 的引擎不碰 UI(唯一的 Compose 集成是 1 个扩展函数),适配面天然最小;ArkTS Demo 只是把桥暴露的状态闭环画出来。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 kotlinx ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、serialization 插件补位
第 3 阶段:桥协议建模     ── 命令/快照 JSON 契约、异步收敛语义、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 状态卡片、计数器/消息/异步/下单四区、Saga 日志、生命周期恢复
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
duks/                                 上游状态引擎(进 ohos 构建,jvm + ohosArm64 + ohosX64)
duks/src/commonMain/                  引擎全量源码(零逻辑改动)
duks/src/composeMain/                 新增目录:mapToPropsAsState 上游原样移入,不注册源集
example/nativeApp/                    Kotlin/Native 动态库(Bridge + MiniJson + FileStateStorage)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.upstream.md                    上游原文保存

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :duks 与 :example:nativeApp,rootProject.name = "ohos_duks"。上游的 dokka / vanniktech 发布插件、AGP classpath、compose 编译器插件全部随根构建替换剥离——本 fork 不发 Maven Central,五个上游构建文件原样保存为 *.upstream。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21(org.gradle.java.home 指向本地 JDK 21 发行版)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle9.6.1(沿用上游 wrapper;2.4.20 线支持 Gradle 9.x)
kotlinxcoroutines 1.11.0-0.1.0-07、datetime 0.8.0-0.1.0-07、serialization-json 1.11.0-0.1.0-07(ohos 制品线)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.duks
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
位置文件依赖形态处置
commonMain(引擎主体)17纯 Kotlin + coroutines + datetime,零 AGP整体编译(jvm + 双 ohos)
commonMain/State.kt(1 个函数)—androidx.compose.runtime(mapToPropsAsState)Compose 扩展原样移入未注册 composeMain,StateModel / mapToProps 留守
commonTest23kotlin-test + coroutines-test + serialization-json零改动,JVM 交叉验证
androidMain / wasmJsMain2平台专属留盘不进构建

expect 声明全库只有 1 处:expect fun backgroundDispatcher(): CoroutineDispatcher。上游 actual 的分布决定了 ohos 侧的处置——android / jvm / wasmJs 各一个,native 平台集中写在 nativeMain(Dispatchers.IO)。这与 KStore(actual 分散在 apple / linux / windows 等平台源集、需要补第 8 个 ohos actual)不同:ohos 目标在默认层级模板的 native 层级之下,nativeMain 的 actual 对 ohos 直接生效,本 fork 没有为 ohos 新增任何源文件。第一次尝试在 ohosMain 补 Dispatchers.Default actual 时编译器直接报 Conflicting overloads,删除后 compileKotlinOhosArm64 一次通过——Dispatchers.IO 在 1.11.0-0.1.0-07 线上可用(与 KStore 记录的"IO 为 internal"结论不同,此点已在主文档踩坑表里如实标注,供后来者复核)。

duks 的状态引擎全部站在 coroutines(StateFlow / Mutex / SupervisorJob)和 datetime 上,两个依赖在鸿蒙制品线上都有与上游同版本的变体——所以这个模块在鸿蒙侧连 8 行 dispatcher actual 都不用补,适配面比 KStore 更小。

1.4 页面边界
标题区          DUKS · OPENHARMONY / 版本握手行(duks 0.4.0-ohos.1 (upstream 0.4.0))
状态卡片        counter / message / user / isLoading / lastError / orderStatus / orders 计数 / storage / storeClosed + 刷新按钮
计数器区        + / - / 重置 三按钮
消息区          TextInput + 设置消息按钮
异步区          userId 输入 + 模拟失败 Toggle + 加载用户按钮(执行中禁用)
Saga 区         四路下单按钮(成功 / 在 reserve 失败 / 在 charge 失败 / 在 ship 失败)
Saga 日志区     sagaLog 逐行展示(最新在前)
生命周期区      reset-store + 重新 init(演示文件恢复)
日志区          带时间戳的桥调用日志(cmd + ok/FAIL 摘要,上限 30 条)

页面启动时先调 {"cmd":"version"} 做握手,再以 filesDir + '/duks-state.json' 调 init 注入持久化路径。

第 2 阶段:目标与依赖打通

2.1 制品线验证(本适配的成败手)

先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 ohos 的 kotlinx 制品。用 maven-metadata.xml 逐个验证:

org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0-0.1.0-07
  → ohosArm64 / ohosX64 / jvm 变体齐全
org.jetbrains.kotlinx:kotlinx-datetime:0.8.0-0.1.0-07
  → 同上(上游钉 0.8.0,制品线存在同主体版本)
org.jetbrains.kotlin:kotlin-serialization:2.4.20-0.1.0-07
  → 编译器插件制品存在(上游 commonTest 的 @Serializable 模型需要)
org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0-0.1.0-07
  → 测试依赖,JVM 变体即可解析

三个关键结论:其一,依赖版本主体与上游声明逐字相同(coroutines 1.11.0、datetime 0.8.0、serialization 1.11.0),只是仓库换线——commonMain 里一行依赖坐标都不用改;其二,ohos 制品同时发布 jvm 变体,jvm 交叉验证可以用同一个版本号,不需要双线隔离;其三,serialization 编译器插件也必须钉在同一条 Kotlin 线上(2.4.20-0.1.0-07),插件与运行时缺一不可(见 8.1 踩坑第 4 条)。

2.2 工具链钉版(为什么是 2.4.20-0.1.0-07)

社区此前批准的钉法是 HarmonyOS Kotlin 2.2.21-1.0.0(qrose 用的)。但 coroutines 1.11.0-0.1.0-07 依赖 kotlin-stdlib 2.4.20-0.1.0-07(元数据 2.4.x),Kotlin 2.2.21 只能读到 2.3.0,编译期直接报元数据不兼容。工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07。额外好处:上游源码本来就是 Kotlin 2.4.10 编的,2.4.20 与之同代,源码级兼容成本为零;Gradle 9.6.1 上游 wrapper 原样沿用(kotlin-channel-event-bus 同线先例),不像 2.2.21 旧线必须降 8.14.1。

settings.gradle.kts 把 eazytec 仓库写在最前:

pluginManagement {
    repositories {
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

rootProject.name = "ohos_duks"

include(":duks")
include(":example:nativeApp")
2.3 编译真实引擎

duks/build.gradle.kts 收敛为纯 KMP 模块:

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.kotlinx.serialization)   // 上游 commonTest 的 @Serializable 模型需要,替换构建时必须补回
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

    jvm()                                     // 交叉验证用
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {
            implementation(libs.kotlinx.coroutines.core)   // 1.11.0-0.1.0-07
            implementation(libs.kotlinx.datetime)          // 0.8.0-0.1.0-07
        }
        commonTest.dependencies {
            implementation(libs.kotlin.test)
            implementation(libs.kotlinx.coroutines.test)
            implementation(libs.kotlinx.serialization.json) // 仅 JVM 测试解析
        }
    }
}

实测:

./gradlew :duks:compileKotlinOhosArm64 :duks:compileKotlinOhosX64    # BUILD SUCCESSFUL

引擎的全部 commonMain 文件一个没动,全仓也没有为 ohos 新增源文件。

2.4 测试依赖的处置:测试统一在 JVM 跑

commonTest 的依赖表对 ohos 测试编译同样可见,测试依赖接 ohos 变体的成本没有收益。处置是两条纪律的组合:测试统一在 JVM 跑(jvmTest),根构建全局禁用 ohos 测试任务:

subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }
}

这样上游测试文件(23 个文件、148 项用例)零改动保留,ohos 侧只承担编译与链接。

第 3 阶段:桥协议建模

3.1 JSON 契约
duks/**(上游引擎,原样编译)
        ↓ KStore<DemoState> / middleware 链 / Saga 状态机 / 持久化中间件
Bridge.kt(@CName × 1,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API duksCommand(request)
        ↓
ArkUI Index.ets(单页滚动)

请求与响应:

请求 : {"cmd":"increment"}
       {"cmd":"set-message","text":"hello"}
       {"cmd":"load-user","userId":"u42","fail":false}
       {"cmd":"place-order","failAt":2}
       {"cmd":"init","path":"/data/.../duks-state.json"}
响应 : {"ok":true,"cmd":"...","state":{counter:…,message:…,user:…,sagaLog:[…],…}}
       {"ok":false,"cmd":"...","error":"..."}

一个关键不对称设计:命令通道极简(1 个 C ABI),快照通道全量——每次响应都带完整 DemoState 快照,ArkTS 无需增量对账;异步与 saga 的收敛语义由桥内等待逻辑承担(见 3.3),调用方拿到的永远是"终态"。

3.2 桥内的 store 怎么开

桥持有全局单例 store(首次命令惰性创建),中间件链全部用上游 DSL 装配:

val created = createStore(DemoState()) {
    middleware {
        exceptionHandling(onError = { _, _ -> }, errorAction = { _, _ -> null })
        async()                                        // AsyncAction 生命周期
        sagas {                                        // 订单 saga:reserve→charge→ship + 补偿
            saga<OrderSagaState>(name = "OrderSaga") {
                startsOn<DemoAction.PlaceOrder> { action -> /* Continue(初始态, Dispatch(SagaStep(1))) */ }
                on<DemoAction.SagaStep>(condition = { a, s -> a.orderId == s.orderId }) { action, state ->
                    // failAt 命中 → 反向补偿 + Fail;step>=3 → Complete;否则 Continue 链式推进
                }
            }
        }
        persistence(                                   // POSIX 落盘 + OnEveryChange + 自动恢复
            storage = FileStateStorage(path),
            strategy = PersistenceStrategy.OnEveryChange
        )
    }
    reduceWith(demoReducer)
}

suspend 调用统一用 runBlocking 收进同步 C ABI:鸿蒙线 Dispatchers.Main 的跨线程派发有已知问题(resaca 仓适配时记录过),桥全程不碰 Main,store 的 ioScope 站在 backgroundDispatcher()(nativeMain actual → Dispatchers.IO,ohos 实测可用)上,天然绕开。

3.3 异步收敛:dispatchAsync 之外的等待语义

dispatchAsync(action) 只等待本 action 的 middleware 链与 reducer 完成;async 中间件在后台跑 getResult,saga 的效果是嵌套 dispatch——两者都不会被 dispatchAsync 等到。桥对这两类命令显式收敛:

"load-user" -> {
    store.dispatchAsync(DemoAction.LoadUser(userId, fail))
    // 等待 async 生命周期落定(isLoading 在 Result/Error/Complete 后翻回 false)
    withTimeoutOrNull(6_000) { store.state.first { !it.isLoading } }
    response(store.state.value)
}
"place-order" -> {
    val orderId = "order-${time(null)}-$seq"           // 唯一 ID,避免旧终态提前命中
    store.dispatchAsync(DemoAction.PlaceOrder(orderId, failAt))
    // 等待本实例到达终态(done:<id> / failed:<id> 由 saga 效果写进 sagaLog)
    val finalState = withTimeoutOrNull(8_000) {
        store.state.first { s -> s.sagaLog.any { it == "done:$orderId" || it == "failed:$orderId" } }
    }
    response(finalState ?: store.state.value)          // 超时也返回快照 + ok:false
}

这就是"命令-快照"模式:一次 Native 调用 = 一次命令 + 一个确定性的终态快照,ArkTS 侧不需要轮询。

3.4 错误通道

导出整体 try/catch,Kotlin 异常永远不跨 FFI:

@CName("DuksCommand")
fun duksCommand(request: String): String = try {
    val parsed = MiniJson.parseObject(request)
    dispatchCommand(parsed["cmd"] as? String ?: error("missing 'cmd'"), parsed)
} catch (t: Throwable) {
    MiniJson.stringify(linkedMapOf("ok" to false, "error" to (t.message ?: t.toString())))
}

非法 JSON、缺 cmd、未知命令、saga 超时,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。

第 4 阶段:原生桥接(技术难点)

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libduks_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosduks.so)
  │ DuksCommand
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制状态管理语义,所以选择 C ABI + JSON。版本信息不走独立导出(KStore 用 KStoreVersion),而是并进同一命令通道({"cmd":"version"}),桥面收敛为 1 个符号。

4.2 Kotlin/Native 导出函数
@CName("DuksCommand")
fun duksCommand(request: String): String   // version / init / state / increment / decrement /
                                           // set-message / reset / load-user / place-order / reset-store 一口进
C ABI 符号ArkTS 方法覆盖内核
DuksCommandduksCommand(req)store 创建 + dispatchAsync + middleware 链 + reducer + saga + 持久化 + close
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"duksCommand", nullptr, NapiDuksCommand, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

回调统一三步:napi_typeof 校验必须是 string(先量长再 std::vector<char> 拷贝,规避缓冲区陷阱)、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosduks_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosduks_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与业务符号),不拖整份生成头。

4.4 CMake 和类型声明
add_library(ohosduks SHARED IMPORTED)
set_target_properties(ohosduks PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(duks_napi SHARED napi_init.cpp)
target_link_libraries(duks_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosduks)

OHOS_ARCH 为空或 libohosduks.so 缺失直接 FATAL_ERROR(提示先跑 :example:nativeApp:linkDebugSharedOhosArm64 / OhosX64);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。

export const duksCommand: (request: string) => string;

第 5 阶段:ArkUI 宿主

5.1 注入路径和触发命令
aboutToAppear(): void {
  this.versionLine = …;                       // {"cmd":"version"} → "duks 0.4.0-ohos.1 (upstream 0.4.0)"
  this.init(`${ctx.filesDir}/duks-state.json`); // {"cmd":"init","path":…} → storage:"file" + 恢复快照
}

private call(cmd: string, params: Record<string, Object>): void {
  const res = JSON.parse(duksCommand(JSON.stringify({ cmd: cmd, ...params }))) as DuksResponse;
  this.apply(res);                            // @State 刷新状态卡片 + 双日志区
}

长阻塞命令(load-user ~0.6s、place-order、init)先置"执行中…"再 setTimeout(16ms) 延后执行,让 UI 先把执行态画出来。

5.2 页面交互

单页自上而下:标题与版本行、Store 状态卡片(十项字段 + 刷新按钮)、计数器区(+/-/重置)、消息区(输入框 + 按钮)、异步区(userId 输入 + 模拟失败 Toggle + 加载按钮,执行中禁用)、Saga 区(四路下单按钮)、Saga 日志区(逐行、最新在前)、生命周期区(reset-store + 重新 init)、桥调用日志区([HH:mm:ss] cmd ok/FAIL,上限 30 条)、关于区(仓库与社区链接,统一 AtomGit)。严格 ArkTS:无 any,interface 响应类型 + 显式请求 class。

5.3 对比度和错误边界

下单四按钮语义对齐(成功深色 / 三路失败橙红);ok:false 时错误原文进状态与日志;Native 调用抛错(理论不该发生)同样被 try/catch 收进日志区。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosduks)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/duks/ohos/
│       ├── Bridge.kt                         @CName DuksCommand + store 单例 + saga/async 收敛
│       ├── Model.kt                          DemoState / DemoAction / reducer / 订单 saga 协议动作
│       ├── MiniJson.kt                       手写 JSON(继承自 cmp_qrose 审核模式)
│       └── FileStateStorage.kt               POSIX StateStorage + DemoState 编解码
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.duks、versionName 0.4.0-ohos.1
    └── entry/
        ├── build-profile.json5               externalNativeOptions + abiFilters [arm64-v8a, x86_64]
        ├── libs/{arm64-v8a,x86_64}/          linkDebugShared* 经 copyKnSo* 回填的三件套(gitignore)
        ├── src/main/cpp/CMakeLists.txt       IMPORTED 引入 KN so、$ORIGIN rpath
        ├── src/main/cpp/napi_init.cpp        N-API 导出、参数检查、DisposeString
        ├── src/main/cpp/include/libohosduks_api.h
        ├── src/main/cpp/types/libduks_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面
6.2 构建、签名和安装
./gradlew :duks:jvmTest                                        # 148 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库。

hdc list targets -v
hdc -t <设备序列号> install -r \
  entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b org.terminator.ohos.duks -m entry

四、完整代码对照

4.1 整体架构

duks/**(上游引擎原样编译)
    │ KStore<TState> / Reducer 组合 / middleware 链 / AsyncAction / Saga / Persistence
Bridge.kt + MiniJson + FileStateStorage(example/nativeApp)
    │ DuksCommand
libduks_napi.so
    │ duksCommand
Index.ets
    │ JSON parse + @State 更新
    ▼
ArkUI 真机页面(单页滚动)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_duks、example 模块接入
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-07 + ohos kotlinx 制品线钉版 + serialization 插件别名
build.gradle.kts(根)版本 0.4.0-ohos.1、全局禁用 Ohos*Test* 任务
duks/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,serialization 插件模块级补回
duks/src/composeMain/.../StateCompose.kt唯一"新增"目录:上游 mapToPropsAsState 原样移入,不注册源集
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt1 个 C ABI、runBlocking 收敛、store 单例、错误通道
nativeApp/.../FileStateStorage.ktPOSIX 文件版 StateStorage<DemoState> + 编解码
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(24 节)

4.3 关键 API 对照

层次API作用
引擎createStore(initialState) {…}StoreBuilder DSL 建店(middleware + reducer)
引擎dispatchAsync(action)挂起等待本 action 的 middleware 链 + reducer
引擎AsyncAction<T>.getResult挂起取数,async() 中间件发射生命周期动作
引擎sagas { saga<T> { startsOn / on } }Saga 注册与触发(条件 handler)
引擎SagaTransition.Continue / Complete / Fail状态机转移,Fail 可带补偿 effects
引擎SagaEffect.Dispatch / Delay / Parallelsaga 效果(含并行)
引擎persistence(storage, strategy)自动恢复 + 按策略落盘
引擎KStore.close()lifecycle 中间件 teardown、store job 取消
引擎mapToProps { selector }Flow 选择器(相等性短路,随引擎编译);mapToPropsAsState 在 composeMain 待接回
NativeDuksCommandC ABI JSON 导出(唯一符号)
N-APIduksCommand(req)向 ArkTS 暴露能力
ArkTScall(cmd, params)触发命令并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const res = JSON.parse(duksCommand(JSON.stringify({
  cmd: 'place-order', failAt: 2
}))) as DuksResponse;
// res.ok === true;res.state.sagaLog ===
//   ["step1:reserve","step2:charge","compensate1:reserve","failed:order-…"]
// res.state.orderStatus === "failed:charge",ordersFailed + 1
// 桥内:saga 在 charge 步失败 → 反向补偿已完成步骤 → Fail
val compensations = (step - 1 downTo 1).map {
    SagaEffect.Dispatch(DemoAction.SagaCompensate(action.orderId, it))
}
SagaTransition.Fail(
    IllegalStateException("${stepLabel(step)} failed for ${action.orderId}"),
    compensations + SagaEffect.Dispatch(DemoAction.OrderFailed(action.orderId, step))
)

两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。

五、关键决策说明

决策 1:真编译整库而不是提取零依赖内核

早期仓库抽内核,是因为 ohos 上没有它们依赖的 kotlinx 制品。本次适配先验证制品线(coroutines / datetime / serialization 全部存在与上游同版本的 ohos 变体),障碍已消失——再抽内核等于把状态引擎手抄一遍,反而引入漂移风险。真编译让 middleware 组合、Saga 状态机、持久化生命周期全部是上游原件。

决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 沿用 9.6.1

coroutines 1.11.0-0.1.0-07 基于 stdlib 2.4.20 构建,旧钉法 2.2.21 读不了 2.4 元数据;上游本来就是 Kotlin 2.4.10,2.4.20 同代零降级。Gradle 方面 2.4.20 线支持 9.x(kotlin-channel-event-bus 同线先例),上游 wrapper 9.6.1 原样保留——只有 2.2.21 旧线才必须降 8.14.1,两条工具链线的经验不能互相套用。

决策 3:Compose 隔离而不是删除(composeMain 留盘模式)

上游唯一的 Compose 集成 mapToPropsAsState(State.kt 里的 1 个 @Composable 扩展)挡住了整个引擎的 ohos 编译。处置是原样移动:函数体一个字符不改,移入未注册的 duks/src/composeMain,同文件的 StateModel / StateAccessor / mapToProps(纯 coroutines)留守 commonMain——等鸿蒙 Compose 运行时制品可用,把文件移回即恢复,上游能力零损失。这延续了 qrose 的 composeMain 留盘纪律:不删除、不注册、不进构建。

决策 4:命令-快照桥 + 显式异步收敛,而不是事件订阅桥

状态库与存储库不同:store 是长生命周期对象,状态在持续演化。桥选择"命令 + 终态快照":DuksCommand 一口进,async / saga 的嵌套 dispatch 由桥内 state.first { … } + 超时收敛成确定性终态,ArkTS 不轮询、不对账。订阅式推送(StateFlow → C 回调 → ArkTS emitter)过不了当前同步 C 签名,列为后续方向。

决策 5:jvmTest 交叉验证 + 测试零改动

引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;148 项 jvmTest 跑的就是上游原测试文件(23 个文件,覆盖 store 组合、middleware 顺序、async 生命周期、saga 状态机与补偿、持久化恢复与迁移、缓存),是"内核没漂"的闸门。测试依赖不接 ohos 的处置不是改测试,而是"测试统一 JVM + 根构建禁用 Ohos*Test*"——测试文件一个字节不动,交叉验证的有效性才成立。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x86_64)、JDK 21(本地发行版,org.gradle.java.home 钉死)、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

./gradlew :duks:jvmTest

148 项用例全部通过(上游原测试文件,零改动),覆盖面包括:

测试域用例文件钉住什么
Store 核心StoreTest / StoreBuilderTest / DispatcherTest建店、dispatch 语义、中间件顺序
MiddlewareMiddlewareTest / IntegrationTest / TestUtils组合链、异常处理钩子
异步AsyncTest / CustomAsyncActionTestAsyncAction 生命周期四动作
SagaSagaTest / SagaPhase9Test / SagaRegistryTest / SagaFanIn(经内核)状态机转移、Fail、fan-in、注册
持久化StatePersistenceTest / SagaPersistenceTest / MigratingStateStorageTest 等恢复、策略、版本迁移
选择器MapToPropsTest / MapActionCacheTest / CachingTest相等性短路、缓存

依赖换线没有 API 漂移:coroutines 1.11.0、datetime 0.8.0、serialization 1.11.0 换成 -0.1.0-07 线后版本主体不变,上游测试照跑全绿——这正是"换线不换版"的直接证据。(serialization 编译器插件首次漏配时 4 个持久化测试失败,补回后全绿,见 8.1。)

6.3 HAP 验证

:duks:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: DuksCommand 为全局 T 符号(双 ABI 均验证)
BuildNativeWithCmake: finished(双 ABI 编出 libduks_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

KN so(debugShared,含 Kotlin/Native 运行时与 coroutines)arm64 约 7.4MB、x86_64 约 6.9MB,连同 libc++_shared.so 由 copyKnSo* 任务回填 entry/libs/<abi>/,HAP 打包时再带上 NAPI 包装 so。

6.4 功能用例

  1. 启动后版本行显示 duks 0.4.0-ohos.1 (upstream 0.4.0)(来自真实 Native 调用,非静态文字);
  2. init 自动注入真机沙箱 filesDir(/data/storage/el2/base/haps/entry/files/duks-state.json),返回 storage:"file";
  3. 计数器 +/- 点按(真机实测连点到 counter=10),每次响应快照 counter 同步 +1;
  4. 设置消息,message 字段即时更新;
  5. 加载用户:按钮禁用约 0.6s 后 user 出现 {id,name,email};
  6. 加载用户(模拟失败):lastError = "user service unavailable",user 不变;
  7. 下单(成功):sagaLog 依序 step1:reserve → step2:charge → step3:ship → done:…,orderStatus=ok;
  8. 下单(在 charge 失败):sagaLog step1:reserve → step2:charge → compensate1:reserve → failed:…——Saga 先补偿已完成步骤再 Fail,ordersFailed + 1;
  9. reset-store → 重新 init:counter / message 恢复为关闭前的持久化值——落盘持久化成立,不是内存缓存;
  10. 每步操作的桥调用日志按时间戳滚动留痕([HH:mm:ss] increment ok)。

6.5 验证结论

状态引擎、coroutines 运行时、Saga 状态机、持久化文件层、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的状态管理会话:版本握手、计数器、消息、异步生命周期、Saga 补偿事务、重启持久化闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/duks.git
cd duks
export JAVA_HOME="/path/to/jdk-21"
./gradlew :duks:compileKotlinOhosArm64 :duks:compileKotlinOhosX64     # klib
./gradlew :duks:jvmTest                                               # 148 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.duks -m entry
hdc shell hilog | grep Duks

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :duks 真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. kotlinx-datetime 在 Maven Central 上的 0.8.0 没有 ohos 变体,必须钉 0.8.0-0.1.0-07——版本主体写对、后缀写错,解析照样失败;
  4. serialization 编译器插件在根构建替换时最容易丢:丢了编译期不报错,@Serializable 类只是没生成 serializer(),直到 jvmTest 运行期才炸 Serializer for class 'TestState' is not found(本仓首次全量测试 4 项持久化用例失败即此因)——插件钉 2.4.20-0.1.0-07 补回后全绿;
  5. ohos 目标在默认层级模板的 native 层级之下:duks 的 backgroundDispatcher actual 集中在 nativeMain,对 ohos 直接生效;在 ohosMain 再补 actual 会报 Conflicting overloads。KStore 需要补第 8 个 actual 是因为它的 actual 分散在平台源集——两库结论不同的根因是 actual 的源集布局,不是 ohos 层级本身;
  6. KStore 文档记录"鸿蒙线 Dispatchers.IO 为 internal、ohos actual 必须用 Default";本仓实测 1.11.0-0.1.0-07 线上 nativeMain 的 Dispatchers.IO actual 对 ohos 编译直接通过。两条记录都如实保留,后来者先看自己库的 actual 布局再决定;
  7. @Volatile 在 Kotlin 2.x 迁到 kotlin.concurrent.Volatile,旧写法报 Unresolved reference;
  8. POSIX 调用(time(null) 等)需要 @OptIn(ExperimentalForeignApi::class),建议文件级声明;
  9. saga 终态等待必须用唯一 orderId 匹配(done:<id> / failed:<id>),复用旧 ID 会被历史日志提前命中——orderId 用 time(null)+序号 生成;
  10. turbine 式问题在 duks 同样存在(coroutines-test 不接 ohos):测试统一 JVM 跑 + 根构建全局禁用 Ohos*Test* 任务,上游测试文件零改动;
  11. 鸿蒙线 Dispatchers.Main 跨线程派发有已知问题(resaca 记录),桥用 runBlocking 收 suspend、store 调度器站在 IO/Default 上,全程不碰 Main;
  12. 根构建的 gitignore 是根锚定模式,盖不住嵌套目录——example/harmonyApp/oh_modules、entry/build、entry/libs(so 回填目录)、.hvigor、.cxx 要单独追加,so 三件套绝不进仓库;
  13. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  14. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可。

8.2 已知问题

  • mapToPropsAsState(Compose 集成)在未注册的 composeMain 留盘,等鸿蒙 Compose 运行时制品;mapToProps(Flow 版)已随引擎编译进 so;
  • 状态订阅(StateFlow)不跨 C ABI:桥是命令-快照模式,订阅式推送过不了 C 签名,ArkTS 侧以命令后快照观察状态;
  • Saga 持久化(SagaStorage)已编译进 so,Demo 未演示 saga 实例跨重启恢复(Demo 的重启恢复走 StateStorage 整状态恢复);
  • 桥是同步 C ABI:load-user / place-order 阻塞调用线程至收敛或超时(6s/8s),Demo 取舍,产品化建议事件订阅桥或 taskpool;
  • android / ios / wasmJs 目标剥离(源文件留在树里),发布、dokka 一并剥离,本 fork 不发 Maven Central;
  • 多 store 实例、dispatchAsync 嵌套 await 等高级用法在桥上未暴露,走 Gradle 依赖直连 so 的形态更合适;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.duks 匹配的签名。

8.3 未来优化方向

  • 增加状态变化推送(StateFlow → N-API 线程安全回表 → ArkTS emitter),替代命令-快照轮询语义;
  • 补 SagaStorage 的桥接入口(saga 实例查询、checkpoint 触发、跨重启恢复演示);
  • 接回 composeMain:鸿蒙 Compose 运行时制品可用后,mapToPropsAsState 移回 commonMain 即恢复上游完整 API;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 duks(全部 commonMain 引擎)
    → 验证 ohos kotlinx 制品线(coroutines / datetime / serialization)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1 沿用)
    → 根构建替换(cmp_qrose 模式,serialization 插件模块级补回)
    → composeMain 隔离(mapToPropsAsState 原样留盘,引擎零逻辑改动)
    → ohosArm64 / ohosX64 KLIB(ohos 复用 nativeMain actual,零新增源文件)
    → C ABI(@CName DuksCommand)+ 命令-快照契约 + 异步收敛
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析
    → ArkUI 真机单页验收(计数器 / 异步 / Saga 补偿 / 重启持久化)

9.2 封装层次

  • 引擎层:上游原版 KStore<TState>、reducer 组合、middleware 链、AsyncAction 生命周期、Saga 状态机与补偿、持久化中间件;
  • Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出 JSON(含 MiniJson 与 POSIX FileStateStorage);
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、命令分发、快照渲染、日志与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 适配面由库的依赖形态决定:duks 全库只有一个 expect 且 actual 集中在 nativeMain,ohos 复用即可——真编译整库连一个新增源文件都不需要;KStore 需要补 8 行 actual 是因为它的 actual 分散在平台源集。适配前先盘点 expect/actual 布局,能准确预判工作量;
  2. 工具链版本跟着依赖走:Kotlin 钉版由 coroutines 制品线的 stdlib 元数据反推(2.4.20-0.1.0-07),Gradle 经验按工具链线区分(2.4.20 线留 9.x,2.2.21 线降 8.14.1);serialization 编译器插件与运行时必须同线成对出现,漏插件的失败要等到测试运行期才暴露;
  3. 交叉验证是"内核没漂"的唯一可信证明:148 项 jvmTest 跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;状态库没有渲染面,Demo 的验收重点天然落在"Saga 补偿顺序正确"和"重启后状态还在"这两条闭环上。

9.4 适配成果

当前适配已完成:

  • duks 0.4.0 真实引擎的双 ABI Kotlin/Native 编译(上游 commonMain 文件零逻辑改动,全仓零新增库源文件,仅 1 个 Compose 扩展原样移入未注册源集);
  • HarmonyOS kotlinx 制品线在状态管理库上的完整落地(coroutines 1.11.0-0.1.0-07 + datetime 0.8.0-0.1.0-07 + serialization 1.11.0-0.1.0-07);
  • 1 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证双 ABI 全局 T 符号);
  • DevEco Stage ArkUI 单页验收页面(版本握手、计数器、消息、异步生命周期、Saga 四路下单与补偿、store 生命周期、重启持久化、双日志区);
  • 148 项 JVM 交叉验证全绿(上游原测试文件)+ HAP 构建(双 ABI 三件套 so)、签名、设备安装和真机验证;
  • AtomGit 项目文档、OpenHarmony README(中英双份)和本地效果图。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐