本文记录 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 / ohosX64 Gradle 变体),因此不需要提取内核——上游 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 / returnedTostate.init 回传会话名;collapse 回传回落项 id。
快照五元组selectedId / returnToId / searchQuery / isSearchExpanded / selectedItem{id,label,type}。
navColor / navIndicatorconfig / 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 MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21(org.gradle.java.home 指向本地 JDK 21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle9.6.1(上游 wrapper 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(与上游逐字同版)
coroutinesohos 线 1.11.0-0.1.0-07、jvm 线 1.10.2
DevEco product26.0.0(API 26,与 KN 侧 hms 26 sysroot 同代)
DevEco Studio6.0.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.flowtab
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
模块commonMain 文件依赖形态处置
flow-tab18Compose 五件套 + haze/haze-blur,纯 UI 与模型整体编译(jvm + 双 ohos,haze 走接缝)
composeApp上游演示 Appmaterial-icons-extended / navigation-compose / haze留盘不进构建
iosAppXcode 工程—留盘不进构建

上游 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 方法覆盖内核
FlowTabBridgeflowtabBridge(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.tomlKotlin 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.ktcommonMain expect 声明(接缝)
flow-tab/src/jvmMain/.../BlurExt.jvm.kt真实 haze 实现原文(typealias 之下源兼容)
flow-tab/src/ohosMain/.../BlurExt.ohos.ktscrim 降级 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.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 三页签验收页面(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 / NavColor15 字段配置 + 七色板
门面FlowTabHeadless.session(...)状态机会话(tap/expand/collapse/query/sync)
门面FlowTabHeadless.badge / interpolate / start徽标规则 / 真实插值 / 真实 tween 引擎
NativeFlowTabBridge单 C ABI JSON 导出(14 op)
N-APIflowtabBridge(req)向 ArkTS 暴露能力
ArkTStapItem / 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 功能用例

  1. 启动后 info 卡显示 FlowTab-CMP 0.5.9-beta-ohos.1(版本握手来自真实 Native 调用,非静态文字);
  2. 组件页四张预设卡渲染成功,几何与颜色来自 preset——modern_pill 60dp 全圆角紫 Ripple、modern_pill2 52dp 无标签、Instagram 直角黑底 32dp 图标 Line 指示器、Floating Minimal 深底 28dp 圆角青色 Dot 指示器带阴影,四套差异与上游 Demo FlowTabStyle 一致;
  3. 点击条目切换选中:图标着色、标签加粗、指示器带曲线动画移动;
  4. 徽标渲染正确:count=5 数字圆、count=100 显示 99+、showDot 纯圆点、count=0 隐藏(模型页矩阵逐格验证);
  5. 点搜索气泡展开成全宽 TextInput,returnToId 同步捕获;点 × 或任意标准项收起,回落到捕获项(状态机页 returnedTo 字段可查);
  6. isolated + 按钮点击图标旋转 45°,再点复位(上游 Demo 同款交互);
  7. 状态机页每个按钮的日志与组件页手动交互行为一致——展开态点标准项日志显示"先收起";
  8. interpolate 滑杆实时返回映射值,越界输入(40/68dp)验证 progress 夹取到 0…1;
  9. 动画引擎 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 踩坑复盘

  1. 只改 ArkTS 页面不算 CMP 适配,必须让 :flow-tab 真正编译成 ohosArm64 / ohosX64 动态库;UI 库还要多回答一步"没有组合宿主怎么渲染"——答案是 Kotlin 出模型与状态、ArkTS 出像素;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. material3 的版本号独立于 Compose 主线:上游 Compose 1.11.1 对应 material3 1.9.0,jvm 线照抄主线版本号会在解析期报 Could not find org.jetbrains.compose.material3:material3:1.11.1;
  4. 社区 Compose 线不发布 *-desktop 子制品,jvm 交叉验证必须双线声明(ohos 走社区线、jvm 走 Maven Central),bubbles-compose / eungabi / compose-shimmer 同款先例;
  5. haze 无 ohos 制品:commonMain 直接 import 第三方类型时,expect/actual 接缝 + actual typealias 是保住上游 API 源兼容的最小改法,ohos scrim 降级与上游 Android 无 RenderEffect 行为一致;
  6. ArkTS 严格模式比 TS 严得多:独立函数里用 this、无类型对象字面量、@Builder 调用漏 this. 前缀、Path() {} 空尾随闭包都是编译错误——首次生成的演示页 692 个错误,按 bubbles-compose 过审写法集(typed interface + JSON.parse() as T + @Builder 方法)重写后一次通过;
  7. SymbolGlyph 的 sys.symbol.* 资源名跨 SDK 版本无静态清单可查,编译期才发现缺失;改用 Shape 原语(Path / Circle / Line)画图标,零资源依赖、随选中态着色;
  8. 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;
  9. 命令行 hvigor 打包起 java 子进程,PATH 没有 JDK 会报 spawn java ENOENT;DEVECO_SDK_HOME 要指向包含版本目录的 sdk 根;
  10. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;import 错 so 则报 does not provide an export named 'flowtabBridge';
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();浮点序列化不要依赖 String.format(KN 行为不稳),手写定点格式化;
  12. 签名材料由 DevEco 自动写入 build-profile.json5,提交前保护,证书绝不进入 AtomGit。

8.2 已知问题

  • OHOS 无社区 Compose 线组合宿主:Composable 编译进 .so 但不上屏,ArkTS 按真实快照渲染视觉——两套渲染在细节(渐变、自定义 ShapeModifier)上存在近似;
  • haze 降级 scrim:enableBlur + blurIntensity 在 ohos 上等价于上游 hazeState = null 的实底外观;
  • 上游 android / ios target 不在本 fork 构建内(源码留盘);上游 Demo composeApp 同理,发布链一并剥离,本 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 三条经验

  1. 纯 UI 库的适配答案与存储库不同:制品线齐货让"真编译整库"成立,但组合宿主缺位让"渲染"必须换轨——把 UI 无关的全部真相(模型、状态机、动画)经无头门面导出给 ArkTS,是当下既保真又可验收的路线;路线选择必须基于当天的生态现实(制品可达性 + 宿主可用性),而不是历史结论;
  2. 第三方缺口用接缝而不是删功能:haze 没有 ohos 制品,但 commonMain 直接 import 它的类型——expect class + actual typealias 的组合让上游 API 在其余平台零变化,ohos 拿到与上游自身降级路径一致的 scrim,比"删掉 blur 参数"诚实得多;
  3. 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。

参考文档

Logo

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

更多推荐