本文记录 compose-navigation-reimagined 接入 OpenHarmony 的完整过程,覆盖导航库盘点、纯 Kotlin 引擎语义提取、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

compose-navigation-reimagined 是 Jetpack Compose 的类型安全导航库:NavController 管理一个由 NavEntry(唯一 NavId + 目的地)组成的 backstack,navigate / pop / popAll / popUpTo / replaceLast / replaceAll / replaceUpTo / moveToTop / setNewBackstack 覆盖全部栈操作;每个 entry 拥有独立的 Lifecycle / ViewModelStore / SavedStateRegistry,NavScopeSpec 让多个目的地共享作用域化组件,NavAction 驱动 AnimatedNavHost 转场,Parcelable 支撑进程死亡恢复。本次适配与 KStore 的"制品线真编译"路线有本质区别:上游 1.5.0 是纯 Android 库(Kotlin 1.9.0 / AGP 8.1.0 / AndroidX Compose BOM,尚无 KMP 支持),没有 commonMain 可言,耦合点在 android.os.Parcelable、androidx.lifecycle 全家桶与 Compose 运行时——社区 Maven 再齐全的 kotlinx 制品线也解决不了 AndroidX 框架耦合。因此本项目的答案是引擎语义提取移植(Decompose / telephoto / cmp_qrose 同款路线):把 backstack 状态机与 NavHostState 的生命周期/作用域/转场队列语义 1:1 重建为纯 Kotlin commonMain,编译成 OpenHarmony 动态库,ArkUI 页面复刻 AnimatedNavHost 行为作为验收宿主。

项目地址: AtomGit/oh-tpc/compose-navigation-reimagined.git

上游仓库: olshevski/compose-navigation-reimagined

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

compose-navigation-reimagined 在 Android 上卖的是"一个类型安全、可组合、组件作用域正确的导航栈":目的地是任意 Parcelable 类型而不是字符串路由;栈操作是带语义的扩展函数而不是 Fragment 事务;每个栈元素有独立身份,因此拥有自己的生命周期、ViewModelStore 与保存状态;NavScopeSpec 用一行声明就把"这几个页面共享同一个 ViewModelStore"写清楚;AnimatedNavHost 按 NavAction 选择转场,退出页面在转场期间保持存活(outdated 队列),生命周期同步升降级。上游把这套能力全部实现在 Android 源集里,由 Jetpack Compose 直接消费。OpenHarmony 应用同样需要这套导航语义,但上游构建不会生成 OpenHarmony 变体,且社区 Compose 制品线(1.12.0-0.1.0-07)尚无 composition 宿主,Compose UI 代码在鸿蒙上还跑不起来。

如果只把示例页面改成 ArkTS,页面可以画出一摞卡片,却无法证明导航状态机在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证栈操作引擎、生命周期状态机、作用域存储、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
平台耦合28 个核心源文件中 11 个 import android.* / java.* / kotlinx.parcelize:Parcelable 泛型约束贯穿整个 public API,NavId 依赖 java.util.UUID + android.util.Base64,NavHostState 整层站在 Lifecycle / ViewModelStore / SavedStateRegistry 上。
无 commonMain上游 1.5.0 是单一 Android target 的 com.android.library(上游 1.6.0+ 才做多平台),不存在"整体编译 commonMain"的选项。
工具链不一致上游 Kotlin 1.9.0 / AGP 8.1.0 / Gradle 8.2.1 / buildSrc 预编译脚本插件,与鸿蒙工具链无法共存于一个 Gradle 构建。
Compose UI 编不了社区 ohos Compose 线无 composition 宿主,NavHost / AnimatedNavHost / DialogNavHost / BottomSheetNavHost 的 Composable 无法直接渲染。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
语义跨不了桥mutableStateOf 观察、转场期间的生命周期联动、rememberSaveable 恢复都是 Compose 宿主概念,必须显式化成协议。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:导航引擎的语义提取与 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。栈操作与状态语义完全由移植引擎负责,ArkTS 只负责调用 Native、解析 JSON、按协议驱动转场和展示结果。

1.2 库提供的能力

Demo 提供一个真实的导航会话,对应桥接的全部入口:

  • 栈操作(navigate/pop/popAll/popUpTo/replaceLast/replaceAll/replaceUpTo/moveToTop/setNewBackstack):与上游 NavControllerExt 逐字对齐的 9 组操作,含 Match.First / Match.Last 匹配策略与 inclusive 语义;
  • 转场协议(onTransitionStart/onTransitionFinish/removeOutdated):上游 AnimatedNavHost 隐式执行的时序被显式化为三个 op,ArkTS 动画严格按 开始 → 动画 → 结束 → 清理 驱动,退出页在转场期间保持存活;
  • 作用域存储(entryStore/scopedStore):每个 entry 独立的组件存储 + NavScopeSpec 作用域共享存储,pop 后重进同名页 entry 计数器归零而 scope 计数器保持;
  • 宿主生命周期(hostLifecycle):start/resume/pause/stop/destroy 五档驱动全栈生命周期级联(min(host, max) 规则);
  • 状态恢复(saveState/restoreState):全量保存栈结构、entry 身份与两级组件存储,模拟进程死亡恢复;
  • 版本握手(version):reimaginedVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok / error操作是否成功,失败时 error 带原文。
op / version回显本次操作与桥版本。
action本次栈操作的 NavAction(Navigate / Pop / Replace / Idle),驱动 ArkTS 选择转场。
entries[]全量 backstack:id / destination / lifecycle / current / scopes / components。
scopedStores[] / outdated[]存活作用域存储与退出动画中的条目。
savedPreview / savedMeta / log[]保存态预览、元信息与滚动事件日志。

引擎侧完整能力(生命周期状态机、outdated 队列、作用域归属、保存恢复)全部在移植的 commonMain 里随编译进入 .so,JVM 侧保留 36 项 jvmTest 交叉验证用例,证明 ohos .so 里执行的导航逻辑没有漂移。

1.3 实现适配

维度要求
语义复用栈操作扩展函数逐字移植;NavHostState 的生命周期 min 规则、INITIALIZED→STARTED→DESTROYED 跳变规则、outdated 队列、作用域清理时序按上游源码 1:1 重建;不移植的 Android 源文件原样留盘。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,:reimagined 出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);material / material3 / hilt / sample / samples-di / test-utils 模块与 buildSrc 预编译脚本全部留盘不进构建。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律引擎 commonMain 零第三方依赖(纯 Kotlin),Kotlin 钉 2.2.21-1.0.0(eazytec 制品线),36 项交叉验证全绿。
UI 完整四 Tab 覆盖全部桥接入口:栈操作 + 转场复刻、作用域计数器、宿主生命周期 + 日志控制台、状态保存恢复。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。导航语义(什么时候 push、什么时候销毁、什么作用域活着、转场期间谁保持 RESUMED)全部由 Kotlin 引擎裁决,ArkTS 只按引擎返回的状态与协议渲染。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、盘点 Android 耦合点、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、buildSrc 摘出
第 3 阶段:引擎语义提取   ── 栈操作逐字移植、生命周期状态机、outdated 队列、作用域存储、save/restore
第 4 阶段:桥协议建模     ── 扁平 JSON 契约、转场三段协议、错误通道
第 5 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 6 阶段:ArkUI 宿主与验证 ── 四 Tab 页面、转场协议驱动、HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
reimagined/                            上游核心模块(重写为 KMP:jvm + ohosArm64 + ohosX64)
reimagined/src/commonMain/             新增:导航引擎(11 个文件,语义移植)
reimagined/src/commonTest/             新增:36 项交叉验证(上游测试语义移植到 kotlin-test)
reimagined/src/{main,test,androidTest} 上游 Android 源码,原样留盘不编译
reimagined-material*/ reimagined-hilt/ 上游 Material/BottomSheet/Hilt 模块,留盘不进构建
sample/ samples-di/ test-utils/        上游示例与 DI 演示(Android 工程),留盘不进构建
example/nativeApp/                     Kotlin/Native 动态库(Bridge + MiniJson + FullJson)
example/harmonyApp/                    DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md         鸿蒙适配说明(中英双份)
README.upstream.md                     上游原文保存
docs/                                  上游 mkdocs 文档站 + 本博客 + img/ 效果图

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :reimagined 与 :example:nativeApp,rootProject.name = "ohos_compose-navigation-reimagined"。上游的 nexusPublishing / publishing-config 签名发布、AGP classpath、buildSrc 预编译脚本全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(Temurin 21.0.12,org.gradle.java.home 写入 gradle.properties)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(wrapper 从 cmp_qrose 同款替换;2.2.21 旧线必须配 8.x,9.x 起不兼容)
引擎依赖commonMain 零第三方依赖(纯 Kotlin);commonTest 仅 kotlin("test")
DevEco product5.1.0(18) 兼容
DevEco Studio26.0.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.reimagined
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点

上游 reimagined 模块 28 个源文件逐一核对 import 面,分三档处置:

耦合档文件处置
纯逻辑NavControllerExt / Match / NavScopeSpec / NavBackstack / NavAction / NavEntry 等逐字移植(去掉 @Stable / Parcelable 等平台标注)
平台 API 渗透NavId(UUID + Base64 + ByteBuffer)、NavController(Parcelable CREATOR + mutableStateOf)、NavHostState(Lifecycle/ViewModelStore/SavedStateRegistry 全家桶)语义重建:NavId 改随机 16 字节 hex;观察改监听器;NavHostState 重建为 NavEngine
Compose UINavHost / AnimatedNavHost / DialogNavHost / NavBackHandler / 转场三件套留盘不编译,ArkTS 复刻宿主行为

expect/actual 一个都不需要——引擎不碰协程、不碰 IO、不碰序列化框架,这是导航状态机天然的优势:它的全部输入输出就是内存里的列表与状态。

1.4 页面边界
标题区          REIMAGINED · OPENHARMONY / 状态行(op · entries · scopes · action 四格实时刷新)
Tab 切换        Stack 栈操作 / Scopes 作用域 / Lifecycle 生命周期 / State 状态恢复
Stack           目的地舞台(当前页卡片 + 按 action 转场)、12 个栈操作按钮全集、
                backstack 列表(彩色 lifecycle 徽章 / scopes 标签 / counter / 短 id)、outdated 列表
Scopes          entryStore 计数器(±/清零,作用于栈顶)、scopedStore 计数器(shared / own 各自 ±)、
                存活作用域列表(scope / counter / lifecycle / 短 id)
Lifecycle       hostLifecycle 五按钮(start/resume/pause/stop/destroy)+ 重新 bootstrap、
                深色事件日志控制台(引擎事件流,最新在底部自动滚动)
State           saveState / restoreState + 便捷改栈按钮、savedMeta 与 preview 截断展示、版本与架构卡

页面启动时先调 bootstrap 建立导航会话(初始 backstack First,scopeRules First=shared|Second=shared|Third=shared+own|Fourth=),每次操作后整页状态由响应全量刷新。

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

2.1 制品线验证(引擎零依赖,钉版只服务工具链)

与 KStore 不同,本引擎 commonMain 没有任何第三方依赖,制品线验证只需要确认一件事:eazytec Maven 上的 HarmonyOS Kotlin 2.2.21-1.0.0 发行版提供 ohosArm64() / ohosX64() target 工厂(qrose / telephoto / sheets-compose-dialogs 已在库模块上验证过这条线)。工具链选择因此回到"跟随已审核先例":

  • Kotlin 钉 2.2.21-1.0.0:与 cmp_qrose / telephoto / Decompose(旧线)同款,社区审过、.konan ohos 工具链(sysroot-ohos-26.0.0.38、cpf-llvm)本机齐备;
  • Gradle 降 8.14.1:2.2.21 旧线不支持 Gradle 9.x,wrapper 四件套从 cmp_qrose 同款复制;
  • 不升 2.4.20 线:升线的唯一理由是依赖的 stdlib 元数据(KStore 的 coroutines 场景),本项目零依赖,无此需求——两条工具链线的经验不能互相套用。

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

pluginManagement {
    repositories {
        // HarmonyOS Kotlin toolchain (ohosArm64/ohosX64 targets) must come first,
        // otherwise the ohosArm64()/ohosX64() factory functions stay unresolved.
        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_compose-navigation-reimagined"

include(":reimagined")
include(":example:nativeApp")
2.2 编译引擎(buildSrc 摘出是本阶段的坑)

上游全部模块配置都站在 buildSrc 预编译脚本插件上(android-library-config / publishing-config,内部 apply AGP 8.1.0 + Kotlin 1.9.0 + AndroidX Compose)。Gradle 对 buildSrc 是无条件自动编译的——哪怕 settings 只 include 两个纯 KMP 模块,AGP 8.1.0 / Kotlin 1.9.0 仍会被拉进构建。处置:buildSrc/build.gradle.kts 置空(保留文件与说明注释),上游脚本插件留在 src/main/kotlin 磁盘上但不再被编译和解析。

reimagined/build.gradle.kts 重写为纯 KMP 模块:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)
    jvm()          // 交叉验证:commonTest 在 JVM 上跑
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {}          // 引擎零依赖
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

上游 Android 源码在 src/main / src/test / src/androidTest 不注册进任何 source set,原样留盘。实测:

.\gradlew :reimagined:compileKotlinJvm                    # BUILD SUCCESSFUL
.\gradlew :reimagined:compileKotlinOhosArm64 :reimagined:compileKotlinOhosX64   # BUILD SUCCESSFUL
2.3 测试任务的处置

Ohos*Test* 任务在 ohos target 上没有 test runner,根构建全局禁用(qrose / KStore 同款纪律),测试统一在 JVM 跑:

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

第 3 阶段:引擎语义提取(技术难点)

3.1 三类语义的移植策略
① 栈操作语义(逐字移植)
   NavControllerExt.kt 9 组扩展 + Match 枚举 —— 函数体是纯列表运算,零改动;
   仅 setNewBackstack 的 mutableStateOf 换成监听器列表(observe/unobserve)。

② 身份语义(替换平台实现,保持外部行为)
   NavId:java.util.UUID + android.util.Base64 → 随机 16 字节 hex(32 字符),
        同样保证唯一性,toString 即存储形式,NavId.of() 支撑恢复路径;
   NavHostId:@JvmInline value class → 普通 internal 类(ohos Kotlin/Native
        没有 kotlin.jvm.JvmInline,见 8.1 踩坑 2)。

③ 宿主状态语义(1:1 重建,本适配的核心工作量)
   NavEngineEntry:每个 entry 的生命周期状态机。hostLifecycleState(宿主档)
        与 maxLifecycleState(转场档)取 min 生效;枚举声明序复刻 Android
        Lifecycle.State(DESTROYED < INITIALIZED < CREATED < STARTED < RESUMED),
        minOf 因此与上游语义完全一致;INITIALIZED→DESTROYED 必须经停 STARTED;
        组件存储 Map<String,String> 替代 ViewModelStore + SavedStateRegistry。
   NavEngine:上游 NavHostStateImpl 的移植。createSnapshot 产出快照并把离开
        backstack 的 entry 排进 outdated 队列(转场期间存活);onTransitionStart
        把不可见 entry 压到 STARTED、onTransitionFinish 把可见升 RESUMED 其余压
        CREATED、removeOutdatedHostEntries 才 DESTROY 并清空组件存储;作用域
        entry 以 scope 为键 getOrPut,最后一个关联 entry 离栈时随之 outdated;
        saveState/restoreState 以全字符串字段替代 Parcelable 恢复。
3.2 转场协议显式化(导航库区别于存储库的适配点)

上游 AnimatedNavHost 在 Compose 里隐式完成"新快照渲染 → 转场开始 → 动画 → 转场结束 → 清理 outdated",生命周期联动藏在组合层。桥协议必须把这段时序变成显式契约:

ArkTS 收到操作响应(含新 backstack 快照与 action)
  → 渲染目标态,调用 op=onTransitionStart      (不可见 entry 压 STARTED)
  → 播放转场动画(Navigate 右滑入 / Pop 左滑出 / Replace 交叉淡化)
  → 调用 op=onTransitionFinish                 (可见升 RESUMED,其余压 CREATED)
  → 调用 op=removeOutdated                     (退出 entry DESTROY,组件存储清空)

快速连续操作打断上一次转场时,ArkTS 侧补发 onTransitionFinish + removeOutdated 再开新转场——引擎侧 outdated 队列支持多个快照排队(上游同款 ArrayDeque 结构),不会提前销毁还在动画中的 entry。

3.3 save/restore:Parcelable 的 JSON 映射

上游进程死亡恢复靠 NavController Parcelable(entry 顺序 + 去重目的地 + action)+ NavHostSavedState(hostId、entry ids、scoped records、outdated ids)。移植引擎把两者合并为一个全字符串的 NavEngineSavedState,桥上序列化为一个 JSON 对象(hostId / action / entries[] / hostEntryIds[] / scopedRecords[] / outdatedHostEntryIds[] / hostEntryStores{} / scopedStores{}),恢复时用 entry id 重建 NavEntry(身份保持),引擎按"backstack 里还在的恢复组件存储、不在的移除"的上游规则重建现场。

第 4 阶段:桥协议建模

4.1 JSON 契约
dev.olshevski.navigation.reimagined/**(移植引擎,原样编译)
        ↓ NavController / NavEngine / 生命周期状态机 / 作用域存储
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API reimaginedVersion() / reimaginedDispatch()
        ↓
ArkUI Index.ets(四 Tab)

请求与响应:

请求 : {"op":"navigate","destinations":"Second,Third"}
       {"op":"popUpTo","destination":"First","inclusive":"false","match":"last"}
       {"op":"scopedStore","scope":"shared","action":"inc"}
       {"op":"hostLifecycle","event":"pause"}
       {"op":"restoreState"}
响应 : {"ok":true,"op":"navigate","version":"1.5.0-ohos.1","hasWorld":true,
        "action":"Navigate","entries":[...],"scopedStores":[...],"outdated":[...],
        "savedPreview":null,"savedMeta":"","log":["#12 backstack -> 2 entries ..."]}
       {"ok":false,"error":"no world yet; call bootstrap first"}

不对称设计照搬 KStore 的经验:请求是扁平 JSON(MiniJson,键映射标量字符串),列表类参数用逗号分隔字符串编码(destinations / scopeRules / keep);响应是完整 JSON,ArkTS 侧 JSON.parse 一次到位,嵌套对象(entries[].components)原生嵌入不再转义。20 个 op 分三组:3 个会话(version / status / bootstrap)、9 个栈操作、8 个协议与状态(转场三段、entryStore / scopedStore、hostLifecycle、saveState / restoreState)。

4.2 scopeRules:把 NavScopeSpec 函数值变成可传输数据

NavScopeSpec 上游是 fun interface(Kotlin 函数值),跨不了 C ABI。桥把作用域声明收敛为声明式规则串:"First=shared|Second=shared|Third=shared+own|Fourth="(目的地 = 作用域 1 + 作用域 2,多条用 | 分隔,空集即 EmptyScopeSpec)。引擎侧仍是真正的 NavScopeSpec 实例——桥在 Kotlin 侧查规则表返回 Set<S>,上游的"作用域 = 返回值集合"语义原样保留,ArkTS 只是规则的书写者。

4.3 错误通道

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

@CName("ReimaginedDispatch")
fun reimaginedDispatch(request: String): String = try {
    val json = MiniJson.parse(request)
    dispatch(json.str("op"), json)
} catch (t: Throwable) {
    JsonOut()
        .bool("ok", false)
        .str("error", t.message ?: t.toString())
        .build()
}

非法 JSON、缺 op、未 bootstrap 就操作、空栈上取栈顶存储、不认识的 op、saveState 前先 restore,全部走同一个 ok:false 通道,ArkTS 侧统一渲染红字。

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

5.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libreimagined_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosreimagined.so)
  │ ReimaginedDispatch / ReimaginedVersion
  ▼
UTF-8 JSON + DisposeString
5.2 Kotlin/Native 导出函数
@CName("ReimaginedDispatch")
fun reimaginedDispatch(request: String): String   // 20 op 一口进

@CName("ReimaginedVersion")
fun reimaginedVersion(): String = "1.5.0-ohos.1"  // 版本握手
C ABI 符号ArkTS 方法覆盖内核
ReimaginedDispatchreimaginedDispatch(req)bootstrap 建会话 + 9 组栈操作 + 转场三段 + 两级存储 + 宿主生命周期 + save/restore
ReimaginedVersionreimaginedVersion()fork 版本握手(真机版本行的数据来源)
5.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"reimaginedDispatch", nullptr, NapiReimaginedDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"reimaginedVersion", nullptr, NapiReimaginedVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosreimagined_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free();API 26 的 napi_get_value_string_utf8 非 const 缓冲区用 std::vector<char> 规避。libohosreimagined_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号)。

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

add_library(reimagined_napi SHARED napi_init.cpp)
target_link_libraries(reimagined_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosreimagined)

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

export const reimaginedDispatch: (request: string) => string;
export const reimaginedVersion: () => string;

第 6 阶段:ArkUI 宿主与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosreimagined)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/org/terminator/ohos/reimagined/
│       ├── Bridge.kt                         @CName × 2 + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
│       └── Json.kt                           JsonOut 写出器 + FullJson 递归解析(restore 路径专用)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.reimagined、versionName 1.5.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/libohosreimagined_api.h
        ├── src/main/cpp/types/libreimagined_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 四 Tab 验收页面(1393 行)
6.2 构建、签名和安装
.\gradlew :reimagined:jvmTest                                              # 36 项交叉验证
.\gradlew :example:nativeApp:linkDebugSharedOhosArm64 `
           :example:nativeApp:linkDebugSharedOhosX64                       # 双 ABI so + 自动回填 entry/libs

cd example\harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,勾选自动签名后由 IDE 回填带 material 的完整配置)。

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.reimagined -m entry

四、完整代码对照

4.1 整体架构

dev.olshevski.navigation.reimagined/**(移植引擎,11 个 commonMain 文件)
    │ NavEntry / NavId / NavBackstack / NavAction / Match
    │ NavController + 9 组栈操作扩展(逐字移植)
    │ NavEngine + NavEngineEntry(生命周期状态机 / outdated 队列 / 作用域存储 / save-restore)
Bridge.kt + MiniJson + JsonOut + FullJson
    │ ReimaginedDispatch / ReimaginedVersion
libreimagined_napi.so
    │ reimaginedDispatch / reimaginedVersion
Index.ets
    │ JSON parse + 转场三段协议驱动 + @State 更新
    ▼
ArkUI 真机页面(四 Tab)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_compose-navigation-reimagined、Android 模块移出
gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 钉版 + multiplatform 插件别名(唯一依赖声明)
build.gradle.kts(根)版本 1.5.0-ohos.1、全局禁用 Ohos*Test* 任务
buildSrc/build.gradle.kts置空摘出(上游预编译脚本留盘不编译)
reimagined/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,commonMain 零依赖
reimagined/src/commonMain/**(11 文件)移植引擎(见 4.3 API 对照)
reimagined/src/commonTest/**(2 文件)36 项交叉验证(上游测试语义移植到 kotlin-test)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、20 op 分发、MiniJson、错误通道
nativeApp/.../Json.ktJsonOut 手写序列化 + FullJson 递归解析(saveState 往返)
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 四 Tab 验收页面 + 转场协议驱动
README.OpenHarmony_CN.md鸿蒙适配主文档(26 节)

4.3 关键 API 对照

层次API作用
引擎navController(initialBackstack)建控制器,栈序自底向顶(对应桥 bootstrap)
引擎navigate / pop / popAll / popUpTo / replaceLast / replaceAll / replaceUpTo / moveToTop9 组栈操作扩展,与上游逐字对齐(对应同名 op)
引擎setNewBackstack(entries, action)自定义重排(对应 setNewBackstack op 的 keep + destinations + action)
引擎NavController.observe(listener)backstack 变化订阅(替代 Compose mutableStateOf 观察)
引擎NavEngine.createSnapshot()产出快照并将离栈 entry 排进 outdated 队列
引擎NavEngine.onTransitionStart / onTransitionFinish / removeOutdatedHostEntries转场三段协议(对应三个同名 op)
引擎NavEngine.setHostLifecycleState宿主生命周期注入(对应 hostLifecycle op 五档)
引擎NavEngine.saveState / NavEngineSavedState全量状态保存与恢复(对应 saveState / restoreState op)
引擎hostEntryStore(id) / scopedEntryStore(scope)两级组件存储(对应 entryStore / scopedStore op)
NativeReimaginedDispatch / ReimaginedVersionC ABI JSON 导出
N-APIreimaginedDispatch(req) / reimaginedVersion()向 ArkTS 暴露能力
ArkTSsend(op, extra)触发操作、驱动转场协议并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const req = new BridgeRequest('navigate');
req.destinations = 'Second';
const res = JSON.parse(reimaginedDispatch(JSON.stringify(req))) as BridgeResponse;
// res.action === 'Navigate';播放右滑入转场前后调
// onTransitionStart → 动画 → onTransitionFinish → removeOutdated
"navigate" -> alive().first.navigate(parseList(json.str("destinations")))
"popUpTo"  -> alive().first.popUpTo(
    inclusive = json.bool("inclusive"),
    match = parseMatch(json.str("match"))
) { it == json.str("destination") }

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

五、关键决策说明

决策 1:引擎语义提取而不是等上游多平台版本

上游 1.5.0 没有 commonMain,material / hilt / sample 八个模块整体绑定 AGP;上游 1.6.0+ 虽做了 KMP 化,但版本断代(依赖链大改)且 Compose UI 在鸿蒙仍无宿主——等版本解决不了"框架耦合 + UI 无宿主"两个根本问题。提取语义的好处是边界清晰:栈操作逐字移植保证行为一致,宿主语义 1:1 重建保证可验证,Android 源文件零改动留盘保证可追溯。

决策 2:工具链钉 2.2.21-1.0.0,Gradle 降 8.14.1

引擎零第三方依赖,不存在"依赖制品线反推 Kotlin 版本"的压力(KStore 的场景),钉版只服务工具链本身:2.2.21 线与 cmp_qrose / telephoto / Decompose(旧线)一致,社区已审核;该线不支持 Gradle 9.x,wrapper 从 cmp_qrose 复制 8.14.1 四件套。JDK 21(Temurin)写入 gradle.properties 的 org.gradle.java.home,Windows 上 gradlew.bat 启动器另需 JAVA_HOME 环境变量。

决策 3:根构建替换(cmp_qrose 模式)+ buildSrc 摘出 + Android 模块留盘

上游根构建(nexusPublishing / publishing-config 签名发布 / AGP classpath / buildSrc 预编译脚本)对鸿蒙没有保留价值,整个替换为极简 ohos 构建。material / material3 / material-common(BottomSheetNavHost,绑定 AndroidX Material)、hilt(绑定 Dagger)、sample / samples-di(Android 应用)留盘不进 settings.gradle.kts——它们的存在方式只有这一种。buildSrc 因 Gradle 无条件自动编译而必须置空,这是 Android 库改 KMP 时和"改 settings"同级的必做步骤。

决策 4:转场协议显式化 + 组件存储字符串化

导航库的适配难点不在数据(栈就是列表)而在时序:转场期间谁存活、谁降级、何时销毁。把 AnimatedNavHost 的隐式时序显式化为三个 op,让 ArkTS 动画严格照协议驱动,是"引擎裁决语义、ArkTS 负责渲染"边界的成立条件。组件存储用 Map<String, String> 替代 ViewModelStore/SavedStateRegistry:值跨 JSON 桥不变形,"存储活着多久"的语义与上游完全一致(entry 存活期内有效、scope 最后关联 entry 离栈即清空)。

决策 5:JVM 交叉验证 + 测试语义移植

引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;36 项 jvmTest 是"内核没漂"的闸门。与 KStore"上游测试零改动"不同:上游测试绑定 JUnit5 + Truth + Android instrumentation(Parcelable CREATOR、ActivityScenario),不可能在 KMP commonTest 里编译——处置是把上游测试的断言语义移植到 kotlin-test(含 Match.Last 在重复目的地栈上的行为、setNewBackstack 外部修改保护、pop 空栈不动作为假等易错点),并为引擎新增转场协议全序列、作用域存活、save/restore 往返等导航特有的用例。

六、测试与验证

6.1 测试环境

本次验证使用 Windows 11(x64)、JDK 21(Temurin 21.0.12)、DevEco Studio 26.0、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64,手动签名安装)。

6.2 JVM 交叉验证

.\gradlew :reimagined:jvmTest

36 项用例全部通过(failures = 0):

测试类用例数钉住什么
NavControllerExtTest259 组栈操作语义(含 Match.First / Match.Last 在重复目的地栈上的差异、inclusive 边界、moveToTop 保持 entry 身份、空栈行为、setNewBackstack 外部修改保护、observe 退订、NavId 字符串往返)
NavEngineTest11转场协议全序列(pop 退出动画 RESUMED→STARTED→CREATED→DESTROYED)、作用域存储共享与销毁时序、entry 存储存活、宿主生命周期 min 级联、INITIALIZED→DESTROYED 跳变、save/restore 往返(身份与组件存储保持)、clear、快照结构

6.3 HAP 验证

:reimagined:compileKotlinJvm: BUILD SUCCESSFUL
:reimagined:jvmTest: BUILD SUCCESSFUL(36/36)
:reimagined:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: ReimaginedDispatch / ReimaginedVersion / libohosreimagined_symbols 均为全局 T 符号
BuildNativeWithCmake: finished(双 ABI 编出 libreimagined_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

entry-default-unsigned.hap 约 6.1 MB,内含双 ABI 各三件套:arm64-v8a 的 libreimagined_napi.so 约 65KB + libohosreimagined.so 约 1.6MB + libc++_shared.so 约 1.3MB,x86_64 的约 67KB + 1.4MB + 1.3MB(HAP 内 strip 后;debugShared 未 strip 时 KN so 约 2.98MB / 2.63MB,含 Kotlin/Native 运行时)。

6.4 功能用例

  1. 启动自动 bootstrap(backstack First),状态行显示 op=bootstrap · entries=1 · scopes=1 · action=Idle,版本握手 1.5.0-ohos.1 来自真实 Native 调用;
  2. navigate Second → 目的地舞台右滑入,First 徽章由 RESUMED 降 STARTED 再降 CREATED,Second 升 RESUMED——转场三段协议全程可见;
  3. pop → Second 退出动画期间出现在 outdated 列表(CREATED),removeOutdated 后 DESTROY 并从列表消失,其 entry 计数器随之清零;
  4. Tab2 给栈顶 entry 计数器 +3、shared 作用域计数器 +2,pop 后重进同名目的地:entry 计数器归零(新身份)、shared 计数器保持(作用域存活)——作用域语义的核心验收点;
  5. Tab3 依次 pause / stop:全栈徽章级联降档(STARTED → CREATED);destroy 后全部 DESTROY,重新 bootstrap 恢复会话;
  6. Tab4 saveState 显示 savedMeta 与 preview,几次 navigate/pop 改栈后 restoreState:栈结构、entry 身份、两级组件存储全部恢复;
  7. 快速连续 navigate 打断上一次转场:ArkTS 补发 finish + removeOutdated 后开新转场,无提前销毁、无僵尸条目;
  8. 空栈上 pop 返回 changed=false,错误操作(未 bootstrap、未知 op)走红字错误通道。

6.5 验证结论

栈操作引擎、生命周期状态机、作用域存储、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的导航会话:栈操作全集、转场协议、作用域存活与销毁、宿主生命周期级联和状态恢复闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/compose-navigation-reimagined.git
cd compose-navigation-reimagined
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
.\gradlew :reimagined:compileKotlinOhosArm64 :reimagined:compileKotlinOhosX64      # klib
.\gradlew :reimagined:jvmTest                                                     # 36 项交叉验证
.\gradlew :example:nativeApp:linkDebugSharedOhosArm64 `
           :example:nativeApp:linkDebugSharedOhosX64                              # 双 ABI so 回填
cd example\harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug
hdc install -r entry\build\default\outputs\default\entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.reimagined -m entry
hdc shell hilog | findstr Reimagined

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算适配,必须让 :reimagined 真正编译成 ohosArm64 / ohosX64 动态库——本项目的验收核心是转场协议与作用域语义,页面只是把它们画出来;
  2. kotlin.jvm.JvmInline 在 ohos Kotlin/Native 上 unresolved:上游 NavHostId 是 @JvmInline value class,JVM 侧去掉注解又报"Value classes without ‘@JvmInline’ are not yet supported"——KMP 公共代码里的 value class 跨 JVM/ohos 双 target 只能改普通类;
  3. Match.Last 的语义是 indexOfLast(从栈底数最后一个匹配项),重复目的地栈 [a, b, c, b] 上 popUpTo { b } 的匹配项就是栈顶自己,什么都不弹——最初按直觉写的测试期望全错,移植测试必须以上游源码为准而不是语义直觉;
  4. pop 退出动画期间的生命周期序列是 RESUMED → STARTED(onTransitionStart)→ CREATED(onTransitionFinish)→ DESTROYED(removeOutdated),不是一步到 CREATED——上游 onTransitionStart / onTransitionFinish 的两段压档各有用途,漏掉任何一段测试就漂;
  5. setNewBackstack 是 NavController 的成员函数不是扩展函数,桥文件里按扩展写了 import 直接 unresolved——Kotlin/Native 编译器不会为这种混淆给更友好的提示;
  6. 扁平 MiniJson 只支持标量字符串,列表参数(destinations / scopeRules / keep)全部用逗号分隔字符串编码;saveState 的结构化往返单写了一个 60 行的 FullJson 递归解析器,响应方向不需要它(ArkTS 有真 JSON.parse);
  7. gradlew.bat 启动器只认 JAVA_HOME 环境变量,gradle.properties 里的 org.gradle.java.home 管不到启动器本身——Windows 上两条都要配;
  8. hvigor daemon 缓存启动时的 PATH:PackageHap 阶段报 spawn java ENOENT(错误码 00308018),把 DevEco 自带 JBR 的 bin 加进 PATH 后必须 hvigorw --stop-daemon 重启 daemon 才生效;
  9. buildSrc 被 Gradle 无条件自动编译,上游预编译脚本插件(AGP 8.1.0 + Kotlin 1.9.0)哪怕没有任何模块引用也会拖垮整个构建——Android 库切 KMP 时置空 buildSrc 与重写 settings 同级重要;
  10. 根构建的 .gitignore 是根锚定模式,盖不住嵌套目录——example/harmonyApp/.idea、entry/libs、entry/build、.cxx、oh_modules、example/nativeApp/build 要单独追加,否则构建产物混进提交;
  11. build-profile.json5 的 signingConfigs 条目缺 material 字段会被 hvigor schema 拒绝(报 00303038)——仓库内留空数组且 product 不引用签名,勾选自动签名后由 IDE 回填完整配置;
  12. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI(真机 arm64、真机 x86_64),每个 ABI 三件套缺一不可;KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();签名材料由 DevEco 自动写入,提交前用 git update-index --skip-worktree 保护,证书绝不进入 AtomGit。

8.2 已知问题

  • Compose UI(NavHost / AnimatedNavHost / DialogNavHost / BottomSheetNavHost / NavBackHandler)未移植:社区 ohos Compose 线(1.12.0-0.1.0-07)尚无 composition 宿主,转场行为由 ArkUI 复刻,语义由引擎裁决;
  • reimagined-material / reimagined-material3 / reimagined-material-common(BottomSheetNavHost 三件)与 reimagined-hilt 留盘未适配(绑定 AndroidX Material / Dagger);
  • NavScopeSpec 是 Kotlin 函数值,跨不了 C ABI:桥上退化为声明式 scopeRules 字符串(目的地精确匹配),上游任意谓词的作用域判定留在 Kotlin 侧;
  • Parcelable 进程死亡恢复映射为显式 saveState / restoreState op:语义等价(entry 身份与组件存储保持),触发时机从系统托管变为手动/宿主托管;
  • 桥持有单导航会话(一个 controller + engine world),多 NavHost 嵌套(上游 nested navigation)未在桥上建模;
  • 宿主生命周期由 ArkTS 手动驱动(hostLifecycle op),未接系统应用生命周期回调;
  • 深链(上游 sample 的 deeplink intent-filter)未演示;
  • android 侧全部目标与源文件留盘不进构建,发布、签名发布链路剥离,本 fork 不发 Maven Central;HAP 安装必须使用与 bundle name org.terminator.ohos.reimagined 匹配的签名。

8.3 未来优化方向

  • 接入系统应用生命周期回调自动驱动 hostLifecycle,替代手动按钮;
  • 把 NavController.observe 的状态推送跨 N-API 回表(线程安全回调),替代操作后读回;
  • 复刻 DialogNavHost / BottomSheetNavHost 的 ArkUI 宿主(弹层导航 + Material 动效);
  • 跟进上游 1.6+ 多平台版本,评估 commonMain 直接真编译替代手工提取的可行性;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;补充真机(x86_64)验收矩阵和自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 reimagined(纯 Android,28 文件)
    → 依赖度盘点(11 文件平台耦合分档处置)
    → 工具链对齐 Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(cmp_qrose 先例线)
    → 根构建替换 + buildSrc 摘出(Android 模块留盘)
    → 引擎语义提取(栈操作逐字 + 宿主状态 1:1 重建)
    → 转场协议显式化(三段 op 时序契约)
    → C ABI(@CName × 2)+ 扁平 JSON
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析 + 转场协议驱动
    → ArkUI 真机四 Tab 验收(栈操作 / 作用域 / 生命周期 / 状态恢复)

9.2 封装层次

  • 引擎层:NavEntry / NavId / NavBackstack / NavAction / Match 与 9 组栈操作扩展(上游逐字移植);
  • 宿主状态层:NavEngine + NavEngineEntry——生命周期 min 状态机、outdated 转场队列、作用域归属与清理、save/restore(上游 NavHostStateImpl 语义重建);
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、JSON 校验、操作分发、转场协议驱动、日志与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 路线选择取决于耦合形态而不是库类型:KStore 的耦合在 kotlinx 制品(制品线补齐即真编译),导航库的耦合在 AndroidX 框架与 Compose 宿主(制品线再全也解不了)——同一个"KMP 适配",前者答案是"换仓库坐标",后者答案是"语义提取 + 协议显式化",选错路线整个项目返工;
  2. 隐式时序必须显式化成协议:AnimatedNavHost 里藏在组合层的转场生命周期联动,跨语言边界后必须变成 onTransitionStart → 动画 → onTransitionFinish → removeOutdated 这样的可调用契约,并让渲染方严格执行——协议化是 UI 框架语义跨桥的唯一通用解;
  3. 交叉验证要测语义而不是测文件:上游测试绑定平台框架(JUnit5 + Truth + instrumentation)时,把断言语义移植到 kotlin-test 同样有说服力——36 项用例钉住的是 Match.Last 在重复栈上的行为、pop 退出动画的生命周期全序列这些真正容易漂的点。

9.4 适配成果

当前适配已完成:

  • compose-navigation-reimagined 1.5.0 导航引擎的双 ABI Kotlin/Native 编译(栈操作逐字移植 + NavHostState 语义 1:1 重建,上游 Android 源码零改动留盘);
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • 20 op 扁平 JSON 桥协议(9 组栈操作 + 转场三段协议 + 两级组件存储 + 宿主生命周期 + save/restore);
  • DevEco Stage ArkUI 四 Tab 验收页面(栈操作转场复刻、作用域计数器、生命周期级联、状态恢复,1393 行);
  • 36 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so,约 6.1MB)、签名、设备安装和真机验证;
  • AtomGit 项目文档、OpenHarmony README(中英双份、中文主文档 26 节)和本地效果图。

适配过程 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

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

更多推荐