开源鸿蒙平台 CMP 三方库「Koala Plot」适配全流程
本文记录
Koala Plot(koalaplot-core)0.12.1接入 OpenHarmony 的完整过程,覆盖图表库盘点、鸿蒙 Compose 制品线选型、真实引擎整库编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
Koala Plot是基于 Compose Multiplatform 的图表与绘图库:饼图/环形图、折线图(线性/对数/分类轴)、堆叠面积图、柱状图、子弹图、雷达/极坐标图、热力图,以及可组合的图表布局与图例,Android、桌面、iOS、Web 单一 API 共享。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取")有本质区别:Koala Plot的 commonMain 全部 79 个 Kotlin 文件都是androidx.compose.*代码,没有可剥离的非 Compose 内核;而社区 Maven 上已经出现完整的鸿蒙 Compose klib 制品线(runtime / foundation / ui / material3 / animation1.12.0-0.1.0-07,全部带ohosArm64/ohosX64变体),因此不需要隔离——整个上游图表库(切片语义、轴模型刻度与缩放/平移状态机、自动定标、二维直方图分箱、色标、手势处理)被原样编译成 OpenHarmony KLIB,再通过@CNameC ABI + CMake N-API 把图表计算引擎暴露给 ArkTS,ArkUI Canvas 重绘;同批源码另有一个jvm()目标对上游制品线(Compose Multiplatform 1.11.1)交叉验证"整库编译没有引入漂移"。
项目地址: AtomGit/oh-tpc/koalaplot-core
上游仓库: KoalaPlot/koalaplot-core
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
Koala Plot 为 Compose 应用提供"图表"这一件事的完整答案:PieChart 可组合切片(悬停展开)、可组合标签与连接线、环形中心内容、首帧动画;XYGraph 的线性/对数/分类轴、可组合标题与值标签、数据点符号、单/双轴缩放平移、注记、直线/阶梯/贝塞尔曲线;堆叠面积;VerticalBarPlot 聚类/堆叠/负值/非零起点;BulletGraphs 子弹图;雷达/极坐标;HeatMapPlot 二维直方图与色标;ChartLayout + 图例。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套图表能力,但上游构建(Kotlin 2.4.10 / Gradle 9.4.1 / AGP 9.0.1)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以画出图表,却无法证明公共图表引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证图表引擎、Compose 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Kotlin 2.4.10 / Gradle 9.4.1 / AGP 9.0.1(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建;旧钉法 Kotlin 2.2.21-1.0.0 读不了 2.4 元数据。 |
| 依赖矩阵 | 全部 79 个 commonMain 文件 import androidx.compose.*——runtime / foundation / ui / material3 / animation 每一件都要有 ohos klib 变体才能解析;coroutines 同理。 |
| 没有可提取的内核 | 与 qrose(纯 Kotlin 编码器 + Painter 隔离)相反,Koala Plot 是 100% Compose 库,composeMain 隔离老路等于把仓库清空。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 组合语义跨不了桥 | @Composable、手势、动画是 Kotlin 侧概念,C ABI 只能同步一来一回;OHOS 侧也没有社区 Compose 线的组合宿主。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:整库 ohos 编译(含唯一的 Modifier.onGestureInput expect/actual 接缝的真实 ohos 实现)、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。图表计算语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和重绘。
1.2 库提供的能力
Demo 提供一组真实的图表计算会话,对应桥接的全部入口:
- 饼图切片语义:切片权重、起始角、扩展角、入场动画进度 β 送入真实的
makePieSliceData(上游 internal 函数,PieChartComposable 的切片引擎),返回每片的起始角/扫过角;切片颜色来自真实的generateHueColorPalette(HSL 均匀色相); - 坐标轴与交互状态机:线性轴(构造参数、主/次刻度、缩放/平移/重置)全部由真实
DoubleLinearAxisModel执行——zoom()/pan()带最小/最大视图范围钳制,连点缩放可观察到真实边界;对数轴刻度与偏移来自真实LogAxisModel; - 数据点映射:折线/散点的每个数据点通过
probes参数在真实模型里computeOffset,ArkTS 永远不做值↔像素的二次插值; - 二维直方图热力图:样本送入真实
generateHistogram2D分箱,值域由真实autoScaleRange计算,格子颜色由真实linearColorScale(蓝→红)逐格插值; - 版本握手:
koalaPlotVersion()返回 fork 版本号,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
op / axis | 回显本次操作与轴会话名(x/y 各自持有真实视图状态)。 |
major / minor | 主/次刻度表 [{v, off}],值与偏移都来自真实模型。 |
viewStart / viewEnd | 缩放/平移后的当前视图范围(真实钳制后的结果)。 |
probeOff | 数据点探测偏移数组(真实 computeOffset)。 |
slices / rows | 饼图切片角度与颜色 / 热力图逐格颜色(真实引擎计算)。 |
引擎侧完整能力(子弹图、极坐标、堆叠面积、注记、图例排版、悬停展开等)全部在上游 commonMain 里原样随编译进入 .so,未单独开桥接入口。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | commonMain 全部 79 个文件原样编译,不提取、不隔离、不重写;仅新增 2 个 ohos 源文件(手势 actual + headless 门面)。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);android/ios/js/wasmJs 目标、Sonatype 发布、dokka/detekt/ktlint 随根构建剥离(原文保留为 *.upstream)。 |
| 桥接稳定 | 使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | 版本主体与上游完全一致(Compose 1.11.1 / material3 1.9.0 / coroutines 1.11.0),ohos 侧只换仓库到鸿蒙制品线(换线不换版,双线拆分见第 2 阶段)。 |
| UI 完整 | 单应用覆盖全部桥接入口:饼图 / 坐标轴 / 热力图 / 链路四个 Tab,全部 Canvas 重绘。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。OHOS 侧暂无社区 Compose 线的组合宿主(没有把
@Composable树变成像素的渲染层),所以页面沿用 cmp_qrose / vetra-ui / compose-shelf 的社区模式:跨 C ABI 只传值,ArkTS Canvas 重绘——但每一个角度、刻度、偏移、分箱计数和颜色都由.so内的上游原版实现计算。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认鸿蒙 Compose klib 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、依赖双线拆分
第 3 阶段:整库编译 ── commonMain 79 文件原样编译 ohos KLIB、唯一 expect 的触摸手势 ohos actual
第 4 阶段:桥协议建模 ── 7 个 op 的扁平 JSON 契约、headless 门面进库内(internal 可见性)
第 5 阶段:原生桥接 ── @CName C ABI、C++ N-API、$ORIGIN rpath、DisposeString
第 6 阶段:ArkUI 宿主 ── 四 Tab 图表 Demo、Canvas 重绘、HAP、真机验收
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
src/commonMain/ 上游图表引擎(79 文件,进 ohos 构建,jvm + 双 ohos)
src/ohosMain/
└─ .../util/ModifierExtensions.ohos.kt 唯一 expect 的 ohos actual(触摸手势)
└─ .../KoalaPlotHeadless.ohos.kt 桥协议门面:7 op + 真实引擎调用
src/jvmMain/ (jvmTest/) 上游 desktopMain/desktopTest 平移(jvm 交叉验证目标)
src/{android,ios,js,wasmJs}Main/ 上游其余平台源,留盘不进构建
example/nativeApp/ Kotlin/Native 动态库(@CName 桥 + 双 ABI 拷贝任务)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
convention-plugins/ versions.properties detekt.yml .github/ 上游工具链,留盘不进构建
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :example:nativeApp,根项目即库模块,rootProject.name = "ohos_koalaplot_core"。上游的 refreshVersions 插件、convention-plugins(Sonatype 发布)、dokka / detekt / ktlint、AGP classpath 全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21(org.gradle.java.home 钉在 F:/cmp/.tools/jdk-21.0.12.1+1) |
| Compose(ohos 线) | 1.12.0-0.1.0-07(runtime / foundation / ui / material3 / animation 均有 ohos klib) |
| Compose(jvm 线) | 1.11.1 / material3 1.9.0 / coroutines 1.11.0(Maven Central,与上游 0.12.1 同版本体) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 9.6.1(2.4.20 线支持 Gradle 9.x) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.koalaplot |
export JAVA_HOME="F:/cmp/.tools/jdk-21.0.12.1+1"
java -version
1.3 源码依赖度盘点
| 源码集 | 文件 | 依赖形态 | 处置 |
|---|---|---|---|
commonMain | 79 | 全部 androidx.compose.{runtime,foundation,ui,material3,animation} + coroutines,零平台 API | 整体编译(jvm + 双 ohos,一行不改) |
desktopMain / desktopTest | 1 / 18 | onGestureInput 鼠标 actual + Compose 桌面测试 | 平移为 jvmMain / jvmTest(模板下 jvm 目标源集名) |
androidMain iosMain jsMain wasmJsMain | 各 1 | onGestureInput 各平台 actual | 留盘不进构建(ohos 另写第 6 个 actual) |
expect 声明全库只有 1 处:internal expect fun Modifier.onGestureInput(...)。上游给了 5 个平台 actual——android/ios 是触摸同型(pointerInput + 公共手势 handler + 样条衰减),desktop/js/wasmJs 是鼠标滚轮型。ohos 补第 6 个,照 android/ios 抄:
// src/ohosMain/kotlin/io/github/koalaplot/core/util/ModifierExtensions.ohos.kt
internal actual fun Modifier.onGestureInput(
key1: Any?, key2: Any?, gestureConfig: GestureConfig,
onZoomChange: (size: IntSize, centroid: Offset, zoom: ZoomFactor) -> Unit,
onPanChange: (size: IntSize, pan: Offset) -> Boolean,
): Modifier = this then Modifier.pointerInput(key1, key2, gestureConfig) {
val gesturesHandler = TransformGesturesHandlerImpl(
panHandler = PanHandlerImpl(),
panFlingHandler = PanFlingHandlerImpl(
panFlingBehavior = PanFlingBehavior(
animationSpec = SplineBasedFloatDecayAnimationSpec(density = this),
),
),
zoomHandler = if (gestureConfig.independentZoomEnabled) StickyAxisZoomHandler(/*…*/)
else LockedRatioZoomHandler(),
)
gesturesHandler.detectTransformGestures(/*…*/)
}
值得单独说明:SplineBasedFloatDecayAnimationSpec 在鸿蒙 Compose 线中存在——android/ios 的惯性衰减在 ohos 上原样可用,不需要降级到 exponentialDecay。加上库内门面,这就是整个 fork 唯二的新增源文件。
1.4 页面边界
Tab 切换 饼图 / 坐标轴 / 热力图 / 链路(底部四 Tab)
饼图 Tab 切片1-6 值滑杆、起始角(-180..180)、扩展角(90..360)、动画β(0..1)、重置
Canvas 饼图(真实角度+真实色板)、图例(值+角度)、状态行
坐标轴 Tab 线性/对数切换、正弦/散点数据预设
放大×1.25 / 缩小×0.8 / ←平移 / 平移→ / 重置(驱动真实 zoom()/pan() 状态机)
Canvas 轴+主/次刻度+曲线(probeOff 映射)、视图范围行
热力图 Tab 采样数 200/500/1000、分箱 6..24、重新采样
Canvas 逐格颜色(真实 linearColorScale)、状态行
链路 Tab 版本握手、调用链路图、桥接日志(最近 8 次 op/ok/耗时/字节)、真实与复刻对照
页面启动时先调 koalaPlotVersion() 做握手,每个 Tab 的 Canvas onReady 即发起首次桥接调用。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 ohos 的 Compose 制品。用 .module 元数据逐个验证:
org.jetbrains.compose.runtime:runtime:1.12.0-0.1.0-07
variants: ohosArm64Api-published / ohosX64Api-published / jvmApi-published
org.jetbrains.compose.foundation:foundation:1.12.0-0.1.0-07 同上
org.jetbrains.compose.ui:ui: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.kotlinx:kotlinx-coroutines-core:1.11.0-0.1.0-07 同上
三个关键结论:其一,Compose 制品线一应俱全,100% Compose 库整库编译的障碍已经消失——qrose 时代"隔离 Painter"是因为这条线当时不存在;其二,ohos 线不发布 *-desktop 子制品,jvm() 交叉验证目标不能从这条线解析桌面变体,必须双线拆分(jvm 走 Maven Central 的 1.11.1,与上游 0.12.1 构建所用版本体完全一致——仍是换线不换版);其三,material-icons 等周边线没有 ohos 变体,好在 Koala Plot 不用图标(vetra-ui 需要另做 icons-shim)。
2.2 工具链钉版(为什么是 2.4.20-0.1.0-07)
社区此前批准的钉法是 HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 用的)。但 coroutines 1.11.0-0.1.0-07 依赖 kotlin-stdlib 2.4.20-0.1.0-07(元数据 2.4.0),Kotlin 2.2.21 只能读到 2.3.0,编译期直接报:
Class 'kotlin.Unit' was compiled with an incompatible version of Kotlin.
The binary version of its metadata is 2.4.0, expected version is 2.3.0 or less.
工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07。额外好处:上游源码本来就是 Kotlin 2.4.10 编的,2.4.20 与之同代,源码级兼容成本为零;Gradle 用 9.6.1(2.4.20 线支持 Gradle 9.x,上游 wrapper 9.4.1 同代小步升级,不像 2.2.21 旧线必须降 8.14.1)。
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()
}
}
// dependencyResolutionManagement 同序;rootProject.name = "ohos_koalaplot_core"
include(":example:nativeApp")
2.3 编译真实引擎
根 build.gradle.kts 收敛为极简 KMP 库构建(上游原文保留为 build.gradle.kts.upstream):
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.compose.compiler) // 库声明 @Composable,编译器插件必须保留
}
kotlin {
explicitApi()
applyDefaultHierarchyTemplate()
jvm() { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } // 交叉验证用
ohosArm64()
ohosX64()
sourceSets {
ohosMain.dependencies { // ohos 线(1.12.0-0.1.0-07)
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.material3)
api(libs.compose.ui)
api(libs.compose.animation)
implementation(libs.kotlinx.coroutines)
}
jvmMain.dependencies { // jvm 线(Maven Central,上游同版本体)
implementation(libs.compose.runtime.jvm) // …1.11.1
implementation(libs.compose.foundation.jvm)
implementation(libs.compose.material3.jvm) // …1.9.0
api(libs.compose.ui.jvm)
api(libs.compose.animation.jvm)
implementation(libs.kotlinx.coroutines.jvm) // …1.11.0
}
}
}
Compose 依赖按目标线声明在 ohosMain / jvmMain 上,commonMain 保持线无关——同一批源码、两条解析线(vetra-ui / compose-shelf 已审核的同款拆法)。一个必须处理的源集细节:上游用 kotlin.mpp.applyDefaultHierarchyTemplate=false + jvm("desktop") 手工层级,而鸿蒙构建启用默认层级模板后 jvm("desktop") 不再生成 desktopMain 访问器——处置是目标改回 jvm(),src/desktopMain / src/desktopTest git mv 到 src/jvmMain / src/jvmTest(git 100% rename,内容零改动)。实测:
./gradlew :compileKotlinJvm # BUILD SUCCESSFUL(79 文件,上游制品线)
./gradlew :compileKotlinOhosArm64 :compileKotlinOhosX64 # BUILD SUCCESSFUL(同批文件,ohos 线)
引擎的全部 commonMain 文件一个没动,新增的只有 ohos actual 与门面两个文件。
2.4 测试依赖的处置:kotlin-test 的实验性标记
上游 18 个 desktopTest 文件基于 Kotlin 2.4.10 的 kotlin-test 编写;2.4.20-0.1.0-07 线把其中使用的断言 API 标为 @ExperimentalKotlinTestApi,直接编译报 opt-in 错误。处置是给 jvmTest 编译加一个 opt-in(测试文件零改动),并沿用社区纪律——测试统一在 JVM 验证,根构建全局禁用 ohos 测试任务:
jvmTest { compilerOptions { optIn.add("kotlin.test.ExperimentalKotlinTestApi") } }
allprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
实测 ./gradlew compileTestKotlinJvm 通过——上游测试文件一个字节不动。
第 3 阶段:桥协议建模
3.1 JSON 契约
io.github.koalaplot.core/**(上游图表引擎,原样编译)
↓ makePieSliceData / 轴模型状态机 / autoScaleRange / generateHistogram2D
KoalaPlotHeadless.ohos.kt(库内门面,7 op 扁平 JSON 进出)
↓ KoalaPlotDispatch / KoalaPlotVersion(@CName)
N-API koalaPlotDispatch() / koalaPlotVersion()
↓
ArkUI Index.ets(四 Tab,Canvas 重绘)
请求与响应(节选):
请求 : {"op":"pie","values":"30,20,45","start":0,"extend":360,"beta":1}
{"op":"axis.linear","axis":"x","min":-0.6,"max":6.9,"len":320,
"zoom":1.25,"pivot":0.5,"probes":"0,0.5,1"}
{"op":"axis.log","expStart":-1,"expEnd":3}
{"op":"axis.category","count":5,"offset":"full"}
{"op":"autoscale","values":"..."}
{"op":"histogram2d","xs":"...","ys":"...","bins":16}
响应 : {"ok":"true","op":"pie","total":95,"slices":[{"i":0,"value":30,
"startAngle":0,"angle":113.68,"color":"#FF800080"},…]}
{"ok":"true","op":"axis.linear","viewStart":1.26,"viewEnd":6.9,
"major":[{"v":1.5,"off":0.045},…],"minor":[…],"probeOff":[0.06,0.31,0.56]}
{"ok":false,"op":"…","error":"…"}
两个关键设计:axis.linear 会话有状态——同一 axis 名 + 同一构造键的调用复用缓存的 DoubleLinearAxisModel 实例,zoom/pan 增量作用于当前视图,zoom()/pan() 里的最小/最大视图范围钳制因此被真实执行(Demo 连点"放大"能观察到钳制);probes 让数据点映射也发生在真实模型里——ArkTS 拿到的 probeOff 就是 computeOffset 的原值,渲染端永不做值↔像素的二次插值。
3.2 门面为什么放在库内
桥接需要调用 makePieSliceData(internal——上游把切片语义封装在 PieChart Composable 内部)。internal 对库模块外不可见,所以门面放在库自己的 src/ohosMain(compose-shelf 的 ShelfHeadless.ohos.kt 同法):
// src/ohosMain/kotlin/io/github/koalaplot/core/KoalaPlotHeadless.ohos.kt(节选)
public object KoalaPlotHeadless {
private val linearAxes = HashMap<String, Pair<String, DoubleLinearAxisModel>>()
private fun pieResponse(json: MiniJson): String {
val slices = makePieSliceData( // 上游 internal 引擎,同模块可见
data = values, beta = beta,
pieStartAngle = start.deg, pieExtendAngle = extend.deg,
)
val colors = generateHueColorPalette(values.size) // 上游真实色板
/* 拼装 slices[{i,value,startAngle,angle,color}] */
}
}
example/nativeApp 的 Bridge.kt 只做 @CName 导出,且不应用 compose 编译器插件——桥只调用非 Composable 门面,没有需要插件变换的 lambda 要链接(vetra-ui / compose-shelf 同款说明)。
3.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("KoalaPlotDispatch")
fun koalaPlotDispatch(request: String): String = try {
/* op 分发 */
} catch (t: Throwable) {
"""{"ok":"false","op":"dispatch","error":${jsonString(t.message ?: t.toString())}}"""
}
非法 JSON、缺 op、空 values、max<=min、不支持的 op,全部走同一个 ok:false 通道,ArkTS 侧统一渲染进状态行。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libkoalaplot_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohoskoalaplot.so,28.1 MB / 26.9 MB)
│ KoalaPlotDispatch / KoalaPlotVersion
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制图表语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("KoalaPlotDispatch")
fun koalaPlotDispatch(request: String): String // 7 op 一口进
@CName("KoalaPlotVersion")
fun koalaPlotVersion(): String // 版本握手
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
KoalaPlotDispatch | koalaPlotDispatch(req) | makePieSliceData + DoubleLinearAxisModel / LogAxisModel / CategoryAxisModel / autoScaleRange / generateHistogram2D / linearColorScale |
KoalaPlotVersion | koalaPlotVersion() | fork 版本握手(真机版本行的数据来源) |
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"koalaPlotDispatch", nullptr, NapiKoalaPlotDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"koalaPlotVersion", nullptr, NapiKoalaPlotVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohoskoalaplot_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohoskoalaplot_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。
4.4 CMake 和类型声明
add_library(ohoskoalaplot SHARED IMPORTED)
set_target_properties(ohoskoalaplot PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(koalaplot_napi SHARED napi_init.cpp)
target_link_libraries(koalaplot_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoskoalaplot)
OHOS_ARCH 为空或 libohoskoalaplot.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const koalaPlotDispatch: (request: string) => string;
export const koalaPlotVersion: () => string;
第 5 阶段:ArkUI 宿主
5.1 触发调用与状态刷新
private callBridge(op: string, req: string): string {
const t0 = new Date().getTime();
const reply = koalaPlotDispatch(req);
const ms = new Date().getTime() - t0;
const parsed = JSON.parse(reply) as VersionResp;
this.logs = [{ op: op, ok: parsed.ok, ms: ms, bytes: reply.length }]
.concat(this.logs).slice(0, 8); // 链路 Tab 桥接日志
return reply;
}
private dispatchLinearAxis(name: string, zoom: number, pan: number, reset: boolean): void {
const req = '{"op":"axis.linear","axis":"' + name + '","min":-0.6,"max":6.9,' +
'"len":320,"probes":"' + this.xs.join(',') + '"' +
(zoom !== 1 ? ',"zoom":' + zoom + ',"pivot":0.5' : '') +
(pan !== 0 ? ',"pan":' + pan : '') + (reset ? ',"reset":"true"' : '') + '}';
this.lastX = JSON.parse(this.callBridge('axis.linear(' + name + ')', req)) as AxisResp;
}
每次桥接调用的 op、ok、耗时与响应字节数进链路 Tab 的日志区;Canvas 在响应到达后重绘。
5.2 页面交互
四个 Tab 自上而下:饼图 Tab 用 6 个切片值滑杆 + 起始角/扩展角/动画 β 滑杆驱动真实 makePieSliceData,图例显示每片值与扫过角;坐标轴 Tab 的放大/缩小/平移/重置按钮把参数发给真实 zoom()/pan(),画布下方实时显示钳制后的视图范围,曲线与散点全部经 probeOff 映射;热力图 Tab 控制采样数与分箱数,重新采样按钮换 LCG 种子;链路 Tab 展示版本握手行、ArkTS → NAPI → C ABI → headless 门面 → 真实引擎 分层说明、桥接日志与"真实与复刻"对照。
5.3 对比度和错误边界
操作按钮深色底白字,数据用蓝色主线(#FF1E88E5)与灰色刻度分层;ok:false 时错误原文进状态行与日志。Native 调用本身抛错(理论不该发生)同样被 try/catch 收进状态行。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohoskoalaplot)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/io/github/koalaplot/nativeapp/
│ └── Bridge.kt @CName × 2(门面薄封装)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.koalaplot、versionName 0.12.1-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/libohoskoalaplot_api.h
├── src/main/cpp/types/libkoalaplot_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
./gradlew :compileKotlinJvm compileTestKotlinJvm # 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库(仓库内 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.koalaplot -m entry
四、完整代码对照
4.1 整体架构
io.github.koalaplot.core/**(上游图表引擎原样编译,79 文件)
│ PieChart / XYGraph / VerticalBarPlot / BulletGraphs / HeatMapPlot / 手势 / 图例
io.github.koalaplot.core.KoalaPlotHeadless(库内 ohosMain 门面)
│ 7 op 扁平 JSON:pie / axis.linear / axis.log / axis.category / autoscale / histogram2d / version
Bridge.kt(@CName × 2)
│ KoalaPlotDispatch / KoalaPlotVersion
libkoalaplot_napi.so
│ koalaPlotDispatch / koalaPlotVersion
Index.ets
│ JSON parse + Canvas 重绘
▼
ArkUI 真机页面(四 Tab)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_koalaplot_core、上游工具链移出 |
gradle/libs.versions.toml | Kotlin 2.4.20-0.1.0-07 + Compose 双线(ohos 1.12.0-0.1.0-07 / jvm 1.11.1)钉版 |
build.gradle.kts(根) | 版本 0.12.1-ohos.1、jvm()+双 ohos 目标、按线依赖、全局禁用 Ohos*Test* 任务 |
src/ohosMain/.../ModifierExtensions.ohos.kt | 唯一 expect 的 ohos actual(触摸手势 + 样条衰减) |
src/ohosMain/.../KoalaPlotHeadless.ohos.kt | 桥协议门面(7 op、轴会话缓存、MiniJson、错误通道) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 2 个 C ABI(门面薄封装,无 compose 编译器插件) |
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 | 鸿蒙适配主文档(21 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | makePieSliceData(values, beta, start, extend) | 饼图切片语义(internal,门面同模块调用) |
| 引擎 | generateHueColorPalette(count) | HSL 均匀色相色板(Demo 切片颜色来源) |
| 引擎 | DoubleLinearAxisModel | 线性轴:构造校验、视图状态机 |
| 引擎 | AxisModel.zoom(factor, pivot) / pan(amount) | 视图缩放/平移,带最小/最大范围钳制 |
| 引擎 | computeTickValues(axisLength: Dp) | 主/次刻度值生成 |
| 引擎 | computeOffset(value) | 值→[0,1] 偏移(Demo 曲线与刻度位置来源) |
| 引擎 | LogAxisModel(range) | 对数轴刻度与偏移 |
| 引擎 | CategoryAxisModel / autoScaleRange() | 分类轴偏移 / 量级取整自动定标 |
| 引擎 | generateHistogram2D(...) / linearColorScale(...) | 二维分箱 / 逐格颜色插值 |
| Native | KoalaPlotDispatch / KoalaPlotVersion | C ABI JSON 导出 |
| N-API | koalaPlotDispatch(req) / koalaPlotVersion() | 向 ArkTS 暴露能力 |
| ArkTS | callBridge(op, req) | 触发调用、计时、刷新日志与画布 |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(koalaPlotDispatch(
'{"op":"pie","values":"30,20,45","start":0,"extend":360,"beta":1}')) as PieResp;
// res.slices[0] = { i:0, value:30, startAngle:0, angle:113.68, color:'#FF800080' }
// Canvas 侧 arc(cx, cy, r, startAngle*PI/180, (startAngle+angle)*PI/180) 直接使用
val slices = makePieSliceData(values, beta, start.deg, extend.deg) // 上游原件
val colors = generateHueColorPalette(values.size) // 上游原件
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:整库编译而不是 composeMain 隔离
qrose 时代的"隔离 Painter"路线,前提是库里存在不依赖 Compose 的内核(编码器),以及当时 ohos 上没有 Compose 制品。本次适配先验证制品线(runtime / foundation / ui / material3 / animation 的 ohos klib 全部存在),障碍已消失——再隔离等于把图表库清空。整库编译让切片语义、轴状态机、刻度生成、手势处理全部是上游原件(vetra-ui / compose-shelf 已审核的同一分支)。
决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 9.6.1
coroutines 1.11.0-0.1.0-07 基于 stdlib 2.4.20 构建,旧钉法 2.2.21 读不了 2.4 元数据;上游本来就是 Kotlin 2.4.10,2.4.20 同代零降级。Gradle 方面 2.4.20 线支持 9.x,wrapper 用 9.6.1——只有 2.2.21 旧线才必须降 8.14.1,两条工具链线的经验不能互相套用。
决策 3:Compose 依赖双线拆分(换线不换版)
ohos 线不发布 *-desktop 子制品,jvm 交叉验证目标必须走 Maven Central。拆分的代价是依赖声明从 commonMain 挪到 ohosMain / jvmMain 两个源集;收益是 jvm 线用与上游 0.12.1 完全相同的版本体(Compose 1.11.1 / material3 1.9.0 / coroutines 1.11.0)——同一批源码对两条线编译通过,本身就是"换线不换版、源码未漂移"的证明。
决策 4:桥只传计算值,门面放在库内
OHOS 没有社区 Compose 线的组合宿主,@Composable 上不了屏;桥传"计算结果"(角度/刻度/偏移/分箱/颜色)而不是"绘制指令",让 ArkTS 重绘始终以下游身份消费上游数学。makePieSliceData 是 internal,门面必须进库内 src/ohosMain(compose-shelf 的 ShelfHeadless 同法);桥上不引入 kotlinx.serialization,手写 MiniJson(cmp_qrose 继承),不增加 KN 依赖、保持 C 签名稳定。
决策 5:jvm 交叉验证 + 测试零改动
引擎逻辑在 commonMain,JVM 与 ohos 编译同一份代码;上游 18 个测试文件平移到 jvmTest 后在鸿蒙工具链下编译通过(唯一追加一个 kotlin.test.ExperimentalKotlinTestApi opt-in,纯工具链版本差异),测试文件零改动——交叉验证的有效性才成立。运行需要桌面 Compose 测试环境(AWT),不在本 fork 验收范围,如实记录。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x64)、JDK 21(F:/cmp/.tools/jdk-21.0.12.1+1)、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew :compileKotlinJvm :compileKotlinOhosArm64 :compileKotlinOhosX64
./gradlew compileTestKotlinJvm
同一批 79 个 commonMain 文件对两条制品线(上游 CMP 1.11.1 / 社区 1.12.0-0.1.0-07)编译全部通过;18 个上游测试文件(饼图 3、坐标轴 7、柱状 2、热力图 2、图例 1、手势 1、动画 1、工具 2 等)在鸿蒙工具链下编译通过、零改动。桌面 UI 测试的运行需要 AWT 环境,属上游 CI 职责,本 fork 以编译验证为准并如实记录。
6.3 链接与 HAP 验证
:compileKotlinJvm / compileTestKotlinJvm: BUILD SUCCESSFUL
:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: KoalaPlotDispatch / KoalaPlotVersion / libohoskoalaplot_symbols 均为全局 T 符号
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs/{arm64-v8a,x86_64}
BuildNativeWithCmake: finished(双 ABI 编出 libkoalaplot_napi.so)
CompileArkTS: finished
PackageHap: finished
install bundle successfully
start ability successfully
libohoskoalaplot.so 因嵌入完整 Compose 栈,arm64 约 28.1 MB、x86_64 约 26.9 MB(debugShared 未 strip;vetra-ui 同路线 28.9 MB 属同量级),HAP 由 DevEco 签名构建生成后安装真机。
6.4 功能用例
- 启动后链路 Tab 握手显示
upstream 0.12.1 / fork 0.12.1-ohos.1 / kotlin 2.4.20-0.1.0-07 / compose 1.12.0-0.1.0-07(来自真实 Native 调用,非静态文字); - 饼图 Tab 拖动切片值滑杆,扇区角度实时变化,状态行出现
生成成功 pie N 片 total …;值拖到 0 的切片消失; - 起始角滑杆整体旋转饼图;扩展角 90…360 变出扇形盘;动画 β 滑杆按真实
makePieSliceData的 β 语义收缩; - 坐标轴 Tab 正弦/散点切换,曲线与散点全部经
probeOff映射绘制,主/次刻度与标签随视图变化; - 连点"放大 ×1.25",视图收窄、刻度变密,到
minViewExtent后状态行 view 区间不再变化(真实钳制);缩小/平移同理受maxViewExtent与范围边界约束;"重置"回到全量程; - 对数轴模式刻度为 0.1/1/10/100/1000,位置来自真实
LogAxisModel.computeOffset; - 热力图 Tab 调采样数与分箱数,高斯团清晰,状态行出现
生成成功 histogram2d N×N samples … max/bin …;重新采样更换 LCG 种子; - 链路 Tab 日志按时间倒序记录每次调用的 op、ok、耗时与响应字节数。
6.5 验证结论
图表引擎、Compose 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的图表计算会话:饼图切片语义、轴刻度生成、缩放/平移钳制、数据点映射与二维直方图分箱(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/koalaplot-core.git
cd koalaplot-core
export JAVA_HOME="F:/cmp/.tools/jdk-21.0.12.1+1"
./gradlew :compileKotlinJvm compileTestKotlinJvm # 交叉验证
./gradlew :compileKotlinOhosArm64 :compileKotlinOhosX64 # KLIB
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
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.koalaplot -m entry
hdc shell hilog | grep KoalaPlot
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让库模块真正编译成
ohosArm64/ohosX64KLIB 并链出动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- 上游
kotlin.mpp.applyDefaultHierarchyTemplate=false+jvm("desktop")的组合在默认层级模板下失效——desktopMain访问器不生成;处置是目标改jvm()并把源集目录 git mv 到jvmMain/jvmTest,而不是去手工拼层级; - Kotlin 2.4.20 线的 kotlin-test 把上游测试所用断言 API 标为实验,编译期 opt-in 错误;处置是 jvmTest 编译加
kotlin.test.ExperimentalKotlinTestApi,测试文件零改动; - 链接完整 Compose 栈时 konanc 默认 3g 堆不够,编译器 JVM 直接原生 malloc 失败(Windows 报"页面文件太小");
kotlin.native.jvmArgs=-Xmx5g(注意大写 A),16 GB 机器上 Gradle 守护进程压到-Xmx1g、workers.max=1,一次链一个 ABI; - 并发构建下偶发
Gradle build daemon has been stopped,gradlew --stop后用--no-daemon重跑即恢复,不是工程问题; - ohos Compose 线不发
*-desktop子制品,jvm 目标必须双线拆分;把 ohos 坐标写到 commonMain 会让 jvm 解析失败; SplineBasedFloatDecayAnimationSpec在 ohos 线可用(android/ios 触摸 actual 可原样照抄),这一点先验证再动手,能省掉降级exponentialDecay的语义偏差;- HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free();napi 字符串先取长度再取内容(长度优先缓冲); - CMake 对 KN so 用
IMPORTED_NO_SONAME TRUE+$ORIGINrpath,避免 Windows 风格绝对路径写进DT_NEEDED; - 签名材料由 DevEco 自动写入
build-profile.json5,仓库内保持空signingConfigs,证书绝不进入 AtomGit。
8.2 已知问题
- OHOS 无社区 Compose 线的组合宿主:
@ComposableAPI(PieChart/XYGraph/VerticalBarPlot/BulletGraphs…)编译进 KLIB 但不上屏,屏幕呈现由 ArkTS 重绘;hoverable悬停展开、鼠标跟踪等指针特性随宿主缺失一并不可用; - 桥协议只覆盖计算面:布局测量(SubcomposeLayout、标签放置)、动画时序、图例排版属于组合宿主职责,不在桥内;
- 上游 desktopTest 在鸿蒙工具链下可编译、未运行(需要桌面 Compose 测试环境与 AWT);
axis.category与autoscale已在协议层,Demo 未单独出 Tab,验收走链路 Tab 日志;- android / ios / js / wasmJs 目标剥离(源文件留在树里),发布、dokka、detekt 一并剥离,本 fork 不发 Maven Central;
- HAP 安装必须使用与 bundle name
org.terminator.ohos.koalaplot匹配的签名。
8.3 未来优化方向
- 跟进社区组合宿主线(
@cpf-kmp-cmp/compose+ComposeArkUIViewController,lazytable 已趟通):版本体追平后把 Demo 从 ArkTS 重绘切到真实 Compose 渲染,KoalaPlotHeadless桥保留为无 UI 场景的计算面; - 补齐
axis.category/autoscale的独立 Demo Tab,桥接极坐标(PolarGraph)与子弹图计算面; - 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。
九、总结
9.1 核心难点回顾
上游 koalaplot-core(79 文件,100% Compose)
→ 验证 ohos Compose klib 制品线(runtime/foundation/ui/material3/animation)
→ 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1)
→ 根构建替换(cmp_qrose 模式)+ 依赖双线拆分(换线不换版)
→ ohosArm64 / ohosX64 KLIB + 唯一 expect 的触摸手势 actual + 库内 headless 门面
→ C ABI(@CName × 2)+ 7 op 扁平 JSON
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析 + Canvas 重绘
→ ArkUI 真机四 Tab 验收(饼图 / 坐标轴缩放平移 / 热力图 / 链路)
9.2 封装层次
- 引擎层:上游原版
PieChart/XYGraph/VerticalBarPlot/BulletGraphs/HeatMapPlot/ 手势 / 图例(整库编译); - 门面层:库内
KoalaPlotHeadless——7 op 扁平 JSON、轴会话状态、探测点映射、错误通道; - Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、JSON 解析、Canvas 重绘、桥接日志;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 先验证生态制品线再定适配路线:Compose ohos klib 线补齐后,100% Compose 库的正确答案就是"整库编译 + 补一个手势 actual + 一个 headless 门面";qrose 的隔离路线是制品缺失时代的产物,路线选择必须基于当天的制品可达性,而不是历史结论;
- 工具链版本跟着依赖走,源集结构跟着模板走:Kotlin 钉版由 coroutines 制品线的 stdlib 元数据反推(2.4.20-0.1.0-07),Gradle 经验按工具链线区分(2.4.20 线留 9.x);启用默认层级模板就要接受
jvm()的源集命名,平移目录好过手工拼层级; - 没有组合宿主时,"计算真实、渲染复刻"是可验收的中间态:桥只传计算值,让每一个角度、刻度、偏移、分箱和颜色都出自
.so内的上游原件,ArkTS 重绘只是下游消费;jvm 双线交叉验证补上"整库编译没有引入漂移"的证明,剩下的真实渲染等组合宿主线合流。
9.4 适配成果
当前适配已完成:
- Koala Plot
0.12.1真实图表引擎的双 ABI Kotlin/Native 编译(上游commonMain79 文件零重写,仅 2 个 ohos 新增源文件:手势 actual + headless 门面); - 鸿蒙 Compose 制品线(1.12.0-0.1.0-07)在纯 Compose 图表库上的完整落地,
jvm()交叉验证线与上游版本体完全一致(换线不换版); - 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号),7 op 扁平 JSON 协议含带状态的轴缩放/平移会话与数据点探测映射; - DevEco Stage ArkUI 四 Tab 验收页面(饼图切片语义、坐标轴刻度与缩放钳制、热力图分箱配色、版本握手与桥接日志);
- 上游 18 个测试文件在鸿蒙工具链下编译验证(零改动)+ 双 ABI 链接(arm64 ≈28.1 MB / x86_64 ≈26.9 MB)、HAP 构建、签名、设备安装和真机运行;
- AtomGit 项目文档、OpenHarmony README(中英双份)和本地效果图。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。
参考文档
更多推荐



所有评论(0)