开源鸿蒙平台 CMP 三方库「FlowTab-CMP」适配全流程
本文记录
FlowTab-CMP接入 OpenHarmony 的完整过程,覆盖动画底部导航栏库盘点、鸿蒙 Compose 制品线选型、真实组件库编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
FlowTab-CMP是基于 Compose Multiplatform 的动画底部导航栏库:胶囊形态导航条、可展开成全宽输入框的搜索项、独立成按钮的隔离操作项(图标可旋转)、99+/圆点徽标、Ripple/Dot/Line/None 四态选中指示器、haze 玻璃拟态模糊,配合受控selectedId与内部NavState状态机完成选中、搜索展开/收起与回落。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取")有本质区别:社区 Maven 上已经出现完整的 HarmonyOS Compose 制品线(org.jetbrains.compose.*1.12.0-0.1.0-07,runtime / foundation / material3 / animation / ui 全带ohosArm64/ohosX64Gradle 变体),因此不需要提取内核——上游commonMain的组件、模型、状态机、数学与动画被原样编译成 OpenHarmony KLIB;又因为 OHOS 目前没有这条 Compose 线的组合宿主(composition host),适配多走了一步:把 UI 无关的全部真相(配置模型、状态机、徽标规则、插值数学、动画引擎)经无头门面提升为 JSON C ABI,ArkTS 按真实数据渲染导航条——上游改一处,鸿蒙侧自动跟进。
项目地址: AtomGit/oh-tpc/FlowTab-CMP.git
上游仓库: Alims-Repo/FlowTab-CMP
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
FlowTab-CMP 为 Compose Multiplatform 应用提供"一条好看的底部导航栏"这一件事的完整答案:BottomNavigation(items, selectedId, onItemSelected) 一行接入,导航决策全部留给调用方。NavItem 携带显示数据与字符串 id,NavItemType 区分三种形态——Standard 常规目的地、Search 展开成全宽搜索框(至多一个)、Isolated(rotation) 主条右侧的圆形操作按钮(图标可旋转,如 + 转 ×);BadgeData 控制未读数(超 99 显示 99+、0 自动隐藏)与纯圆点;NavIndicator 四态指示器(全宽胶囊 Ripple / 圆点 Dot / 横线 Line / 无 None)随选中项滑动;NavConfig 十五个字段管几何、动画时长、标签、描边、阴影、触感与 NavColor 七色板;Modifier.backgroundBlur 经 haze 提供玻璃拟态。搜索展开瞬间同步捕获 returnToId,收起清空 query 并回落——这套状态语义全部在上游 commonMain,由 Android、iOS 共享。OpenHarmony 应用同样需要这套导航能力,但上游构建(Kotlin 2.4.0 / Compose Multiplatform 1.11.1 / AGP 9.2.1)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以画出导航条,却无法证明公共组件模型与状态机在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证组件库本体、状态机语义、动画引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Kotlin 2.4.0 / AGP 9.2.1 / vanniktech 发布链与鸿蒙工具链无法共存于一个 Gradle 构建。 |
| 依赖矩阵 | 组件库依赖 Compose 五件套(runtime / foundation / material3 / animation / ui),每个都要有 ohos 变体;material3 还独立走自己的版本线。 |
| 第三方缺口 | 玻璃拟态依赖 dev.chrisbanes.haze 2.0.0-alpha02,社区线没有它的 ohos 制品,commonMain 却直接 import 它的类型。 |
| 无组合宿主 | OHOS 上没有这条社区 Compose 线的 composition host,Composable 编译得进 .so 却上不了屏。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:整个上游 commonMain 的 ohos 编译、根构建替换(cmp_qrose 模式)、haze 的 expect/actual 接缝、无头门面(headless facade)与 C ABI/N-API 桥接层。导航语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和按真实数据渲染。
1.2 库提供的能力
Demo 提供一个真实的导航会话,对应桥接的全部入口:
- 预设样式(preset):
modern_pill/modern_pill2/instagram/floating_minimal四套,镜像上游 Demo 的FlowTabStyle——返回的全部几何(高、圆角、图标尺寸、指示器尺寸间距)与颜色(七色 ARGB)逐字段来自 Kotlin 侧NavConfig对象; - 状态机(state):
init建会话(空列表/重复 id 报错、非法selected回落第一项)、tap点击路由(isolated 项走无守卫路径、展开态点标准项先收起)、expand同步捕获returnToId、collapse清 query 回落并返回returnedTo、query逐键更新、sync外部选中同步——每步返回{selectedId, returnToId, searchQuery, isSearchExpanded, selectedItem}完整快照; - 徽标规则(badge):
visible = showDot || count > 0、99+截断、16dp 数字圆 / 8dp 纯圆点,逐条复刻上游Badge.kt; - 响应式数学(math.interpolate):库内真实
interpolate线性映射(progress 夹 0…1),上游用它算条目分栏宽度; - 动画引擎(anim):真实 Compose
Animatable + tween在无头帧时钟上跑完 0→1,progress逐帧采样——这正是导航条宽度/高度/指示器过渡用的同一条动画机器; - 版本握手(info):返回 fork 版本、上游版本与工具链行,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
tag / returnedTo | state.init 回传会话名;collapse 回传回落项 id。 |
| 快照五元组 | selectedId / returnToId / searchQuery / isSearchExpanded / selectedItem{id,label,type}。 |
navColor / navIndicator | config / preset 返回的七色 ARGB 与指示器 kind + 几何。 |
组件侧完整能力(三种 item 类型、四态指示器、搜索展开/收起、徽标、rememberSaveable 进程重建恢复、haptics 钩子)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 compileKotlinJvm 交叉验证(上游源码对上游依赖线整编通过),证明 ohos .so 里执行的组件逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | flow-tab 的全部 commonMain 文件原样编译,不提取、不重写;仅把 haze 触点收进 expect/actual 接缝(6 个文件换 import 与类型名,实现移 jvmMain/ohosMain),无头门面是唯一新增 Kotlin 源文件。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 composeApp / iosApp 演示工程留盘不进构建。 |
| 桥接稳定 | 使用单一 C ABI(FlowTabBridge)+ UTF-8 JSON + 多会话 tag,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | ohos 线走社区 Compose 1.12.0-0.1.0-07,jvm 交叉验证线走 Maven Central 1.11.1 + material3 1.9.0 + haze 2.0.0-alpha02(与上游逐字同版,换线不换版)。 |
| UI 完整 | 三页签覆盖全部桥接入口:四套预设导航条(可交互)、状态机演练(快照卡 + 事件日志)、模型探针(config 表 / 徽标矩阵 / 插值滑杆 / 动画引擎)。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现——OHOS 上还没有这条 Compose 线的组合宿主。ArkTS Demo 把桥暴露的模型、状态与动画帧画出来:几何与颜色来自
preset,每次点击/展开/收起都经状态机拿回真实快照。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、探测 Compose ohos 制品线与 haze 缺口、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、material3 独立版本线
第 3 阶段:接缝与门面 ── NavHazeState/backgroundBlur expect/actual、FlowTabHeadless 无头门面、状态机与会话建模
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 三页签、四套预设导航条、Shape 原语图标、状态机演练与模型探针
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
flow-tab/ 上游组件库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
flow-tab/src/commonMain/ 组件、Nav* 模型、Badge、interpolate、动画(原样编译)
flow-tab/src/jvmMain/ 新增:NavHazeState typealias + 真实 haze backgroundBlur actual
flow-tab/src/ohosMain/ 新增:scrim actual + FlowTabHeadless 无头门面 + 预设镜像
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
composeApp/ iosApp/ media/ docs/ 上游演示工程与资产,留盘不进构建
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :flow-tab 与 :example:nativeApp,rootProject.name = "ohos_flowtab_cmp"。上游的 AGP classpath、compose 编译器插件别名、vanniktech 发布段全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21(org.gradle.java.home 指向本地 JDK 21) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作交叉验证) |
| Gradle | 9.6.1(上游 wrapper 9.4.1 升一档,与社区 Compose 线工具链组合对齐) |
| Compose(ohos 线) | org.jetbrains.compose.* 1.12.0-0.1.0-07(runtime / foundation / material3 / animation / ui 的 ohos klib) |
| Compose(jvm 线) | Maven Central 1.11.1 + material3 1.9.0 + haze / haze-blur 2.0.0-alpha02(与上游逐字同版) |
| coroutines | ohos 线 1.11.0-0.1.0-07、jvm 线 1.10.2 |
| DevEco product | 26.0.0(API 26,与 KN 侧 hms 26 sysroot 同代) |
| DevEco Studio | 6.0.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.flowtab |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
| 模块 | commonMain 文件 | 依赖形态 | 处置 |
|---|---|---|---|
flow-tab | 18 | Compose 五件套 + haze/haze-blur,纯 UI 与模型 | 整体编译(jvm + 双 ohos,haze 走接缝) |
composeApp | 上游演示 App | material-icons-extended / navigation-compose / haze | 留盘不进构建 |
iosApp | Xcode 工程 | — | 留盘不进构建 |
上游 commonMain 直接 import 的第三方只有 haze 一家:BottomNavigation.kt / BottomNavContent.kt / StandardNavContainer.kt / SearchSection.kt / IsolatedSection.kt 五个文件用 HazeState 做参数类型,BlurExt.kt 调 hazeEffect + HazeProgressive.verticalGradient。探测社区 Maven 的结论很干脆:eazytec 仓库上 dev.chrisbanes.haze 只有一份 1.6.5 的镜像 pom,既没有 2.0.0-alpha02(API 完全不同代),也没有任何 ohos klib——haze 是唯一的第三方缺口,处置见第 2.3 节。
1.4 页面边界
标题区 FLOWTAB · OPENHARMONY / info 卡(版本握手:fork 版本、上游版本、Kotlin/Compose 行)
Tab 切换 组件 / 状态机 / 模型
组件页 四张预设卡:导航条(可点击切换选中、指示器动画、徽标、搜索气泡展开成
TextInput 再收起、isolated 按钮 45° 旋转)+ 会话参数行
状态机页 快照卡(selectedId / returnToId / searchQuery / isSearchExpanded / selectedItem)
+ tap/sync/expand/collapse 按钮组 + query 输入框 + 时间戳事件日志
模型页 NavConfig 默认值表 + NavColor 七色色板 + 徽标 4×2 矩阵 + interpolate 滑杆
+ 动画引擎(start/stop + 33ms 轮询进度条与采样包络)
页面启动时先 aboutToAppear 拉齐 info / 四套 preset / config 默认值,并给四条导航条各建一个 state.init 会话(tag=bar0…bar3)。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 Compose 的 ohos 制品。结论:
org.jetbrains.compose.runtime:runtime:1.12.0-0.1.0-07 ohosArm64/ohosX64 klib ✓
org.jetbrains.compose.foundation:foundation:1.12.0-0.1.0-07 同上 ✓
org.jetbrains.compose.material3:material3:1.12.0-0.1.0-07 同上 ✓
org.jetbrains.compose.animation:animation:1.12.0-0.1.0-07 同上 ✓
org.jetbrains.compose.ui:ui:1.12.0-0.1.0-07 同上 ✓
dev.chrisbanes.haze:haze(-blur):ohos 任意版本 ✗(仅 1.6.5 jvm 镜像)
三个关键结论:其一,Compose 五件套在社区线 1.12.0-0.1.0-07 全数齐货,整个 commonMain(含 @Composable 本体)可以真编译;其二,这条线不发布 *-desktop 子制品,jvm 交叉验证目标必须单独走 Maven Central 的上游线(bubbles-compose / eungabi / compose-shimmer 的"换线不换版"先例);其三,material3 的版本号独立于 Compose 主线——上游 catalog 里 Compose 1.11.1 旁边钉的是 material3 1.9.0,jvm 线照抄这组坐标(第一次写成 1.11.1 直接 Could not find org.jetbrains.compose.material3:material3:1.11.1)。
2.2 工具链钉版
社区 Compose 线 1.12.0-0.1.0-07 基于 HarmonyOS Kotlin 2.4.20-0.1.0-07 构建,ohosArm64() / ohosX64() 目标也由这条 Kotlin 提供——工具链直接钉 2.4.20-0.1.0-07。上游源码本来就是 Kotlin 2.4.0 编的,2.4.20 与之同代,源码级兼容成本为零。Gradle 从上游 wrapper 9.4.1 升到 9.6.1,与社区已过审仓库(bubbles-compose / eungabi)在同一条工具链组合上验证过的版本对齐。
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_flowtab_cmp"
include(":flow-tab")
include(":example:nativeApp")
2.3 编译真实组件库:haze 接缝
flow-tab/build.gradle.kts 收敛为纯 KMP 模块,Compose 依赖按"线"分列——ohosMain 走社区线、jvmMain 走 Maven Central 上游线:
kotlin {
applyDefaultHierarchyTemplate()
jvm { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) } } // 交叉验证用
ohosArm64()
ohosX64()
sourceSets {
ohosMain.dependencies {
api(libs.compose.runtime) // 1.12.0-0.1.0-07
api(libs.compose.foundation)
api(libs.compose.material3)
api(libs.compose.animation)
api(libs.compose.ui)
implementation(libs.kotlinx.coroutines.core) // 无头动画引擎需要
}
jvmMain.dependencies {
api(libs.compose.runtime.jvm) // 1.11.1(上游线)
api(libs.compose.foundation.jvm)
api(libs.compose.material3.jvm) // 1.9.0(独立版本线)
api(libs.compose.animation.jvm)
api(libs.compose.ui.jvm)
api(libs.haze.jvm) // 2.0.0-alpha02,接缝后面的真实现
api(libs.haze.blur.jvm)
}
}
}
haze 的 expect/actual 接缝是本阶段的核心动作。commonMain 不再 import dev.chrisbanes.haze,公共 API 改说库自己的 NavHazeState:
// commonMain —— 线无关声明
@Stable
expect class NavHazeState()
expect fun Modifier.backgroundBlur(config: NavConfig, hazeState: NavHazeState?): Modifier
// jvmMain —— typealias 到真 HazeState,上游 API 源兼容
actual typealias NavHazeState = HazeState
// BlurExt.jvm.kt:上游 hazeEffect + HazeProgressive.verticalGradient 原文照搬
// ohosMain —— inert token + scrim 降级(与上游 Android 无 RenderEffect 同策略)
actual class NavHazeState
actual fun Modifier.backgroundBlur(config: NavConfig, hazeState: NavHazeState?) = this
StandardNavContainer 本来就自己画两层背景(enableBlur 时的不透明底 + blurIntensity 半透明覆盖层),ohos 上 backgroundBlur 返回原 Modifier 后,导航条自然降级为上游 hazeState = null 时的实底外观——视觉策略与 bubbles-compose 已过审的 Haze.ohos.kt 完全同款。五个传参文件只换 import 和 HazeState? → NavHazeState?,函数体一个字符不动。实测:
./gradlew :flow-tab:compileKotlinJvm # BUILD SUCCESSFUL
./gradlew :flow-tab:compileKotlinOhosArm64 :flow-tab:compileKotlinOhosX64 # BUILD SUCCESSFUL
组件、模型、状态机、徽标、数学、动画的全部 commonMain 文件一个没动。
2.4 测试任务的处置
上游 flow-tab 没有 commonTest 源集,但 ohos 测试任务照样会在任务图里等待设备。根构建全局禁用:
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
交叉验证由 compileKotlinJvm 承担:上游源码 + 上游依赖线(Compose 1.11.1 / material3 1.9.0 / haze 2.0.0-alpha02)整编通过,证明接缝没有改变上游语义(jvm 上 NavHazeState 就是 HazeState 的别名)。
第 3 阶段:接缝之上的无头门面
3.1 JSON 契约
io.github.alimsrepo.flowtab/**(上游组件库,原样编译)
↓ NavConfig / NavColor / NavIndicator / NavState / BadgeData / interpolate
FlowTabHeadless.ohos.kt(无头门面:快照、预设镜像、状态机会话、动画引擎)
↓ Kotlin 对象
Bridge.kt(@CName × 1,扁平 JSON 进出 + 多会话 tag)
↓ UTF-8 JSON
N-API flowtabBridge()
↓
ArkUI Index.ets(三页签)
请求与响应:
请求 : {"op":"preset","name":"instagram"}
{"op":"state.init","tag":"bar0","items":[{"id":"home","label":"Home","type":"standard"},
{"id":"search","type":"search"}],"selected":"home"}
{"op":"state.tap","tag":"bar0","id":"favorites"}
{"op":"state.expand" | "state.collapse" | "state.query","tag":"bar0","text":"..."}
{"op":"badge","count":100,"showDot":false}
{"op":"math.interpolate","value":52,"inMin":48,"inMax":60,"outMin":0,"outMax":100}
{"op":"anim.start","duration":250} / {"op":"anim.progress"} / {"op":"anim.stop"}
{"op":"config"} / {"op":"info"}
响应 : {"ok":true, ...字段平铺...}
{"ok":false,"error":"..."}
两个设计:config / preset 的快照字段平铺在响应顶层(heightDp / cornerRadiusDp / navColor{} / navIndicator{}……),ArkTS 一次 JSON.parse 直接取值;状态机支持多会话 tag——Demo 四条导航条各持一个会话互不干扰,缺省作用于最近一次 init 的会话。
3.2 无头门面:把上游语义提升为可序列化对象
FlowTabHeadless 是全仓唯一的新增 Kotlin 源文件,四类内容全部站在上游原件上:
object FlowTabHeadless {
// ① 快照:真 NavConfig()(或任意 config)逐字段导出,dp/sp 为 density 1 数值,颜色 toArgb()
fun configSnapshot(config: NavConfig = NavConfig()): ConfigSnapshot
// ② 状态机会话:镜像 BottomNavigation 的内部逻辑——
// init 的 require 校验、safeSelectedId 回落、initialNonSearchId;
// tap 的 BottomNavContent 守卫(展开态点标准项先收起)与 isolated 无守卫路由;
// expand 的同步 returnToId 捕获;collapse 的清 query 回落
fun session(items: List<ItemSpec>, selectedId: String): NavSession
// ③ 徽标规则:BadgeData(count, showDot) 按上游 Badge.kt 逐条复刻
fun badge(count: Int?, showDot: Boolean): BadgeSnapshot
// ④ 数学与动画:真 interpolate()(同模块可调 internal 函数);
// 真 Animatable + tween(duration) 在自备 HeadlessFrameClock 上 0→1
fun interpolate(...): Float
fun start(durationMillis: Int = 250): Boolean
fun progress(): ProgressSnapshot
}
动画引擎与 bubbles-compose 的 BubblesHeadless 同款纪律:专用单线程 dispatcher(回落 Dispatchers.Default)、自备 MonotonicFrameClock(8ms 一帧、TimeSource.Monotonic 时间戳)、CoroutineExceptionHandler 把引擎异常收进 progress().error 通道,全程不碰 Dispatchers.Main(鸿蒙线已知问题)。
四套预设常量在 ohosMain 里镜像上游 Demo App.kt 的 FlowTabStyle(60dp 全圆角紫 Ripple / 52dp 无标签 / Instagram 黑底 Line / Floating 深底青 Dot)——这是"fork 新增的镜像",上游 Demo 改动时需同步(见 8.2)。
3.3 错误通道
导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("FlowTabBridge")
fun flowtabBridge(request: String): String = try {
val json = MiniJson.parse(request)
when (json.str("op")) { "config" -> ...; "preset" -> ...; /* op 分发 */ else -> error("unknown op") }
} catch (t: Throwable) {
"""{"ok":false,"error":${(t.message ?: t.toString()).jsonString()}}"""
}
非法 JSON、未知 op、state.init 前缺会话、空 items / 重复 id(上游 require 规则的原样复刻),全部走同一个 ok:false 通道,ArkTS 侧统一渲染。浮点序列化不依赖 String.format(KN 上行为不稳),手写 4 位定点格式化。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libflowtab_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosflowtab.so)
│ FlowTabBridge
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;逐能力导出整数又会让页面复制状态机语义,所以选择单一 C ABI + JSON + 会话 tag。
4.2 Kotlin/Native 导出函数
@CName("FlowTabBridge")
fun flowtabBridge(request: String): String // config/preset/state.*/badge/math/anim/info 一口进
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
FlowTabBridge | flowtabBridge(req) | 14 个 op:模型快照、四套预设、六步状态机、徽标规则、插值、动画引擎、版本握手 |
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"flowtabBridge", nullptr, NapiFlowTabBridge, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr}};
回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 FlowTabBridge(request.c_str()) 并包装返回。KN 返回的字符串必须经 libohosflowtab_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosflowtab_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与业务符号),不拖整份生成头。
4.4 CMake 和类型声明
add_library(ohosflowtab SHARED IMPORTED)
set_target_properties(ohosflowtab PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(flowtab_napi SHARED napi_init.cpp)
target_link_libraries(flowtab_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohosflowtab)
OHOS_ARCH 为空或 libohosflowtab.so 缺失直接 FATAL_ERROR(提示先跑 :example:nativeApp:linkDebugSharedOhosArm64 / OhosX64);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const flowtabBridge: (request: string) => string;
第 5 阶段:ArkUI 宿主
5.1 启动拉齐与交互路由
aboutToAppear(): void {
this.callInfo();
for (let i = 0; i < this.presets.length; i++) {
this.loadPreset(i); // op=preset ×4
this.initSession(`bar${i}`, this.barItems(i), 'home'); // op=state.init ×4
}
this.initStateSession(); // tag=state,五项列表
this.loadConfigDefaults(); // op=config
this.loadBadgeMatrix(); // op=badge ×8
}
private tapItem(idx: number, id: string): void {
const req: StateTapRequest = { op: 'state.tap', tag: this.bars[idx].tag, id: id };
const res = JSON.parse(flowtabBridge(JSON.stringify(req))) as StateResponse;
this.applyStateToBar(idx, res); // 快照回写 @State,四条栏即时刷新
}
点击、展开、收起、输入全部走桥拿回快照;isolated 按钮走 state.tap(add) 后由 ArkTS 侧翻转旋转角(镜像上游 Demo 的 rotation 逻辑,桥不越权管动画状态)。
5.2 页面交互
组件页四张预设卡:几何与颜色全部来自 preset 响应(不在 ArkTS 写死数值);指示器按 kind 渲染 ripple 胶囊 / dot 圆点 / line 短横线 / none,切换带 FastOutSlowIn 曲线动画;徽标 visible / text / sizeDp 来自 badge;搜索气泡点击 state.expand 展开成 TextInput(placeholder 来自 config),点 × 或任意标准项 state.collapse 收起;每张卡底部的会话参数行实时显示 session=bar0 · sel=... · ret=...。状态机页提供 tap×4 / sync×2 / expand / collapse / query 全按钮组与时间戳日志(collapse 的 returnedTo 单独展示)。模型页是探针盘:NavConfig 默认值逐行表、七色 ARGB→hex 色板、徽标 4×2 矩阵(count 0/5/100/缺省 × showDot,全部真实调桥)、interpolate 滑杆(越界值验证 progress 夹取)、动画引擎(33ms setInterval 轮询 anim.progress 画进度条与采样包络,aboutToDisappear 清理定时器)。
5.3 图标策略与错误边界
图标不用 SymbolGlyph(符号资源名跨 SDK 版本有不可验证的风险),全部用 Shape 原语(Path / Circle / Line)绘制五枚线性图标(home / search / heart / person / plus),按选中态着色、按 iconsSizeDp 缩放——bubbles-compose 过审 Demo 同款策略,编译期零资源依赖。ok:false 时错误原文进状态行;Native 调用本身的异常同样被 try/catch 收进可见通道,页面永不白屏。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohosflowtab)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ ├── Bridge.kt @CName("FlowTabBridge") + 会话表 + 序列化
│ └── MiniJson.kt 扁平 JSON(含数组解析,继承自 cmp_qrose 审核模式)
└── harmonyApp/
├── build-profile.json5 compatibleSdkVersion 26.0.0,signingConfigs 留空
├── AppScope/app.json5 bundleName org.terminator.ohos.flowtab、versionName 0.5.9-beta-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/libohosflowtab_api.h
├── src/main/cpp/types/libflowtab_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 三页签验收页面
6.2 构建、签名和安装
./gradlew :flow-tab:compileKotlinJvm # JVM 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap --mode module -p module=entry@default -p product=default --no-daemon
签名在 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.flowtab -m entry
命令行构建要点:DEVECO_SDK_HOME 指向 DevEco 安装内的 sdk 目录;打包工具起 java 子进程,PATH 里必须有 JDK 21(spawn java ENOENT otherwise)。
四、完整代码对照
4.1 整体架构
io.github.alimsrepo.flowtab/**(上游组件库原样编译)
│ BottomNavigation / NavConfig / NavItem / NavItemType / BadgeData
│ NavColor / NavIndicator / NavState / interpolate / 动画
flow-tab/src/{jvmMain,ohosMain}/ui/extension/
│ NavHazeState 接缝(jvm=真 HazeState,ohos=scrim)
flow-tab/src/ohosMain/FlowTabHeadless.ohos.kt
│ 快照 / 预设镜像 / 状态机会话 / 徽标规则 / 动画引擎
Bridge.kt + MiniJson
│ FlowTabBridge(14 op + 会话 tag)
libflowtab_napi.so
│ flowtabBridge
Index.ets
│ JSON parse + @State 更新
▼
ArkUI 真机页面(三页签)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_flowtab_cmp、演示工程移出 |
gradle/libs.versions.toml | Kotlin 2.4.20-0.1.0-07 + 双 Compose 线(ohos 1.12.0-0.1.0-07 / jvm 1.11.1 + material3 1.9.0 + haze 2.0.0-alpha02) |
build.gradle.kts(根) | 版本 0.5.9-beta-ohos.1、全局禁用 Ohos*Test* 任务 |
flow-tab/build.gradle.kts | 纯 KMP:jvm + ohosArm64 + ohosX64,依赖按线分列 |
flow-tab/.../ui/extension/NavHazeState.kt | commonMain expect 声明(接缝) |
flow-tab/src/jvmMain/.../BlurExt.jvm.kt | 真实 haze 实现原文(typealias 之下源兼容) |
flow-tab/src/ohosMain/.../BlurExt.ohos.kt | scrim 降级 actual |
flow-tab/src/ohosMain/.../FlowTabHeadless.ohos.kt | 无头门面(快照/预设/状态机/徽标/动画引擎) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 单 C ABI、会话表、序列化、错误通道 |
nativeApp/.../MiniJson.kt | 扁平 JSON + 数组解析 |
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 三页签验收页面(Shape 原语图标) |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(11 节,桥协议全字段表) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 组件 | BottomNavigation(items, selectedId, onItemSelected) | 主入口,受控选中 + 内部 NavState |
| 组件 | NavItemType.Search / Isolated(rotation) | 展开搜索 / 隔离按钮两种特殊形态 |
| 组件 | NavIndicator.Ripple/Dot/Line/None | 四态选中指示器,未指定色回落 NavColor |
| 组件 | BadgeData(count, showDot) | 99+ 截断、0 隐藏、8dp 纯圆点 |
| 组件 | NavConfig / NavColor | 15 字段配置 + 七色板 |
| 门面 | FlowTabHeadless.session(...) | 状态机会话(tap/expand/collapse/query/sync) |
| 门面 | FlowTabHeadless.badge / interpolate / start | 徽标规则 / 真实插值 / 真实 tween 引擎 |
| Native | FlowTabBridge | 单 C ABI JSON 导出(14 op) |
| N-API | flowtabBridge(req) | 向 ArkTS 暴露能力 |
| ArkTS | tapItem / expandBar / collapseBar / queryBar | 触发状态机并按快照刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(flowtabBridge(JSON.stringify({
op: 'state.tap', tag: 'bar0', id: 'favorites'
})));
// res.ok === true;res.selectedId === 'favorites'、res.returnToId === 'favorites'
// 展开态下同一调用:isSearchExpanded 变 false,returnedTo 给出回落项
fun tap(id: String) {
if (id == isolatedItem?.id) { tapIsolated(); return } // 无守卫路由
if (isSearchExpanded) { collapse(); return } // BottomNavContent 守卫
stateSelectedId = id; stateReturnToId = id // 上游 onItemClick 两连写
rawSelectedId = id; isSearchExpanded = false
}
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:真编译整库 + expect/actual 接缝,而不是提取模型
社区 Compose 线五件套齐货后,@Composable 本体都能编进 .so——再抽模型等于把组件库手抄一遍。唯一的第三方 haze 用最小接缝处理:commonMain 改说 NavHazeState(6 个文件换 import 与类型名),jvm 侧 actual typealias 保住上游 API 的源兼容性,ohos 侧 scrim 降级与上游 Android 无 RenderEffect 时的行为一致。上游模型、状态机、数学、动画全部是原件。
决策 2:无组合宿主之下的分工——Kotlin 出真相,ArkTS 出像素
OHOS 上没有这条 Compose 线的 composition host,Composable 编译得进 .so 却上不了屏。与其等待,不如把"UI 无关的全部真相"经无头门面导出:几何与颜色来自真实 NavConfig,每次交互的状态转移来自真实 NavState 语义镜像,动画进度来自真实 Animatable + tween。这不是把导航条重写一遍——上游改一处,快照与状态机自动跟进,ArkTS 只是渲染层。
决策 3:根构建替换(cmp_qrose 模式),演示工程留盘
上游根构建(AGP / compose 编译器别名 / vanniktech 发布链)对鸿蒙没有保留价值,整个替换为极简 ohos 构建。composeApp(依赖 material-icons-extended / navigation-compose)与 iosApp 留盘不进 settings.gradle.kts——它们的存在方式只有这一种。
决策 4:单一 C ABI + 多会话 tag + 快照平铺
14 个 op 收在一个 FlowTabBridge 里,N-API 层只需要导出一个 JS 函数;会话用 tag 区分(Demo 四条栏 + 状态机页各持一会话),缺省作用于最近一次 init;config / preset 快照字段平铺响应顶层,ArkTS 免二次解包。状态机每步返回完整快照而不是增量——页面无状态化,崩溃恢复后重新 init 即可。
决策 5:JVM 交叉验证 + 双依赖线隔离
ohos 线不发布 *-desktop 子制品,jvm 目标单独走 Maven Central 上游线(Compose 1.11.1 / material3 1.9.0 / haze 2.0.0-alpha02,与上游 catalog 逐字同版)。compileKotlinJvm 整编上游源码通过,就是"接缝没有改变上游语义"的闸门——jvm 上 NavHazeState 就是 HazeState。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x64)、JDK 21(本地安装)、DevEco Studio 26(HarmonyOS SDK 26.0.0 / API 26)、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew :flow-tab:compileKotlinJvm
上游 commonMain 全部 18 个文件对上游依赖线(Compose 1.11.1 + material3 1.9.0 + haze 2.0.0-alpha02)整编通过。上游没有 commonTest 源集,交叉验证落在编译闸门与桥接行为验收(6.4)上——接缝在 jvm 上是透明的 typealias,编译通过即语义未变。
6.3 HAP 验证
:flow-tab:compileKotlinJvm: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: FlowTabBridge / libohosflowtab_symbols 均为全局 T 符号
BuildNativeWithCmake + BuildNativeWithNinja: finished(双 ABI 编出 libflowtab_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
entry-default-unsigned.hap 约 42MB,内含双 ABI 各三件套(libflowtab_napi.so / libohosflowtab.so / libc++_shared.so);HAP 内 strip 后 KN so arm64 约 19.7MB、x86_64 约 19.6MB(debugShared 未 strip 时分别约 27.6MB / 26.4MB,含 Kotlin/Native 运行时与整套 Compose 依赖)。
6.4 功能用例
- 启动后 info 卡显示
FlowTab-CMP 0.5.9-beta-ohos.1(版本握手来自真实 Native 调用,非静态文字); - 组件页四张预设卡渲染成功,几何与颜色来自
preset——modern_pill 60dp 全圆角紫 Ripple、modern_pill2 52dp 无标签、Instagram 直角黑底 32dp 图标 Line 指示器、Floating Minimal 深底 28dp 圆角青色 Dot 指示器带阴影,四套差异与上游 DemoFlowTabStyle一致; - 点击条目切换选中:图标着色、标签加粗、指示器带曲线动画移动;
- 徽标渲染正确:
count=5数字圆、count=100显示99+、showDot纯圆点、count=0隐藏(模型页矩阵逐格验证); - 点搜索气泡展开成全宽 TextInput,
returnToId同步捕获;点 × 或任意标准项收起,回落到捕获项(状态机页returnedTo字段可查); - isolated + 按钮点击图标旋转 45°,再点复位(上游 Demo 同款交互);
- 状态机页每个按钮的日志与组件页手动交互行为一致——展开态点标准项日志显示"先收起";
- interpolate 滑杆实时返回映射值,越界输入(40/68dp)验证 progress 夹取到 0…1;
- 动画引擎 start 后进度条按 33ms 采样推进,停止即冻结,
easing报告FastOutSlowInEasing。
6.5 验证结论
组件库本体、状态机语义、动画引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的导航会话:版本握手、四套预设渲染、选中切换、徽标、搜索展开/收起回落与状态机日志闭环(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/FlowTab-CMP.git
cd FlowTab-CMP
export JAVA_HOME="/path/to/jdk-21"
./gradlew :flow-tab:compileKotlinJvm # 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap --mode module -p module=entry@default -p product=default --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.flowtab -m entry
hdc shell hilog | grep FlowTab
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 CMP 适配,必须让
:flow-tab真正编译成ohosArm64/ohosX64动态库;UI 库还要多回答一步"没有组合宿主怎么渲染"——答案是 Kotlin 出模型与状态、ArkTS 出像素; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- material3 的版本号独立于 Compose 主线:上游 Compose
1.11.1对应 material31.9.0,jvm 线照抄主线版本号会在解析期报Could not find org.jetbrains.compose.material3:material3:1.11.1; - 社区 Compose 线不发布
*-desktop子制品,jvm 交叉验证必须双线声明(ohos 走社区线、jvm 走 Maven Central),bubbles-compose / eungabi / compose-shimmer 同款先例; - haze 无 ohos 制品:commonMain 直接 import 第三方类型时,expect/actual 接缝 +
actual typealias是保住上游 API 源兼容的最小改法,ohos scrim 降级与上游 Android 无 RenderEffect 行为一致; - ArkTS 严格模式比 TS 严得多:独立函数里用
this、无类型对象字面量、@Builder调用漏this.前缀、Path() {}空尾随闭包都是编译错误——首次生成的演示页 692 个错误,按 bubbles-compose 过审写法集(typed interface +JSON.parse() as T+ @Builder 方法)重写后一次通过; SymbolGlyph的sys.symbol.*资源名跨 SDK 版本无静态清单可查,编译期才发现缺失;改用 Shape 原语(Path/Circle/Line)画图标,零资源依赖、随选中态着色;compatibleSdkVersion必须与本机 DevEco 自带 SDK 匹配(本仓 Demo 钉26.0.0对应 DevEco 26 / API 26,与 KN 侧 hms 26 sysroot 同代),写模板默认值5.1.0(18)会在配置期报Cannot find the corresponding SDK version;- 命令行 hvigor 打包起
java子进程,PATH 没有 JDK 会报spawn java ENOENT;DEVECO_SDK_HOME要指向包含版本目录的 sdk 根; - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可;import 错 so 则报does not provide an export named 'flowtabBridge'; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free();浮点序列化不要依赖String.format(KN 行为不稳),手写定点格式化; - 签名材料由 DevEco 自动写入
build-profile.json5,提交前保护,证书绝不进入 AtomGit。
8.2 已知问题
- OHOS 无社区 Compose 线组合宿主:Composable 编译进
.so但不上屏,ArkTS 按真实快照渲染视觉——两套渲染在细节(渐变、自定义 ShapeModifier)上存在近似; - haze 降级 scrim:
enableBlur + blurIntensity在 ohos 上等价于上游hazeState = null的实底外观; - 上游
android/iostarget 不在本 fork 构建内(源码留盘);上游 DemocomposeApp同理,发布链一并剥离,本 fork 不发 Maven Central; - 四套预设常量是
ohosMain镜像:上游FlowTabStyle改动需手动同步(快照逐字段可比对); enableHaptics走 compose.ui 的LocalHapticFeedback,ohos 线编译通过但运行时为 no-op;- 桥的多会话
tag由调用方自理生命周期(重复state.init同名 tag 即重置); - HAP 安装必须使用与 bundle name
org.terminator.ohos.flowtab匹配的签名。
8.3 未来优化方向
- 跟进社区组合宿主进度,宿主可用后把
commonMain的 Composable 直接接上屏,ArkTS 渲染降级为 fallback; - 把指示器滑动动画的偏移计算也搬到桥内(目前 ArkTS 侧按快照自算);
- 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查、ArkTS 严格模式 lint 和 HAP 构建;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。
九、总结
9.1 核心难点回顾
上游 flow-tab commonMain(组件 + 模型 + 状态机 + 数学 + 动画)
→ 验证 Compose ohos 制品线(五件套 1.12.0-0.1.0-07 齐货,haze 缺货)
→ 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1)
→ 根构建替换(cmp_qrose 模式,双依赖线换线不换版)
→ NavHazeState expect/actual 接缝(jvm=真 HazeState,ohos=scrim)
→ ohosArm64 / ohosX64 KLIB
→ FlowTabHeadless 无头门面(快照 / 预设 / 状态机 / 动画引擎)
→ C ABI(@CName × 1,14 op + 会话 tag)
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析 + Shape 原语图标
→ ArkUI 真机三页签验收(预设 / 状态机 / 模型探针)
9.2 封装层次
- 组件层:上游原版
BottomNavigation、NavConfig/NavItem/NavItemType/BadgeData/NavColor/NavIndicator与NavState状态机; - 接缝层:
NavHazeState/backgroundBlur的 expect/actual,jvm 透明、ohos scrim; - 门面层:
FlowTabHeadless把模型、状态机、徽标规则、插值与动画引擎提升为 JSON 友好对象; - Native 层:生成双 ABI 动态库并通过单一 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、会话 tag、JSON 校验、事件分发、日志与错误文本;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 纯 UI 库的适配答案与存储库不同:制品线齐货让"真编译整库"成立,但组合宿主缺位让"渲染"必须换轨——把 UI 无关的全部真相(模型、状态机、动画)经无头门面导出给 ArkTS,是当下既保真又可验收的路线;路线选择必须基于当天的生态现实(制品可达性 + 宿主可用性),而不是历史结论;
- 第三方缺口用接缝而不是删功能:haze 没有 ohos 制品,但 commonMain 直接 import 它的类型——
expect class + actual typealias的组合让上游 API 在其余平台零变化,ohos 拿到与上游自身降级路径一致的 scrim,比"删掉 blur 参数"诚实得多; - ArkTS 严格模式要当第一等公民对待:演示页 692 个编译错误的教训是,ArkTS Demo 必须在过审写法集(typed interface、@Builder、无独立函数 this)内书写,且图标这类"资源名依赖"改用 Shape 原语规避——编译期可验证性优先于视觉花哨。
9.4 适配成果
当前适配已完成:
- FlowTab-CMP
0.5.9-beta真实组件库的双 ABI Kotlin/Native 编译(上游commonMain文件零重写,仅 haze 触点收进 expect/actual 接缝); - HarmonyOS Compose 制品线在 UI 库上的完整落地(
org.jetbrains.compose.*1.12.0-0.1.0-07,material3 在列)+ jvm 双线交叉验证(Compose 1.11.1 / material3 1.9.0 / haze 2.0.0-alpha02); FlowTabHeadless无头门面与单一 C ABI 桥(llvm-nm验证全局 T 符号,14 op + 多会话 tag);- DevEco Stage ArkUI 三页签验收页面(四套预设导航条交互、状态机演练与日志、模型探针、版本握手);
- HAP 构建(双 ABI 六件套 so,约 42MB)、签名、设备安装和真机验证;
- 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)