开源鸿蒙平台 CMP 三方库「compose-destinations」适配全流程
本文记录
compose-destinations接入 OpenHarmony 的完整过程,覆盖 KMP/Compose 三方库盘点、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。
compose-destinations是一个用 KSP 注解处理生成类型安全导航代码的 Compose 库,上游为纯 Android 工程。本次适配把它的路由模型、18 种类型安全参数编解码器、导航图遍历和结果返回语义提取为纯 Kotlin 内核,进入 OpenHarmony ARM64 运行时;KSP 处理器保持宿主 JVM 工具不变,ArkUI 页面作为真机验收宿主,Compose Multiplatform(CMP)应用后续可通过同一个内核依赖复用这些导航契约。
项目地址: AtomGit/oh-tpc/compose-destinations
上游仓库: rafaelcosta/compose-destinations
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
compose-destinations 为 Jetpack Compose 提供 @Destination 注解驱动的类型安全导航:路由模式建模、导航参数的强类型编解码、导航图树遍历和带结果返回的回退导航。这些能力集中在库的纯 Kotlin 部分,与 UI 框架无关;而 DestinationsNavHost、@Composable Content 等则是 Compose 载体。OpenHarmony 应用同样需要这套路由契约,但上游默认构建不会生成 OpenHarmony 的 Kotlin/Native 变体。
如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明路由模型与参数编解码实现已经在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证提取内核、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 工程形态缺失 | 上游 13 个模块全部是 com.android.library/application(AGP),配置期就依赖 Android SDK,整仓无法加 ohos 目标。 |
| 载体依赖密集 | 核心模块约 59% 的文件 import androidx.compose/androidx.navigation,参数编解码公开签名大量出现 android.os.Bundle。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony Native SDK、LLVM 和 Gradle 插件需要匹配。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 编码契约敏感 | null 哨兵 %02null%03、空串哨兵 %02%03、%2C 逗号转义是路由字符串协议的一部分,改了就与 KSP 生成代码失去一致。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在内核提取、C ABI/N-API 桥接和 ArkUI 验收层。路由与参数语义仍由提取的公共实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
示例提供四个可观察场景,对应四个验收 Tab:
- 路由栈:
build把强类型参数拼装为Direction填充路由,navigate/singleTop/navigateUp/pop/popRoute/clearBackStack驱动返回栈; - 参数编解码:18 种
DestinationsNavType的serializeValue/parseValue/roundtrip,覆盖 null、空串、含逗号、含空格、中文和 URL; - 导航图:
describe树、allDestinations/allRoutes/findDestination/matchRoute/contains/startDestination/defaultStartDirection/requireGraphArgs; - 结果返回:
setResult/navigateBack触发NavResult.Value/Canceled投递,getOr提供兜底语义。
桥接响应包含:
| 字段 | 作用 |
|---|---|
ok | 是否成功,异常统一转 {"ok":false,"error":...}。 |
op | 当前操作名,便于日志对账。 |
top/entries/count/canPop | 返回栈快照。 |
output/parsed/isNull/stable | 编解码结果与 roundtrip 稳定性。 |
pattern/args | matchRoute 命中的路由模式与还原出的参数对象。 |
kind/value | NavResult 的类型与负载。 |
events | 本次会话事件序列。 |
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 路由模型/参数编解码/图遍历/结果语义全部来自上游纯 Kotlin 源码的提取层,算法逐字保留。 |
| 平台目标 | 独立 example 构建增加 ohosArm64 + ohosX64,生成双 ABI 动态库。 |
| 桥接稳定 | 使用 5 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址。 |
| CMP 兼容 | KSP 处理器保持宿主 JVM 工具;CMP 工程后续可复用同一内核。 |
| UI 完整 | 四 Tab 全接口操作、快捷参数、统计区、错误横幅和事件日志。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。路由与参数语义由提取内核在 OpenHarmony Native 运行时执行,ArkUI 只负责真机验收。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点上游模块形态、AGP 边界和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── 独立 example 构建、鸿蒙 Kotlin 工具链和双 ohos 目标
第 3 阶段:导航场景建模 ── 内核提取、Demo 镜像生成代码、路由匹配和返回栈会话
第 4 阶段:原生桥接 ── C ABI、C++ N-API、CMake 和类型声明
第 5 阶段:ArkUI 宿主 ── 四 Tab 工作台、快捷参数、统计区和错误边界
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
compose-destinations/ 上游核心库(AGP,零改动)
compose-destinations-ksp/ KSP 处理器(纯 JVM,零改动,留在宿主机)
compose-destinations-codegen/ 代码生成逻辑(纯 JVM,零改动)
example/ 独立 Gradle 构建入口(ohos_compose_destinations)
example/nativeApp/ 内核提取 + C ABI 动态库
example/harmonyApp/ DevEco Stage 工程、N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 适配说明文档
根构建与上游 8 个模块完全不动,鸿蒙适配只新增 example/ 与文档,保证与上游同步零冲突。Gradle 和 Hvigor 可以分开执行。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | 独立 example 构建使用鸿蒙 Kotlin 2.2.21-1.0.0(eazytec Maven) |
| Gradle / JDK | 8.14.1 / 21 |
| OpenHarmony 目标 | ohosArm64 + ohosX64 |
| DevEco product | 6.0.1(21) |
| DevEco Studio | 6.0.1 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | com.ramcosta.composedestinations.ohos |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 页面边界
标题区 compose-destinations · OpenHarmony / 版本行
路由栈 Tab 目的地选择、参数输入、build/navigate/pop 系列和返回栈列表
参数编解码 Tab 18 种类型选择、特殊值快捷、serialize/parse/roundtrip
导航图 Tab describe 树、遍历、findDestination、matchRoute、图参数
结果返回 Tab setResult/navigateBack/receive/getOr 和验收剧本
页面启动时通过 destinationsVersion() 读取版本,并对四个 Tab 各发起一次初始快照调用。
第 2 阶段:目标与依赖打通
2.1 独立构建与 ohos 目标
上游是纯 Android 工程,不能在根构建加目标;适配在 example/settings.gradle.kts 建立独立构建,鸿蒙工具链仓库置前:
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_compose_destinations"
include(":nativeApp")
gradle/libs.versions.toml 只保留 kotlin = "2.2.21-1.0.0" 一个插件项。
2.2 构建内核和 Native 产物
./gradlew :nativeApp:linkDebugSharedOhosArm64 # 真机 arm64
./gradlew :nativeApp:linkDebugSharedOhosX64 # 模拟器 x86_64
链接任务自动把产物与 konan 里匹配 triple 的 libc++_shared.so 回填:
example/harmonyApp/entry/libs/arm64-v8a/libohosdestinations.so ≈ 9.2 MB (debug)
example/harmonyApp/entry/libs/arm64-v8a/libc++_shared.so
example/nativeApp/build/bin/ohosArm64/debugShared/include/libohosdestinations_api.h
2.3 nativeApp 模块
kotlin {
applyDefaultHierarchyTemplate()
ohosArm64 { binaries.sharedLib { baseName = "ohosdestinations" } }
ohosX64 { binaries.sharedLib { baseName = "ohosdestinations" } }
sourceSets {
ohosMain.dependencies {} // 零第三方依赖,产物自包含
}
}
内核源码放在 ohosMain 源集,包名保持上游 com.ramcosta.composedestinations,每个文件头部注明上游路径与差异清单。
第 3 阶段:导航场景建模
3.1 JSON 契约
DemoDestinations.kt(手写镜像 KSP 生成代码)
↓ Direction 填充路由 / argsFrom 还原参数
NavHostSession.kt(路由匹配 + 返回栈 + 结果投递)
↓ op 分发
Bridge.kt(@CName)
↓ UTF-8 JSON
N-API destinationsRoute/...
↓
ArkUI Index.ets
页面不复制路由判断,每个结果由提取内核生成。
3.2 结果数据类
public data class ProfileScreenNavArgs(
val id: Int,
val name: String? = null,
val premium: Boolean? = null,
)
object ProfileScreenDestination : TypedDestinationSpec<ProfileScreenNavArgs>, DemoDestinationWithSpecs {
override val route: String = "profile/{id}?name={name}&premium={premium}"
override val baseRoute: String = "profile"
}
Demo 目录包含五类目的地与三个图:
private val catalog = listOf( // greeting/settings 无参数 Direction
"profile" to "profile/{id}?name={name}&premium={premium}",
"items" to "items/{ids}?filter={filter}&type={type}", // ArrayList + enum
"tags" to "tags/{tags}", // ArrayList<String>
)
// NestedNavGraph(嵌套图) / SearchNavGraph(带图参数) / RootNavGraph(NavHost 根图)
3.3 运行逻辑
填充路由(内核 invoke 契约,空格/中文等按 encodeForRoute 百分号编码):
operator fun invoke(id: Int, name: String? = null): Direction = Direction(
buildString {
append(baseRoute).append('/').append(intNavType.serializeValue(id))
if (name != null) append("?name=").append(stringNavType.serializeValue(name))
}
)
参数编解码(上游算法逐字提取,null 走哨兵):
override fun parseValue(value: String): Int? =
if (value == DECODED_NULL) null else IntType.parseValue(value)
override fun serializeValue(value: Int?): String =
value?.toString() ?: ENCODED_NULL // "%02null%03"
结果返回(NavResult 密封类语义):
val result: NavResult<String> = from.pendingResult
?.let { NavResult.Value(it) }
?: NavResult.Canceled
receivedResults[to.destination.route] = result
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libdestinations_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosdestinations.so)
│ spec/navargs/navgraph/result 内核
▼
UTF-8 JSON + DisposeString 归还
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制路由语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("DestinationsVersion")
public fun destinationsVersion(): String = try {
versionJson()
} catch (t: Throwable) {
fail(t)
}
@CName("DestinationsRoute")
public fun destinationsRoute(request: String): String = try {
handleRoute(request) // build/navigate/pop/popRoute/... JSON 进 JSON 出
} catch (t: Throwable) {
fail(t)
}
// 同构:DestinationsNavArgs / DestinationsNavGraph / DestinationsResult
每个导出都被 try/catch 包住,Kotlin 异常以 {"ok":false,"error":...} 返回,绝不跨 FFI 边界抛出;返回字符串由 Kotlin 运行时通过 libohosdestinations_symbols()->DisposeString 释放。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"destinationsVersion", nullptr, NapiVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"destinationsRoute", nullptr, NapiRoute, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
// destinationsNavArgs / destinationsNavGraph / destinationsResult 同构
};
nm_modname = "destinations_napi" 与 ArkTS import 'libdestinations_napi.so'、index.d.ts 三处对齐。N-API 只负责参数类型检查、字符串转换和释放,不实现路由语义。
4.4 CMake 和类型声明
add_library(ohosdestinations SHARED IMPORTED)
set_target_properties(ohosdestinations PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/libohosdestinations.so"
IMPORTED_NO_SONAME TRUE)
target_link_options(destinations_napi PRIVATE "-Wl,--no-undefined" "-Wl,-rpath,\$ORIGIN")
target_link_libraries(destinations_napi PUBLIC
libace_napi.z.so libhilog_ndk.z.so ohosdestinations)
export const destinationsVersion: () => string;
export const destinationsRoute: (request: string) => string;
export const destinationsNavArgs: (request: string) => string;
export const destinationsNavGraph: (request: string) => string;
export const destinationsResult: (request: string) => string;
第 5 阶段:ArkUI 宿主
5.1 加载目录和运行结果
import {
destinationsVersion, destinationsRoute, destinationsNavArgs,
destinationsNavGraph, destinationsResult
} from 'libdestinations_napi.so';
const info = JSON.parse(destinationsVersion()) as VersionInfo;
this.versionText = `${info.fork} · 上游 ${info.upstream} · Kotlin ${info.kotlin}`;
this.routeEntries = (JSON.parse(destinationsRoute('{"op":"snapshot"}')) as RouteInfo).entries ?? [];
每个 Tab 一组 refresh* 方法统一处理 ok/error 与字段回填。
5.2 页面交互
路由栈 Tab 提供目的地与参数快捷芯片(id=42、ids=1,2,3|type=PREMIUM 等)和 build/navigate/singleTop/popRoute/clearBackStack 操作;参数 Tab 提供空串、null 标记、含逗号、中文、URL 特殊值快捷;导航图 Tab 提供遍历与匹配操作;结果 Tab 按剧本走 setResult → navigateBack 的 Value/Canceled 投递。所有结果来自 Native 调用,ArkTS 不重新实现路由语义。
5.3 对比度和错误边界
选中 Tab 与选中芯片使用深蓝色和白字,操作芯片使用浅蓝底深蓝字,正文和辅助文字使用更深的灰色,事件日志使用等宽字体。Native 异常统一显示在当前 Tab 顶部错误横幅,事件日志倒序展示最近 60 条。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/src/ohosMain/kotlin/
│ ├── com/ramcosta/composedestinations/ 提取内核(spec/navargs/result/utils)
│ └── example/nativeapp/ Bridge / MiniJson / Demo 镜像 / 会话
└── harmonyApp/
├── entry/src/main/cpp/CMakeLists.txt
├── entry/src/main/cpp/napi_init.cpp
├── entry/src/main/cpp/include/libohosdestinations_api.h
└── entry/src/main/ets/pages/Index.ets
6.2 构建、签名和安装
export JAVA_HOME="/path/to/jdk-21"
cd example
./gradlew :nativeApp:linkDebugSharedOhosArm64
# DevEco 打开 example/harmonyApp:
# File → Project Structure → Signing Configs → Automatically generate signature
hvigorw assembleHap --mode module -p product=default
hdc list targets -v
hdc -t FMR0223825079397 install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t FMR0223825079397 shell aa start \
-a EntryAbility -b com.ramcosta.composedestinations.ohos
四、完整代码对照
4.1 整体架构
内核提取(spec / navargs / result / utils)
│ 路由模式 / 18 种编解码 / 图遍历 / NavResult
▼
DemoDestinations.kt + NavHostSession.kt
│ invoke(Direction) / argsFrom / 路由匹配 / 返回栈
▼
Bridge.kt
│ DestinationsVersion/Route/NavArgs/NavGraph/Result
▼
libdestinations_napi.so
│ destinations* 五个 N-API 方法
▼
Index.ets
│ JSON parse + state update
▼
ArkUI 真机页面
4.2 文件清单
| 文件 | 职责 |
|---|---|
example/settings.gradle.kts | 独立构建入口与鸿蒙工具链仓库 |
example/nativeApp/build.gradle.kts | 双 ohos 目标、sharedLib 和 so 回填任务 |
example/nativeApp/.../spec/*.kt(11 文件) | 路由模型内核(逐字或载体替换) |
example/nativeApp/.../navargs/*.kt(15 文件) | 18 种参数编解码内核与 Uri.encode 重实现 |
example/nativeApp/.../result/NavResult.kt | Value/Canceled 密封类与 getOr |
example/nativeApp/.../utils/SpecExtensions.kt | 图遍历算法 |
example/nativeApp/.../Bridge.kt | 5 个 @CName C ABI,JSON 进 JSON 出 |
example/nativeApp/.../DemoDestinations.kt | 手写镜像 KSP 生成代码 |
example/nativeApp/.../NavHostSession.kt | 路由匹配与返回栈会话 |
example/harmonyApp/.../napi_init.cpp | N-API 导出与参数检查 |
example/harmonyApp/.../CMakeLists.txt | IMPORTED so、rpath 与三件套并排 |
example/harmonyApp/.../Index.ets | ArkUI 四 Tab 验收页面 |
README.OpenHarmony.md / _CN.md | 双语适配说明(含逐文件对照与踩坑表) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 内核 | TypedRoute.invoke(navArgs) | 生成填充参数的 Direction |
| 内核 | TypedDestinationSpec.argsFrom(RouteArgs) | 从参数表还原强类型参数对象 |
| 内核 | DestinationsNavType.serializeValue/parseValue | 路由字符串编解码(含哨兵) |
| 内核 | NavGraphSpec.findDestination | 按路由 pattern 精确查找 |
| 内核 | NavHostGraphSpec.defaultStartDirection | 起始方向解析(上游算法) |
| 内核 | NavResult.Value/Canceled + getOr | 结果返回语义 |
| Native | DestinationsRoute | 路由构建与返回栈 JSON |
| Native | DestinationsNavArgs | 编解码操作 JSON |
| Native | DestinationsNavGraph | 图遍历与匹配 JSON |
| Native | DestinationsResult | 结果投递 JSON |
| N-API | destinationsVersion 等 5 个 | 向 ArkTS 暴露数据 |
| ArkTS | routeOp/argsOp/graphOp/resultOp | 触发操作并刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const info = JSON.parse(destinationsRoute(
JSON.stringify({ 'op': 'build', 'destination': 'profile', 'params': 'id=42|name=alice' })
)) as RouteInfo;
@CName("DestinationsRoute")
public fun destinationsRoute(request: String): String = try { handleRoute(request) } catch (t: Throwable) { fail(t) }
两者之间只传输 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:根构建零改动,独立 example 构建
上游 13 个模块全是 AGP 工程,在根构建加 ohos 目标不可行;独立构建让 Android 侧完全不受影响,与上游同步零冲突。
决策 2:内核提取而非整库移植
核心模块约 59% 文件依赖 Compose/Navigation,全部移植等于在鸿蒙上重造 androidx。提取零依赖的 spec/navargs/result/utils(34 个文件基础),Compose 载体由 ArkTS Demo 承接,保住类型安全算法本身。
决策 3:Bundle → Map 载体替换,算法逐字保留
argsFrom(bundle) 换成 argsFrom(RouteArgs)(Map<String, Any?>),null 哨兵、空串哨兵、%2C 转义与 Uri.encode 默认行为逐字保留——这些是路由字符串协议,不容漂移。
决策 4:JSON 作为跨语言数据契约
ohosMain.dependencies {} 保持为空(连序列化库都不引,JSON 用手写 MiniJson),产物自包含;JSON 边界清楚可调试,允许后续增加字段而不暴露内部对象布局。
决策 5:ArkUI 作为验收宿主,KSP 留在宿主机
代码生成是编译期行为,:compose-destinations-ksp 与 :codegen 是纯 JVM 工具,零改动继续运行;鸿蒙侧用手写镜像生成代码证明内核契约可按同样方式实现。
六、测试与验证
6.1 测试环境
本次真机验证使用 macOS、JDK 21、DevEco Studio、OpenHarmony ARM64 Native SDK 和 HUAWEI Mate 60 Pro,设备序列号为 FMR0223825079397。
6.2 编译与符号验证
cd example
./gradlew :nativeApp:compileKotlinOhosArm64 :nativeApp:compileKotlinOhosX64
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
nm -gU nativeApp/build/bin/ohosArm64/debugShared/libohosdestinations.so | grep Destinations
nm 确认 DestinationsVersion/Route/NavArgs/NavGraph/Result 与 libohosdestinations_symbols 全部为 T 符号;copyKnSo* 任务把 so 与 libc++_shared.so 回填双 ABI 目录。
6.3 HAP 验证
BuildNativeWithCmake: finished
BuildNativeWithNinja: finished
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
6.4 功能用例
- 启动后版本行显示
2.3.0-ohos.1 · 上游 2.3.0 · Kotlin 2.2.21-1.0.0,返回栈初始化为greeting; - 路由栈 Tab:
profile + id=42|name=alice构建profile/42?name=alice,navigate 入栈、singleTop 去重、popRoute/clearBackStack 按语义弹栈; - 参数编解码 Tab:
hello world序列化为hello%20world,%02null%03解析为 null 且isNull=true,stringArrayroundtripstable=true,非法 int 显示内核原始报错; - 导航图 Tab:
allRoutes返回 7 条 pattern,matchRoute profile/42?name=alice还原ProfileScreenNavArgs(id=42, name=alice, premium=null),requireGraphArgs空参触发上游报错文案; - 结果返回 Tab:
setResult → navigateBack收到Value,不设结果直接返回收到Canceled,getOr兜底生效; - 事件日志倒序记录全部操作轨迹,Native 异常显示在错误横幅。
6.5 验证结论
提取内核、Native 动态库、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在 HUAWEI Mate 60 Pro 上成功安装并进入前台,页面展示了真实路由构建、参数编解码、图遍历与结果返回结果。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/compose-destinations.git
cd compose-destinations
export JAVA_HOME="/path/to/jdk-21"
(cd example && ./gradlew :nativeApp:linkDebugSharedOhosArm64)
cd example/harmonyApp
hvigorw assembleHap --mode module -p product=default
hdc -t FMR0223825079397 install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t FMR0223825079397 shell aa start \
-a EntryAbility -b com.ramcosta.composedestinations.ohos
八、遗留问题与改进方向
8.1 踩坑复盘
- Gradle 8.14.1 不能用 JDK 25 启动(报裸版本号错误),必须钉 JDK 21;
- Kotlin/Native 公共库没有
ArrayList(size) { init }构造,需要显式循环填充; - 上游枚举 NavType 用
java.lang.Class反射,KN 上无 Java 反射,改传Array<E>枚举常量; encodeForRoute不能换成URLEncoder(空格会变+),按android.net.Uri.encode默认字符集重实现;- DevEco 自动签名会把本机证书路径写进
build-profile.json5,入库前必须还原为空signingConfigs。
8.2 已知问题
- Kotlin/Native 工具链和 DevEco SDK 需要匹配;
- 本适配不编译 Compose:
@Composable内容、动画、Bottom Sheet 由 ArkTS Demo 承接; - KSP 生成代码仍面向
androidx.navigation,ohos 侧为内核 + 镜像验证; - 返回栈为 Demo 层重建,不含 saved state 保存/恢复;
- 深链(NavDeepLink)与 Parcelable/Serializable 参数类型未桥接;
- 当前 ArkUI 示例是单页面宿主,不包含 CMP UI。
8.3 未来优化方向
- 增加 JVM 交叉验证单测:同一批 NavType 用例在 Android 单测与 ohos 桥输出逐字符比对;
- 待社区 Compose ohos 制品稳定后,把
style/@Composable Content按上游原样接回; - 为 codegen 增加 ohos 目标常量,让 KSP 直接生成鸿蒙侧代码;
- 在 CI 中加入 Native 链接、ELF 符号检查和 HAP 构建;
- 补充不同设备 ABI、API 版本的路由匹配验收矩阵。
九、总结
9.1 核心难点回顾
上游纯 Kotlin 内核(spec/navargs/result/utils)
→ 独立 example 构建 + ohosArm64/ohosX64 Kotlin/Native
→ C ABI(@CName ×5)
→ C++ N-API(libdestinations_napi.so)
→ ArkTS JSON 解析
→ ArkUI 四 Tab 真机页面
9.2 封装层次
- 内核层:提供路由模式、Direction、18 种参数编解码、图遍历和 NavResult 语义;
- Demo 层:镜像 KSP 生成代码,重建路由匹配与返回栈会话;
- Native 层:生成双 ABI 动态库并通过 5 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理 Tab 状态、统计区、错误文本和页面交互;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 重度 AGP 的上游库,先做依赖分类再定边界:零依赖内核提取、载体依赖替换、纯 Compose 不碰;
- 路由字符串协议(哨兵值、转义规则)必须逐字保留并用 roundtrip 与真机页面双重验证;
- 分别记录编译、链接、符号、HAP 构建和真机运行,避免把编译成功误认为设备链路可用。
9.4 适配成果
当前适配已完成:
- 28 个内核文件的提取(逐字/载体替换,头部注明差异);
ohosArm64+ohosX64双目标动态库与 so 自动回填;- 5 个 C ABI 符号、C++ N-API 与 CMake 三件套交付;
- DevEco Stage ArkUI 四 Tab 验收页面;
- HAP 构建、签名、设备安装和 Mate 60 Pro 真机验证;
- 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。
参考文档
更多推荐



所有评论(0)