开源鸿蒙平台 CMP 三方库「JetCo」适配全流程
本文记录
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 Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21(gradle.properties 里 org.gradle.java.home 钉死本机路径) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 9.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-datetime | ohos 线 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 product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.jetco |
1.3 源码依赖度盘点
| 依赖 | 形态 | 处置 |
|---|---|---|
| compose runtime/foundation/ui/material3 | commonMain 全量使用 | 按目标线声明(ohosMain 社区线 / jvmMain 上游线),换线不换版 |
| material-icons-extended 1.7.3 | commonMain 引用 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.toml | Kotlin 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.kt | 11 项 JVM 语义闸门(新增) |
icons-shim/ | 6 图标 shim 模块(官方 sources jar 路径逐字转录) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 2 个 C ABI、14 op、MiniJson、错误通道 |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 四 Tab 验收页面 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(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 / radialPlacements | FAB 偏移 / stagger / 屏幕修正 |
| 门面 | JetCoHeadless.ticketGeometry | 票券轮廓路径(四角圆角 − 双半圆缺口) |
| Native | JetCoDispatch / JetCoVersion | C ABI JSON 导出 |
| N-API | jetcoDispatch(req) / jetcoVersion() | 向 ArkTS 暴露能力 |
| ArkTS | call(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 |
| lineGraphKeepsInsertionOrder | linkedMap 顺序保持 |
| candleChangeClassification | 涨/跌/平三分类(真实 CandleChange.forPrices) |
| candlestickEntryValidatesOhlcBounds | CandlestickEntry 的 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.0-beta-ohos.1,构建信息行显示 Kotlin2.4.20-0.1.0-07/ Compose1.12.0-0.1.0-07(来自真实 Native 握手,非静态文字); - 图表 Tab 柱状图 8 根圆角条按桥返回的 x/y/w/h 渲染,条色
#FFEFB8C8、圆角 8 与 defaults 域一致;进度滑杆拖动实时重算animH; - 折线图平滑曲线 / 关闭 smooth 后折线拐点一致,点击数据点按
popupAnchor弹出真实值; - 选择器 Tab 切换
order(dmy/mdy/ymd)三列重排;切到 2 月天数按闰年变化(2026→28、2028→29);Done 输出与上游toFormattedString()逐字一致; - 时间轮切 12h 出 AM/PM 列,5 分钟间隔吸附到最近网格,to24 换算 12AM→0、12PM→12;
- 步进器前进/标错后节点色与连接线虚/实切换符合状态规则,连线按 1200ms 生长;
- Stack FAB 三方向混合展开逐项错峰;Radial 三弧切换坐标即变;Switch 点击后滑块 700ms 缓动到
travel;SearchBar 事件流写入操作日志;Ticket 缺口随notchWeight滑杆实时重绘; - 状态行
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 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让
:jetco真正编译成ohosArm64/ohosX64动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- 平台源集里的声明对 commonMain 不可见——icons shim 放
src/ohosMain报Unresolved reference 'icons',必须做成独立 Gradle 模块再从 ohosMain 依赖;平台源集声明的依赖才对 commonMain 可见(Kore 双线依赖的机制); kotlinx.datetime.Clock废弃别名的伴生成员在 common 源码分析中不可解析(平台源集正常)——用同表达式双位置探针判别后,以 expect/actual 接缝收口,两个 actual 各保留上游原表达式;- 社区 Compose ohos 线不发布
*-desktop子制品,Compose 依赖必须按目标线声明(ohosMain 社区线 / jvmMain 上游线),不能写在 commonMain; - 上游 commonTest 只有 1 行 package 的空壳,Gradle 9「零测试发现」直接失败——补 11 项语义闸门而不是关检查;
- 命令行
hvigorw assembleHap的 PackageHap 需要java:系统没有 java 时报spawn java ENOENT,必须用 DevEco 自带 JBR 作JAVA_HOME(KStore 先例同款); - 真机 ArkTS 编译暴露四个 SDK 适配点:
@ohos.curves是默认导出(不是具名导出);该 SDK 版本ListAttribute.snapAlign不存在(改onScrollStop手动scrollToIndex吸附 + 亚像素防循环);@Builder 返回 void 不能链.onClick(回调入参在构建器内挂载);Circle.fill/stroke要 SDK 26(改Column+borderRadius/border画法); - 根
.gitignore的build/是任意层级匹配,但example/harmonyApp的.hvigor/.idea/oh_modules/entry/libs需要单独追加规则,否则生成物混进提交; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); 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 三条经验
- 「纯 Compose UI 库」的正确答案是整库编译 + 无头门面:库没有可隔离的纯 Kotlin 内核时,整库编译让每个 Composable 拿到鸿蒙工具链的编译证明,无头门面把真实取值(默认配置 / 格式化 / 归一化 / 分类)和转录几何送到 ArkTS——单一事实来源始终在上游代码;
- 源码集可见性是双向单行道:平台源集看得到 commonMain 的声明与依赖,commonMain 看不到平台源集的声明,但看得到平台源集声明的依赖——这决定了 icons shim 必须是模块而不是源集,也解释了双线依赖为什么成立;
- 接缝要最小且可验证:
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。
参考文档
- AtomGit/oh-tpc/JetCo
- KMP&CMP 鸿蒙社区
- 上游 DeveloperChunk/JetCo(1.1.0-beta,Apache-2.0)
- 同路线已过审仓:cmp_qrose(模式命名来源)、Kore、compose-shimmer、eungabi、KStore
- OpenHarmony N-API
- DevEco Studio
- OpenHarmony ArkUI
更多推荐



所有评论(0)