本文记录 JetCo 接入 OpenHarmony 的完整过程,覆盖 UI 组件库盘点、社区 Compose ohos 制品线选型、ohosArm64 / ohosX64 目标、material-icons 缺口的 shim 模块方案、kotlinx.datetime.Clock 别名的 common 源码分析陷阱、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机四 Tab 页面、HAP 构建和真机验收。

JetCo 是一个 Compose Multiplatform UI 组件库(上游坐标 com.developerstring.jetco-kmp:ui:1.1.0-beta,Apache-2.0):图表(折线 / 多折线 / K 线 / 柱状三兄弟 / 饼图)、滚轮式日期时间选择器(Date / Time / DateTime / DateRange / MonthYear)、步进器(水平 / 垂直 / 紧凑)、浮动操作按钮(Stack / Radial / Morph)、AnimatedSearchBar、SwitchButton 和 TicketCard。本次适配与社区早期路线(qrose 的「编码器隔离」)有本质区别:JetCo 是纯 Compose UI 库,没有可隔离的纯 Kotlin 内核,因此走社区已过审的第二档路线(Kore / compose-shimmer / eungabi 同款)——上游 commonMain 整库原样编译成 OpenHarmony KLIB 与动态库,一个无头门面 JetCoHeadless 把库的真实模型、默认配置与几何公式提升为 UI-free API,ArkTS Demo 用桥返回的运行时真实数值重绘组件视觉,页面作为验收宿主。

项目地址: AtomGit/oh-tpc/JetCo.git

上游仓库: DeveloperChunk/JetCo

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

JetCo 为 Kotlin Multiplatform 应用提供一整套高颜值交互组件:LineGraph 一行出平滑曲线 + 面积填充 + live 模式,CandlestickChart 出 K 线 + 成交量 + 十字线,DatePicker 系列出滚轮选择器(大小月 / 闰年合法性、12/24 小时制、就近吸附内建),Stack/Radial/MorphFloatingActionButton 出三种展开编排,AnimatedSearchBar 出 spring 展开动画 + 控制器,TicketCard 出半圆缺口票券。KMP 项目把这些组件放在 commonMain,由 Android、iOS、桌面共享。OpenHarmony 应用同样需要这套组件能力,但上游构建(Compose Multiplatform 1.9.0 / Kotlin 2.2.20)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明公共组件代码在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证组件库本体、无头门面的真实取值(默认配置、几何公式、格式化)、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
制品线缺口社区 Compose ohos 线没有发布任何 material-icons 制品(icons-core / icons-extended 都没有 ohos 变体,已逐一验证 .module 元数据);库的 commonMain 引用了 6 个图标符号。
工具链特性差异HarmonyOS Kotlin 2.4.20-0.1.0-07 下,kotlinx.datetime.Clock(指向 kotlin.time.Clock 的废弃 typealias)在 common 源码分析中可解析 Clock 却不可解析 .System;库的 10 处 Clock.System.now() 全部受影响。
UI 无法上屏OHOS 目前没有社区 Compose 线的 composition 宿主,且社区线(1.12.0-0.1.0-07)与上游线(1.9.0)不能在同一目标混用,Composable 编进 so 但不能直接渲染。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:组件库整库的 ohos 编译、根构建替换(cmp_qrose 模式)、无头门面 + 扁平 JSON 桥。组件语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和按真实数值绘制。

1.2 库提供的能力

Demo 四个 Tab 覆盖桥接的全部 14 个 op,每个 Tab 的数值都来自 Kotlin 侧真实计算:

  • 图表(linegraph / barchart / candlestick / piechart):折线路径与面积路径由上游 buildLineAndAreaPaths 公式逐行转录生成(含动画进度截断),归一化走真实 mapToLineGraphEntries;柱状条 x 按 LazyRow+SpaceEvenly 布局语义补齐公式;K 线涨跌分类走真实 CandleChange.forPrices,±5% padded 区间、影线 / 实体 / 成交量 0.9 封顶全部按上游公式;饼图 sweep = 360·v/sum、静止旋转 990° mod 360。
  • 选择器(datewheel / timewheel / format):滚轮项表(年 1922…2122 / 月名三态 / 当月天数)、DateOrder 三排列、12/24 小时制、分钟间隔就近吸附;Done 后的显示文本走真实 PickerDate.toFormattedString() 与 PickerTime.toFormattedString(),12→24 小时换算走上游 get24Hour 规则。
  • 步进器(stepper):COMPLETE / ACTIVE / IDLE / ERROR 四态的节点色、图标(error > completed > active 优先级)、图标 tint、连接线颜色与「仅 COMPLETE 实线、其余 10,10 虚线」规则,色板读自真实 StepperConfig() 默认值。
  • 按钮族(fab / switch / searchbar / ticket):Stack 方向分组偏移与 stagger 错峰、Radial 三弧(END/START/CENTER)极坐标与 -8dp 屏幕修正、Switch 滑块行程 travel = W - knob - 2p 与 700ms LinearOutSlowIn、SearchBar 状态机七条转移与 spring 参数、TicketCard 四角圆角 + 半圆缺口 + 虚线分隔的完整轮廓路径。

响应统一为扁平 JSON(ok / op + 字段),失败一律 {"ok":"false","error":...},异常不跨 FFI。

1.3 实现适配

维度要求
代码复用JetCoKMP/jetco 的全部 commonMain 文件原样编译,不提取、不重写;唯一源码接缝是 10 处 Clock.System 表达式改走 expect/actual(各 actual 保留上游原表达式)。
平台目标根构建直接声明 jvm() + ohosArm64 + ohosX64,jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 composeApp / jetco-android / iosApp / kotlin-js-store 留盘不进构建。
桥接稳定2 个 C ABI + UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律ohos 用社区 Compose 1.12.0-0.1.0-07,jvm 交叉验证用上游线 1.9.0(换线不换版);material-icons 走独立 shim 模块;datetime 换 0.8.0-0.1.0-07 线(库只触碰 Clock/TimeZone/toLocalDateTime)。
UI 完整四 Tab 覆盖全部 14 个 op:图表四子视图 + 动画进度滑杆、三列滚轮 + 12/24h 切换、步进器状态驱动、FAB/开关/搜索栏/票券全交互。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、验证社区 Compose ohos 制品线(runtime/foundation/ui/material3
                            四制品 1.12.0-0.1.0-03..11 版本交集,取 07)、确认 icons 无制品、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、双线依赖声明、
                            icons-shim 模块、Clock 接缝、jvmTest 语义闸门
第 3 阶段:门面与桥协议   ── JetCoHeadless 无头门面(复用 vs 转录)、jetcoDispatch/jetcoVersion 扁平 JSON 契约、
                            14 个 op、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── DevEco 工程、四 Tab 页面、滚轮组件、Canvas 图表渲染
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
JetCoKMP/jetco/             上游组件库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
jetco/src/ohosMain/         新增:JetCoHeadless 无头门面 + Clock 接缝 ohos actual
icons-shim/                 新增:material-icons 六图标 shim 模块(仅 ohos 目标)
example/nativeApp/          Kotlin/Native 动态库(Bridge.kt,14 op)
example/harmonyApp/         DevEco Stage、CMake N-API 和 ArkTS 四 Tab 页面
JetCoKMP/composeApp/ 等     上游演示模块,留盘不进 settings
*.upstream                  上游构建备份(settings / build / libs.versions.toml,必须提交)

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):根 settings.gradle.kts 只 include :icons-shim、:jetco(projectDir 指向 JetCoKMP/jetco)与 :example:nativeApp,rootProject.name = "ohos_jetco"。上游的 vanniktech 发布、compose-resources、AGP classpath、js/wasm/ios 目标全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21(gradle.properties 里 org.gradle.java.home 钉死本机路径)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle9.6.1(wrapper;2.4.20 线支持 Gradle 9.x)
Compose(ohos 线)社区 org.jetbrains.compose.* 1.12.0-0.1.0-07(runtime/foundation/ui/material3 均有 ohosArm64/ohosX64 klib 变体,已查 .module 逐项确认)
Compose(jvm 线)上游 1.9.0(Maven Central,上游 1.1.0-beta 所用线)
kotlinx-datetimeohos 线 0.8.0-0.1.0-07 / jvm 线 0.6.2(0.6.x-compat 版本无 ohos 变体,已验证)
material-icons无 ohos 制品 → icons-shim 模块(路径数据逐字取自 material-icons-core:1.7.3 官方 sources jar)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.jetco
1.3 源码依赖度盘点
依赖形态处置
compose runtime/foundation/ui/material3commonMain 全量使用按目标线声明(ohosMain 社区线 / jvmMain 上游线),换线不换版
material-icons-extended 1.7.3commonMain 引用 6 个符号(Icons、Filled.Check/Close/Search、Rounded.Add/Check/Close)icons-shim 模块逐字重声明
kotlinx-datetime 0.6.2仅 Clock / TimeZone / toLocalDateTime 三个符号、10 处调用ohos 换 0.8.0-0.1.0-07 线 + Clock 接缝
lifecycle-viewmodel/runtime-compose零源文件引用(catalog 声明但未用)不进 ohos 构建
uiToolingPreview / compose-resources零引用、零资源不进构建

关键结论:JetCo 与 Kore / compose-shimmer 同属「纯 Compose UI 库」——全部价值都是 Compose 语义(Composable / Canvas / state),没有 qrose 那样可隔离的纯 Kotlin 编码器内核。因此路线定为整库编译 + 无头门面:上游每个 Composable 都要在鸿蒙工具链下编译通过(这是 CMP 库适配的核心资产),而屏幕像素由 ArkUI 按桥返回的真实数值绘制。

1.4 页面边界
标题区          JETCO · OPENHARMONY / 版本徽章 + 构建信息行(Kotlin/Compose 版本,来自 jetcoVersion())
状态行          ok · N ops · 最近 op(错误时显示 error 原文)
Tab 切换        图表 / 选择器 / 步进器 / 按钮
图表 Tab        Line / Bar / Candle / Pie 四子视图 + 动画进度滑杆 + 平滑开关 + 点击选中弹值
选择器 Tab      三列滚轮(order 可切 dmy/mdy/ymd,月份三态可切)+ 时间轮(24h/12h、5 分钟间隔吸附)+ Done 结果
步进器 Tab      4 节点 + 前进/后退/标错按钮 + 节点圆 + 虚线连接线(1200ms 进度动画)
按钮 Tab        Stack FAB(T/S/E 混合方向 stagger)/ Radial FAB(三弧)/ Switch / SearchBar 状态机 / Ticket 滑杆

页面启动时先调 jetcoVersion() 做握手,再逐域拉取 defaults。

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

2.1 制品线验证(本适配的成败手)

先查社区 Maven(https://maven.eazytec-cloud.com)上 ohos 制品的真实覆盖。用 .module 元数据逐个验证:

org.jetbrains.compose.runtime:runtime   1.12.0-0.1.0-03 .. 11  → ohosArm64ApiElements-published ✓
org.jetbrains.compose.foundation:foundation 同上 ✓
org.jetbrains.compose.ui:ui            同上 ✓
org.jetbrains.compose.material3:material3 同上 ✓(版本 03..11 与前者完全交集,取 07 与 Kore 对齐)
org.jetbrains.compose.material:material-icons-core 1.7.3       → 无 ohos 变体 ✗
org.jetbrains.compose.material:material-icons-extended 1.7.3   → 无 ohos 变体 ✗
org.jetbrains.kotlinx:kotlinx-datetime 0.8.0-0.1.0-07           → ohosArm64ApiElements ✓(0.6.x-compat 无 ohos ✗)

三个关键结论:其一,Compose 四件套在 1.12.0-0.1.0-07 线上 ohos klib 齐全(含 material3),整库编译路线成立;其二,material-icons 完全没有 ohos 制品,库引用的 6 个图标必须另行供给;其三,ohos 线不发布 *-desktop 子制品,jvm 交叉验证不能从社区线解析——依赖必须按目标线声明。

2.2 工具链钉版

社区此前批准的钉法分两条线:2.2.21 旧线(qrose / haze,配 Gradle 8.14.1)与 2.4.20 新线(KStore / Kore,配 Gradle 9.x)。JetCo 上游是 Kotlin 2.2.20 / Gradle 8.14.3,与 2.2.21 旧线同代;但社区 Compose ohos 线 1.12.0-0.1.0-07 的 klib 元数据要求 Kotlin 2.4.x 读取,因此工具链钉 2.4.20-0.1.0-07 + Gradle 9.6.1(Kore 同款,2.4.20 线支持 Gradle 9.x,wrapper 从 Kore 复制)。

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

pluginManagement {
    repositories {
        // HarmonyOS Kotlin toolchain must resolve first, otherwise ohosArm64()
        // fails with "Unresolved reference" at configuration time.
        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_jetco"

include(":icons-shim")
include(":jetco")
project(":jetco").projectDir = file("JetCoKMP/jetco")
include(":example:nativeApp")
2.3 编译整库:双线依赖与两个关键障碍

jetco/build.gradle.kts 收敛为纯 KMP 模块,Compose 依赖按目标线声明:

kotlin {
    applyDefaultHierarchyTemplate()
    jvm { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } }
    ohosArm64()    // the adaptation
    ohosX64()

    sourceSets {
        ohosMain.dependencies {
            api(libs.compose.runtime)      // 1.12.0-0.1.0-07
            api(libs.compose.foundation)
            api(libs.compose.ui)
            api(libs.compose.material3)
            api(libs.datetime)             // 0.8.0-0.1.0-07
            api(project(":icons-shim"))
        }
        jvmMain.dependencies {
            api(libs.compose.runtime.jvm)  // 上游线 1.9.0
            api(libs.compose.foundation.jvm)
            api(libs.compose.ui.jvm)
            api(libs.compose.material3.jvm)
            api(libs.datetime.jvm)         // 0.6.2
            api(libs.icons.extended.jvm)   // 真实 icons-extended 1.7.3
        }
        commonTest.dependencies { implementation(libs.kotlin.test) }
    }
}

障碍一:icons shim 的位置。 最初把 6 个图标的重声明放进 jetco/src/ohosMain——编译报 Unresolved reference 'icons'。原因:commonMain 的导入看不到平台源集里的声明(源码集可见性单向:平台看得到 common,common 看不到平台)。而 Kore 的双线依赖之所以成立,靠的是「平台源集声明的依赖对 commonMain 在该目标的编译可见」。因此正解是把 shim 做成独立 Gradle 模块(:icons-shim,仅含 ohos 目标),从 ohosMain 依赖引入——klib 里的声明因此对 commonMain 可见,jvm 目标继续用真实制品,两条 classpath 永不相见:

// icons-shim/build.gradle.kts
kotlin {
    applyDefaultHierarchyTemplate()
    ohosArm64()
    ohosX64()
    sourceSets {
        ohosMain.dependencies { api(libs.compose.ui) }
    }
}

shim 内容为 Icons 对象 + materialIcon/materialPath 构建器 + 6 个向量,路径数据逐字取自 material-icons-core:1.7.3 的官方 sources jar(连 Icons.Default = Filled 别名都补齐)。

障碍二:Clock.System 的 common 分析陷阱。 上游 10 处 Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) 编译报 Unresolved reference 'System'。用探针文件判别:同一表达式在 ohosMain 编译通过、在 commonMain 必失败;commonMain 里直接写 kotlin.time.Clock.System(配 @OptIn)则能过。即:kotlinx.datetime.Clock 这个废弃 typealias 在鸿蒙工具链的 common 源码分析中,类型可解析、伴生成员不可解析。处置是全库唯一源码接缝:

// commonMain(新增文件)
internal expect fun jetcoNowLocalDateTime(): LocalDateTime

// ohosMain actual —— 直接引用 typealias 的展开目标
internal actual fun jetcoNowLocalDateTime(): LocalDateTime =
    kotlin.time.Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())

// jvmMain actual —— 保留上游原表达式
internal actual fun jetcoNowLocalDateTime(): LocalDateTime =
    Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())

上游 10 处调用点机械替换为 jetcoNowLocalDateTime()(语义逐字保留)。实测:

./gradlew :jetco:compileKotlinJvm :jetco:jvmTest                       # BUILD SUCCESSFUL
./gradlew :jetco:compileKotlinOhosArm64 :jetco:compileKotlinOhosX64    # BUILD SUCCESSFUL

整库组件(图表 / 选择器 / 步进器 / FAB / 搜索栏 / 开关 / 票券卡)一个没动,全部编入 ohos KLIB。

2.4 测试依赖的处置:上游零测试,补语义闸门

上游 commonTest 只有 1 行 package 的空壳,Gradle 9 会因「零测试发现」直接失败。处置不是关掉检查,而是按 texty 先例新增 JetCoSemanticsTest(11 项),把桥复用面的上游语义钉死(见 6.2)。

第 3 阶段:门面与桥协议建模

3.1 无头门面:复用 vs 转录
com.developerstring.jetco_kmp/**(上游组件库原样编译)
        ↓ 真实对象:*Defaults / PickerDate.format / mapToLineGraphEntries / CandleChange.forPrices
JetCoHeadless.ohos.kt(无头门面:真实取值 + @Composable 体内几何逐行转录,每节标注 file:line)
        ↓ 纯 Kotlin 数据
Bridge.kt(@CName × 2,扁平 JSON 进出,14 op)
        ↓ UTF-8 JSON
N-API jetcoVersion() / jetcoDispatch()
        ↓
ArkUI Index.ets(四 Tab)

门面的复用纪律:能读真实对象的绝不手抄——各族默认配置(PickerDefaults / StepperConfig() / FabMainConfig() / SwitchButtonConfig() / AnimatedSearchBarConfig() / 各图表 *Defaults 全是普通 object/data class,无 @Composable 工厂)、归一化(mapToLineGraphEntries,public)、涨跌分类(CandleChange.forPrices,public)、日期时间格式化(PickerDate.format / toFormattedString、PickerTime.toFormattedString)直接调用;@Composable / DrawScope 体内的几何(点映射、路径截断、网格线、弹窗锚点、K 线 y 映射、饼图 sweep、FAB 偏移、stagger、Ticket 轮廓)按上游公式逐行转录并标注 file:line。

3.2 JSON 契约(14 个 op)
version     版本握手(version/upstream/kotlin/compose/repo)
defaults    域默认值(picker/stepper/fab/searchbar/linegraph/ticket/candle,读真实 *Defaults)
linegraph   n,label{i},value{i},maxValue?,width,height,hPad,axisScaleCount,gridLines,…
            → linePath/areaPath(M/L/C 命令串)、x{i},y{i},ny{i}、gridY{i}、yText{i}、popupAnchor
barchart    n,name{i},value{i},plotWidth,barWidth,barAreaHeight,… → 每条 x/y/w/h、animH{i}、yText{i}
candlestick n,o{i},h{i},l{i},c{i},v{i},… → cx/change/color/highY/lowY/bodyTop/bodyBottom/hollow、
            成交量矩形、paddedLow/High、visibleCount
piechart    n,name{i},value{i},radius,thickness,rotations → start{i},sweep{i},color{i},legend{i}、
            innerRadius、staticRotationDeg
datewheel   order(dmy/mdy/ymd),shortMonths,monthAsNumber,year,month,day → 滚轮项表 + 选中下标 + daysInMonth
timewheel   format(24h/12h),seconds,minuteInterval,… → 时/分/秒/AMPM 项表 + 吸附下标
format      what:date/time/to24 → 真实 PickerDate/PickerTime 格式化、12→24 换算
stepper     n,currentStep,statuses? → 每节点 status/color/icon/iconTint/connectorColor/connectorDashed
fab         mode:stack/radial,n,dir{i}/arc,… → x{i},y{i},delay{i},screenX{i},screenY{i}
searchbar   expanded,hasText,event → 新状态 + effect 串(真实状态机七条转移)
switch      width,height,selected,progress → knobSize/travel/knobX/trackColor/animationMs/easing
ticket      width,height,四角半径,notch,notchWeight → path 命令串 + dividerY + dash

所有值为字符串(数值 parseFloat),颜色 #AARRGGBB 大写,长度单位 vp(密度换算归 ArkTS),数组走逗号串或索引键。

3.3 错误通道

jetcoDispatch 整体 try/catch,Kotlin 异常永远不跨 FFI;非法参数、越界 n、未知 op 全部走 {"ok":"false","op":...,"error":原文},ArkTS 状态行统一渲染。

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

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libjetco_napi.so,CMake 产出,≈64 KB)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosjetco.so)
  │ JetCoDispatch / JetCoVersion
  ▼
UTF-8 JSON + DisposeString
4.2 Kotlin/Native 导出函数
@CName("JetCoVersion")
fun jetcoVersion(): String = versionJson()

@CName("JetCoDispatch")
fun jetcoDispatch(request: String): String = try {
    val json = MiniJson.parse(request)
    when (json.str("op")) {
        "version" -> versionJson()
        "defaults" -> defaultsResponse(json)
        "linegraph" -> lineGraphResponse(json)
        // … 14 个 op
        else -> err(json.str("op").ifBlank { "(missing)" }, "unsupported op")
    }
} catch (t: Throwable) {
    err("dispatch", t.message ?: t.toString())
}

llvm-nm -D 验证:JetCoDispatch / JetCoVersion 均为全局 T 符号。桥模块不应用 compose 编译器插件——门面从不注册组合内容,没有插件变换的 lambda 需要链接。

4.3 C++ N-API 方法分发与 CMake

napi_init.cpp 每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回;KN 返回的字符串经 libohosjetco_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosjetco_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号)。

add_library(ohosjetco SHARED IMPORTED)
set_target_properties(ohosjetco PROPERTIES
  IMPORTED_LOCATION ${KN_SO}          # ../../../libs/${OHOS_ARCH}/libohosjetco.so
  IMPORTED_NO_SONAME TRUE)

add_library(jetco_napi SHARED napi_init.cpp)
target_link_options(jetco_napi PRIVATE "-Wl,--no-undefined" "-Wl,-rpath,\$ORIGIN")
target_link_libraries(jetco_napi PUBLIC libace_napi.z.so libhilog_ndk.z.so ohosjetco)
# POST_BUILD 拷贝 KN so + libc++_shared.so 到身边,$ORIGIN rpath 保 dlopen 互相可见

OHOS_ARCH 为空或 libohosjetco.so 缺失直接 FATAL_ERROR(提示先跑 link 任务)。

第 5 阶段:ArkUI 宿主

5.1 版本握手与调用封装
import { jetcoDispatch, jetcoVersion } from 'libjetco_napi.so';

aboutToAppear(): void {
  this.versionInfo = JSON.parse(jetcoVersion()) as Record<string, string>;  // 版本握手
  this.fetchDefaults('picker'); /* …七个域 */
}

private call(req: Record<string, string>): Record<string, string> {
  const res = JSON.parse(jetcoDispatch(JSON.stringify(req))) as Record<string, string>;
  this.opCount++;
  if (res.ok !== 'true') { this.statusLine = `error: ${res.error}`; }
  return res;
}

只 import libjetco_napi.so,绝不 import KN so——HarmonyOS Kotlin 的 CAdapter 不会把 @CName 业务符号挂到 ArkTS。

5.2 页面交互与真机适配

四 Tab 自上而下的交互全部由桥数值驱动:图表 Canvas 按 linePath/areaPath 命令串重放(px/vp 密度换算在 onReady 完成),点击命中后按 popupAnchor 锚定弹值;滚轮为自绘 snap 组件(List + Scroller,selectorHeight=48 三行、选中行放大加粗、邻行透明度 0.6/0.3/0.15、上下渐隐遮罩——全部来自 picker defaults);FAB 展开按 delay{i} 逐项错峰 translate+opacity+scale;SearchBar 每步事件把 effect 写进操作日志;Ticket 的 notchWeight 滑杆实时重新请求轮廓路径。

开发期在真机上踩到四个 ArkTS 编译/运行适配点并全部修复:@ohos.curves 在该 SDK 是默认导出(import curves from '@ohos.curves');ListAttribute.snapAlign 不可用(改为 onScrollStop 手动 scrollToIndex(idx, true) 吸附 + 亚像素防循环保护);@Builder 返回 void 不能链式 .onClick(回调作为参数传入构建器内部挂载);Circle.fill/stroke 在 SDK 26 才有(图例色点改 Column+borderRadius、圆环改 border 画法)。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosjetco)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/jetco/nativeapp/
│       └── Bridge.kt                         @CName × 2 + 14 op + MiniJson(继承自 cmp_qrose 审核模式)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.jetco、versionName 1.1.0-beta-ohos.1
    └── entry/
        ├── build-profile.json5               externalNativeOptions + abiFilters [arm64-v8a, x86_64]
        ├── libs/{arm64-v8a,x86_64}/          linkDebugShared* 回填的三件套(gitignore)
        ├── src/main/cpp/                     CMakeLists.txt / napi_init.cpp / libohosjetco_api.h
        │                                     / types/libjetco_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 四 Tab 验收页面(约 2700 行)
6.2 构建、签名和安装
./gradlew :jetco:jvmTest                                          # 11 项交叉验证
./gradlew :jetco:compileKotlinOhosArm64 :jetco:compileKotlinOhosX64
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

cd example/harmonyApp
# 命令行打包需 DevEco 自带 JBR(系统 JDK 会让 PackageHap 报 spawn java ENOENT)
export JAVA_HOME="/path/to/DevEco Studio/jbr"
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机,不进入仓库(仓库内 signingConfigs 保持空数组)。

hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.jetco -m entry

四、完整代码对照

4.1 整体架构

com.developerstring.jetco_kmp/**(上游组件库原样编译,ohosArm64 / ohosX64 KLIB)
    │ Composable 全量编译进 .so(核心资产:鸿蒙工具链编译证明)
icons-shim(ohos 专用 klib)
    │ Icons + materialIcon/materialPath + 6 个官方路径向量
JetCoHeadless.ohos.kt
    │ 真实取值(*Defaults / format / mapTo* / CandleChange)+ 几何逐行转录(file:line 标注)
Bridge.kt + MiniJson
    │ JetCoDispatch / JetCoVersion(14 op 扁平 JSON)
libjetco_napi.so(CMake,≈64 KB)
    │ jetcoDispatch / jetcoVersion
Index.ets(四 Tab)
    │ JSON parse + Canvas/滚轮/动画重绘
    ▼
ArkUI 真机页面(版本握手 + 图表 + 选择器 + 步进器 + 按钮族)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_jetco、上游模块移出
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-07 + 双 Compose 线 + 双 datetime 线 + icons 钉版
build.gradle.kts(根)版本 1.1.0-beta-ohos.1、全局禁用 Ohos*Test* 任务
JetCoKMP/jetco/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,依赖按目标线声明
jetco/src/commonMain/.../NowClock.kt唯一源码接缝 expect(Clock 别名 common 分析陷阱)
jetco/src/{ohos,jvm}Main/.../NowClock.*.kt两个 actual,各保留上游原表达式
jetco/src/ohosMain/.../JetCoHeadless.ohos.kt无头门面:真实取值 + 几何转录
jetco/src/commonTest/.../JetCoSemanticsTest.kt11 项 JVM 语义闸门(新增)
icons-shim/6 图标 shim 模块(官方 sources jar 路径逐字转录)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、14 op、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 四 Tab 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(16 节)

4.3 关键 API 对照

层次API作用
门面JetCoHeadless.lineGraphGeometry(...)折线全量几何(真实归一化 + 路径转录)
门面JetCoHeadless.candlestickGeometry(...)K 线 ±5% padded 区间 + 影线/实体/成交量
门面JetCoHeadless.dateWheel / timeWheel滚轮项表 / 闰年 / 吸附 / 初始下标
门面JetCoHeadless.formatDate / formatTime真实 PickerDate.format / PickerTime 格式化
门面JetCoHeadless.stepperNodeVisuals四态节点视觉(真实色板 + 图标优先级)
门面JetCoHeadless.stackPlacements / radialPlacementsFAB 偏移 / stagger / 屏幕修正
门面JetCoHeadless.ticketGeometry票券轮廓路径(四角圆角 − 双半圆缺口)
NativeJetCoDispatch / JetCoVersionC ABI JSON 导出
N-APIjetcoDispatch(req) / jetcoVersion()向 ArkTS 暴露能力
ArkTScall(req)统一分发 + op 计数 + 错误渲染

4.4 ArkTS 与 Kotlin 的边界

const res = JSON.parse(jetcoDispatch(JSON.stringify({
  op: 'linegraph', n: 7,
  label0: 'Mon', value0: 12, /* … label6/value6 */
  width: 312, height: 200, hPad: 24,
  axisScaleCount: 4, gridLines: 4, gridStrokeWidth: 1, maxLabelLength: 6,
})));
// res.linePath === "M 24 188 C …"(vp 坐标),Canvas 直接重放
val entries = map.mapToLineGraphEntries(maxDataValue)   // 真实上游归一化
val stepX = drawableWidth / (entries.size - 1).coerceAtLeast(1)   // LineGraph.kt:419-435 转录
points.add(LinePoint(horizontalPadding + index * stepX,
                      chartHeight - entry.normalizedY * chartHeight, …))

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

五、关键决策说明

决策 1:整库编译而不是提取零依赖内核

JetCo 没有「内核」——图表的坐标计算藏在 DrawScope 里,选择器的滚轮项生成藏在 Composable 默认参数里,FAB 的偏移公式藏在状态动画里。qrose 式的 composeMain 隔离会把库拆空。整库编译让每一个 Composable 都获得「在鸿蒙工具链下编译通过」的证明,且色彩 / 几何 / 格式化的单一事实来源始终是上游代码——这正是「纯 Compose UI 库」第二档路线(Kore / compose-shimmer / eungabi 先例)的意义。

决策 2:icons 走独立 shim 模块而不是平台源集

社区线没有任何 material-icons ohos 制品。把 6 个图标的重声明放进 jetco/src/ohosMain 不可行——commonMain 的导入看不到平台源集里的声明(实测 Unresolved reference 'icons')。正解是仅含 ohos 目标的独立 Gradle 模块 :icons-shim,从 ohosMain 依赖引入:平台源集声明的依赖对 commonMain 在该目标的编译可见(Kore 双线依赖的同一机制),klib 声明因此可达;jvm 目标继续解析 Maven Central 真实制品,两条 classpath 永不相见。路径数据逐字取自官方 material-icons-core:1.7.3 sources jar,不是手绘近似。

决策 3:Clock 接缝是全库唯一源码改动

鸿蒙工具链(Kotlin 2.4.20-0.1.0-07 + datetime 0.8.0-0.1.0-07)下,kotlinx.datetime.Clock 是指向 kotlin.time.Clock 的废弃 typealias;实测该别名的伴生成员访问在平台源集可解析、在 common 源码分析不可解析(同表达式两处对照验证)。10 处上游调用统一改走 jetcoNowLocalDateTime() expect/actual,jvm/ohos 两个 actual 各自保留上游原表达式(ohos actual 直接引用 kotlin.time.Clock.System)。这是本 fork 对上游源码的唯一侵入,与 Kore 的 googleSansFlex 接缝同等地位。

决策 4:依赖换线不换版(datetime 例外单列)

Compose 四件套 ohos 线与上游线版本主体不同(1.12.0 vs 1.9.0),但按目标线声明后 commonMain 一行坐标不用改,jvm 交叉验证仍用上游线 1.9.0——「换线不换版」在 jvm 侧成立。datetime 的 ohos 线只有 0.8.0 体(0.6.x-compat 无 ohos 变体),库只触碰 Clock/TimeZone/toLocalDateTime 三个符号,API 表面一致,风险收敛在编译期验证。

决策 5:补 jvmTest 语义闸门而不是关掉测试发现

上游零测试,Gradle 9 的「零测试发现」会直接失败。按 texty 先例新增 11 项语义测试:钉住归一化 clamp/保护、OHLC 合法性 require、真实日期/时间格式化(含 12AM→"12:05 AM"、12PM→"12:30 PM" 的正午/午夜语义)、默认值契约(yearsRange 1922…2122、rowCount 3 等)。测试跑的是上游原对象,是「换线没漂」的直接证据。

六、测试与验证

6.1 测试环境

本次验证使用 Windows、JDK 21(Gradle org.gradle.java.home)、DevEco Studio 26(E 盘安装,命令行借其 JBR / hvigor / node)、OpenHarmony 命令行工具(hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

./gradlew :jetco:jvmTest

11 项用例全部通过(新增 JetCoSemanticsTest,跑上游原对象):

测试钉住什么
lineGraphNormalizationScalesToMax真实归一化 0/0.5/1 刻度
lineGraphNormalizationClampsAndGuards超界 clamp、NaN→0、0 max→1 保护后 clamp
lineGraphKeepsInsertionOrderlinkedMap 顺序保持
candleChangeClassification涨/跌/平三分类(真实 CandleChange.forPrices)
candlestickEntryValidatesOhlcBoundsCandlestickEntry 的 low≤min / high≥max require
pickerDateFormats / pickerDateMonthTokensAreCorruptedUpstream真实 format:yyyy/dd/d 可用;月份 token 缺陷钉住为可见 diff
pickerTimeFormats24h / 12h真实 24h “HH:mm” 与 12h “hh:mm AM/PM”
datePickerDefaultYearsRangeIs1922to2122 / wheelDefaultsAreContract默认值契约

6.3 HAP 验证

:jetco:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:example:nativeApp:linkDebugSharedOhosArm64 / OhosX64: BUILD SUCCESSFUL
llvm-nm -D: JetCoDispatch / JetCoVersion 均为全局 T 符号
BuildNativeWithCmake / Ninja: finished(双 ABI 编出 libjetco_napi.so)
CompileArkTS: finished
PackageHap: finished(JBR 下 BUILD SUCCESSFUL in 4s260ms)
install bundle successfully / start ability successfully(真机)

entry-default-unsigned.hap 约 48.0 MB(50,359,432 B),内含双 ABI 各三件套(libjetco_napi.so arm64 64 KB / x86_64 66 KB;strip 后 libohosjetco.so arm64 23.4 MB / x86_64 23.7 MB;libc++_shared.so 各 ≈1.3 MB);debugShared 未 strip 时 KN so 分别 36.4 MB / 34.9 MB(含全量 Compose runtime + material3)。

6.4 功能用例

  1. 启动后首屏版本徽章显示 1.1.0-beta-ohos.1,构建信息行显示 Kotlin 2.4.20-0.1.0-07 / Compose 1.12.0-0.1.0-07(来自真实 Native 握手,非静态文字);
  2. 图表 Tab 柱状图 8 根圆角条按桥返回的 x/y/w/h 渲染,条色 #FFEFB8C8、圆角 8 与 defaults 域一致;进度滑杆拖动实时重算 animH;
  3. 折线图平滑曲线 / 关闭 smooth 后折线拐点一致,点击数据点按 popupAnchor 弹出真实值;
  4. 选择器 Tab 切换 order(dmy/mdy/ymd)三列重排;切到 2 月天数按闰年变化(2026→28、2028→29);Done 输出与上游 toFormattedString() 逐字一致;
  5. 时间轮切 12h 出 AM/PM 列,5 分钟间隔吸附到最近网格,to24 换算 12AM→0、12PM→12;
  6. 步进器前进/标错后节点色与连接线虚/实切换符合状态规则,连线按 1200ms 生长;
  7. Stack FAB 三方向混合展开逐项错峰;Radial 三弧切换坐标即变;Switch 点击后滑块 700ms 缓动到 travel;SearchBar 事件流写入操作日志;Ticket 缺口随 notchWeight 滑杆实时重绘;
  8. 状态行 ok · 18 ops 随交互递增,错误 op 显示 error 原文。

6.5 验证结论

组件库本体、无头门面真实取值、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的组件数值会话:版本握手、图表几何、滚轮数据与上游格式化、步进器状态视觉、FAB/开关/搜索栏/票券编排(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/JetCo.git
cd JetCo
export JAVA_HOME="/path/to/jdk-21"
./gradlew :jetco:compileKotlinOhosArm64 :jetco:compileKotlinOhosX64   # klib
./gradlew :jetco:jvmTest                                             # 11 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
export JAVA_HOME="/path/to/DevEco Studio/jbr"                        # HAP 打包用 DevEco 自带 JBR
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.jetco -m entry
hdc shell hilog | grep JetCoNapi

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :jetco 真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 平台源集里的声明对 commonMain 不可见——icons shim 放 src/ohosMain 报 Unresolved reference 'icons',必须做成独立 Gradle 模块再从 ohosMain 依赖;平台源集声明的依赖才对 commonMain 可见(Kore 双线依赖的机制);
  4. kotlinx.datetime.Clock 废弃别名的伴生成员在 common 源码分析中不可解析(平台源集正常)——用同表达式双位置探针判别后,以 expect/actual 接缝收口,两个 actual 各保留上游原表达式;
  5. 社区 Compose ohos 线不发布 *-desktop 子制品,Compose 依赖必须按目标线声明(ohosMain 社区线 / jvmMain 上游线),不能写在 commonMain;
  6. 上游 commonTest 只有 1 行 package 的空壳,Gradle 9「零测试发现」直接失败——补 11 项语义闸门而不是关检查;
  7. 命令行 hvigorw assembleHap 的 PackageHap 需要java:系统没有 java 时报 spawn java ENOENT,必须用 DevEco 自带 JBR 作 JAVA_HOME(KStore 先例同款);
  8. 真机 ArkTS 编译暴露四个 SDK 适配点:@ohos.curves 是默认导出(不是具名导出);该 SDK 版本 ListAttribute.snapAlign 不存在(改 onScrollStop 手动 scrollToIndex 吸附 + 亚像素防循环);@Builder 返回 void 不能链 .onClick(回调入参在构建器内挂载);Circle.fill/stroke 要 SDK 26(改 Column+borderRadius / border 画法);
  9. 根 .gitignore 的 build/ 是任意层级匹配,但 example/harmonyApp 的 .hvigor / .idea / oh_modules / entry/libs 需要单独追加规则,否则生成物混进提交;
  10. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  11. build-profile.json5 的 signingConfigs 条目缺 material 字段会被 hvigor schema 拒绝——仓库内留空数组,勾选自动签名后由 IDE 回填,证书绝不进入 AtomGit。

8.2 已知问题

  • OHOS 无 Compose composition 宿主:Composable 已编进 so 但不上屏,像素由 ArkUI 按桥数值重绘;等社区出现与 Compose 对齐的鸿蒙宿主后,Demo 可逐步换成真实 Compose 视图;
  • 桥是同步一次一调用:live 图表的持续刷新、Compose 动画插值本体不跨 FFI,Demo 用 ArkTS 动画按桥给出的目标值 / 时长 / 参数近似复刻;
  • 图表 X 轴文本宽度无法在 Kotlin 侧测量:桥给锚点,ArkTS 用自身测量居中(上游同样在 Compose 侧测量);
  • 上游缺陷保持原样并写入文档:PickerDate.format 月份 token(MM/MMM/MMMM)被占位符替换损坏输出乱码(Demo 规避只用 yyyy-MM-dd);AnimatedSearchBarAnimationConfig 四个字段未接线(实现用字面量);WheelPickerConfig.height 被 selectorHeight×rowCount 覆盖;12h 模式不滚动直接 Done 可能越界(桥入参约束 1…12 不触发);短月名六月是 “Jun”;柱状归一化无除零保护(桥内补保护);Radial 折叠目标参数是死代码;
  • android / ios / js / wasmJs 目标剥离(源文件留盘),发布、dokka、compose-resources、vanniktech 一并剥离,本 fork 不发 Maven Central。

8.3 未来优化方向

  • 等社区 Compose 鸿蒙 composition 宿主落地后接回真实 Compose 渲染,桥降级为对照工具;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建(借 DevEco 命令行 JBR 链路);
  • 补充 MultiLineGraph / GroupColumnBarChart / DateRangePicker / MorphFloatingActionButton 的桥入口(当前覆盖单线/单柱/日期时间/双 FAB 形态);
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 jetco 整库 commonMain(全部 Compose 组件)
    → 验证社区 Compose ohos 制品线(runtime/foundation/ui/material3 @1.12.0-0.1.0-07)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-07 + Gradle 9.6.1(2.4.20 线)
    → 根构建替换(cmp_qrose 模式,上游构建存 *.upstream)
    → icons 缺口:独立 shim 模块(官方路径逐字转录,源集方案不可行)
    → Clock 陷阱:expect/actual 接缝(全库唯一源码改动)
    → jvm + ohosArm64 + ohosX64 KLIB + 11 项 JVM 语义闸门
    → JetCoHeadless 无头门面(真实取值 + 几何转录)
    → C ABI(@CName × 2)+ 14 op 扁平 JSON
    → C++ N-API(DisposeString 纪律)
    → ArkTS 四 Tab(Canvas 图表 + 自绘滚轮 + 状态驱动组件)
    → ArkTS 真机适配四修复 + HAP(JBR)+ 签名 + 真机验收

9.2 封装层次

  • 库层:上游原版组件(图表 / 选择器 / 步进器 / FAB / 搜索栏 / 开关 / 票券卡)+ 唯一 Clock 接缝;
  • 门面层:JetCoHeadless——真实对象取值 + @Composable 几何逐行转录(file:line 标注);
  • Native 层:双 ABI 动态库,2 个 C ABI 输出 JSON;
  • N-API 层:参数检查、字符串转换和 Native 内存释放(≈64 KB 薄包装);
  • ArkTS 层:版本握手、调用封装、Canvas/滚轮/动画重绘、状态行与错误文本;
  • DevEco 层:CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 「纯 Compose UI 库」的正确答案是整库编译 + 无头门面:库没有可隔离的纯 Kotlin 内核时,整库编译让每个 Composable 拿到鸿蒙工具链的编译证明,无头门面把真实取值(默认配置 / 格式化 / 归一化 / 分类)和转录几何送到 ArkTS——单一事实来源始终在上游代码;
  2. 源码集可见性是双向单行道:平台源集看得到 commonMain 的声明与依赖,commonMain 看不到平台源集的声明,但看得到平台源集声明的依赖——这决定了 icons shim 必须是模块而不是源集,也解释了双线依赖为什么成立;
  3. 接缝要最小且可验证:Clock.System 的 common 分析陷阱用同表达式双位置探针定位后,以 10 处机械替换 + expect/actual 收口;11 项 JVM 语义闸门 + 真机四 Tab 把「没漂」证明到像素级。

9.4 适配成果

当前适配已完成:

  • JetCo 1.1.0-beta 真实组件库的双 ABI Kotlin/Native 编译(上游 commonMain 零结构性改动,唯一 Clock 接缝 + icons-shim 模块);
  • 社区 Compose ohos 制品线(1.12.0-0.1.0-07,含 material3)在纯 UI 库上的完整落地;
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • 14 op 无头门面(真实取值 + 几何转录,file:line 标注);
  • DevEco Stage ArkUI 四 Tab 验收页面(图表 / 选择器 / 步进器 / 按钮族,约 2700 行,真机 ArkTS 适配四修复);
  • 11 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so,unsigned ≈48.0 MB)、签名、设备安装和真机验证;
  • AtomGit 项目文档(README 首页 / OpenHarmony 中英双份 / 上游备份)和本地效果图。

适配过程 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐