开源鸿蒙平台 CMP 三方库「compose-navigation-reimagined」适配全流程
本文记录
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 重建为纯 KotlincommonMain,编译成 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 Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| JDK | 21(Temurin 21.0.12,org.gradle.java.home 写入 gradle.properties) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 8.14.1(wrapper 从 cmp_qrose 同款替换;2.2.21 旧线必须配 8.x,9.x 起不兼容) |
| 引擎依赖 | commonMain 零第三方依赖(纯 Kotlin);commonTest 仅 kotlin("test") |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 26.0.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.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 UI | NavHost / 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(旧线)同款,社区审过、.konanohos 工具链(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 方法 | 覆盖内核 |
|---|---|---|
ReimaginedDispatch | reimaginedDispatch(req) | bootstrap 建会话 + 9 组栈操作 + 转场三段 + 两级存储 + 宿主生命周期 + save/restore |
ReimaginedVersion | reimaginedVersion() | 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.toml | Kotlin 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.kt | 2 个 C ABI、20 op 分发、MiniJson、错误通道 |
nativeApp/.../Json.kt | JsonOut 手写序列化 + FullJson 递归解析(saveState 往返) |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 四 Tab 验收页面 + 转场协议驱动 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(26 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | navController(initialBackstack) | 建控制器,栈序自底向顶(对应桥 bootstrap) |
| 引擎 | navigate / pop / popAll / popUpTo / replaceLast / replaceAll / replaceUpTo / moveToTop | 9 组栈操作扩展,与上游逐字对齐(对应同名 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) |
| Native | ReimaginedDispatch / ReimaginedVersion | C ABI JSON 导出 |
| N-API | reimaginedDispatch(req) / reimaginedVersion() | 向 ArkTS 暴露能力 |
| ArkTS | send(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):
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
NavControllerExtTest | 25 | 9 组栈操作语义(含 Match.First / Match.Last 在重复目的地栈上的差异、inclusive 边界、moveToTop 保持 entry 身份、空栈行为、setNewBackstack 外部修改保护、observe 退订、NavId 字符串往返) |
NavEngineTest | 11 | 转场协议全序列(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 功能用例
- 启动自动
bootstrap(backstackFirst),状态行显示op=bootstrap · entries=1 · scopes=1 · action=Idle,版本握手1.5.0-ohos.1来自真实 Native 调用; navigate Second→ 目的地舞台右滑入,First 徽章由 RESUMED 降 STARTED 再降 CREATED,Second 升 RESUMED——转场三段协议全程可见;pop→ Second 退出动画期间出现在 outdated 列表(CREATED),removeOutdated后 DESTROY 并从列表消失,其 entry 计数器随之清零;- Tab2 给栈顶 entry 计数器 +3、shared 作用域计数器 +2,
pop后重进同名目的地:entry 计数器归零(新身份)、shared 计数器保持(作用域存活)——作用域语义的核心验收点; - Tab3 依次
pause/stop:全栈徽章级联降档(STARTED → CREATED);destroy后全部 DESTROY,重新 bootstrap 恢复会话; - Tab4
saveState显示 savedMeta 与 preview,几次 navigate/pop 改栈后restoreState:栈结构、entry 身份、两级组件存储全部恢复; - 快速连续 navigate 打断上一次转场:ArkTS 补发 finish + removeOutdated 后开新转场,无提前销毁、无僵尸条目;
- 空栈上
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 踩坑复盘
- 只改 ArkTS 页面不算适配,必须让
:reimagined真正编译成ohosArm64/ohosX64动态库——本项目的验收核心是转场协议与作用域语义,页面只是把它们画出来; 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 只能改普通类;Match.Last的语义是indexOfLast(从栈底数最后一个匹配项),重复目的地栈[a, b, c, b]上popUpTo { b }的匹配项就是栈顶自己,什么都不弹——最初按直觉写的测试期望全错,移植测试必须以上游源码为准而不是语义直觉;- pop 退出动画期间的生命周期序列是 RESUMED → STARTED(onTransitionStart)→ CREATED(onTransitionFinish)→ DESTROYED(removeOutdated),不是一步到 CREATED——上游
onTransitionStart/onTransitionFinish的两段压档各有用途,漏掉任何一段测试就漂; setNewBackstack是NavController的成员函数不是扩展函数,桥文件里按扩展写了 import 直接 unresolved——Kotlin/Native 编译器不会为这种混淆给更友好的提示;- 扁平 MiniJson 只支持标量字符串,列表参数(destinations / scopeRules / keep)全部用逗号分隔字符串编码;saveState 的结构化往返单写了一个 60 行的 FullJson 递归解析器,响应方向不需要它(ArkTS 有真 JSON.parse);
gradlew.bat启动器只认JAVA_HOME环境变量,gradle.properties里的org.gradle.java.home管不到启动器本身——Windows 上两条都要配;- hvigor daemon 缓存启动时的 PATH:
PackageHap阶段报spawn java ENOENT(错误码 00308018),把 DevEco 自带 JBR 的bin加进 PATH 后必须hvigorw --stop-daemon重启 daemon 才生效; buildSrc被 Gradle 无条件自动编译,上游预编译脚本插件(AGP 8.1.0 + Kotlin 1.9.0)哪怕没有任何模块引用也会拖垮整个构建——Android 库切 KMP 时置空 buildSrc 与重写 settings 同级重要;- 根构建的
.gitignore是根锚定模式,盖不住嵌套目录——example/harmonyApp/.idea、entry/libs、entry/build、.cxx、oh_modules、example/nativeApp/build要单独追加,否则构建产物混进提交; build-profile.json5的signingConfigs条目缺material字段会被 hvigor schema 拒绝(报00303038)——仓库内留空数组且 product 不引用签名,勾选自动签名后由 IDE 回填完整配置;- 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/restoreStateop:语义等价(entry 身份与组件存储保持),触发时机从系统托管变为手动/宿主托管;- 桥持有单导航会话(一个 controller + engine world),多 NavHost 嵌套(上游 nested navigation)未在桥上建模;
- 宿主生命周期由 ArkTS 手动驱动(
hostLifecycleop),未接系统应用生命周期回调; - 深链(上游 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 三条经验
- 路线选择取决于耦合形态而不是库类型:KStore 的耦合在 kotlinx 制品(制品线补齐即真编译),导航库的耦合在 AndroidX 框架与 Compose 宿主(制品线再全也解不了)——同一个"KMP 适配",前者答案是"换仓库坐标",后者答案是"语义提取 + 协议显式化",选错路线整个项目返工;
- 隐式时序必须显式化成协议:
AnimatedNavHost里藏在组合层的转场生命周期联动,跨语言边界后必须变成onTransitionStart → 动画 → onTransitionFinish → removeOutdated这样的可调用契约,并让渲染方严格执行——协议化是 UI 框架语义跨桥的唯一通用解; - 交叉验证要测语义而不是测文件:上游测试绑定平台框架(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。
参考文档
更多推荐


所有评论(0)