本文记录 compose-multiplatform-chart 接入 OpenHarmony 的完整过程,覆盖图表库源码依赖度盘点、Compose 源码集隔离、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、软件光栅化复现 Compose 绘制语义、ArkUI 真机页面、HAP 构建和真机验收。

compose-multiplatform-chart 是基于 Compose Multiplatform 的图表库:LineChart 面向大数据集(内部先做 LTTB 降采样再绘制)、LiveLineChart 面向实时数据、BarChart 提供柱状图,全部通过 Compose Canvas 渲染,支持平滑曲线、区域渐变填充、数据点、带标签的坐标轴与捏合缩放手势。本次适配走的是社区早中期路线(qrose / Vico / Reorderable 的"算法内核真编译 + Compose UI 隔离"):上游工具链(Kotlin 1.9.20 / CMP 1.5.10)与鸿蒙工具链(HarmonyOS Kotlin 2.2.21-1.0.0)对不齐,@Composable 组件无法编进 ohos,因此数据模型与 LTTB 降采样算法原样编译成 OpenHarmony KLIB,Compose 组件隔离到不参与编译的 composeMain,鸿蒙侧用 Kotlin/Native 软件光栅化复现上游的绘制语义(轴标签位置、辅助网格线、拐角平滑、渐变填充),一个能画 200 万点级折线图与柱状图的完整管线运行在真机上,ArkUI 页面作为验收宿主。

项目地址: AtomGit/oh-tpc/compose-multiplatform-chart.git

上游仓库: maximilianproell/compose-multiplatform-chart

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

compose-multiplatform-chart 为 Compose Multiplatform 应用提供"数据 → 图表"这一件事的完整答案:LineChart 接收 List<LineDataSet>,数据多于 250 点时内部自动走 LTTB(Largest-Triangle-Three-Buckets)降采样,这正是它能流畅绘制上游 Demo 中 200 万行心率 CSV 的原因;LiveLineChart 砍掉降采样换实时性;BarChart 的 BarChartEntry(xValue, yValue) 用字符串标签建模柱体。样式面覆盖 LineConfig(平滑曲线 / 数据点 / 区域填充 / 线宽)与 LabeledXAxisConfig / LabeledYAxisConfig(标签数量、格式化器、辅助线开关)。KMP 项目把这些能力放在 commonMain,由 Android、iOS 共享。OpenHarmony 应用同样需要这套图表能力,但上游构建(Kotlin 1.9.20 / CMP 1.5.10 / AGP 8.2.0 / Gradle 8.2.1,Groovy 根脚本)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS 画个折线,页面可以显示,却无法证明公共的 LTTB 算法与数据模型在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证算法内核、光栅化输出、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 1.9.20 / CMP 1.5.10 / AGP 8.2.0(Groovy 根构建)与鸿蒙工具链无法共存于一个 Gradle 构建。
Compose 依赖全面渗透库的 18 个源文件里 10 个直接 import androidx.compose(组件、配置、模型里都有 Color / Dp),ohos 编译器看不到这些符号。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
绘制语义跨不了桥Canvas / Path / PathEffect.cornerPathEffect / TextMeasurer 是 Compose 运行时概念,C ABI 只能同步一来一回。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:数据模型与 LTTB 算法的 ohos 编译、Compose 源码集隔离(cmp_qrose 模式)、软件光栅化复现绘制语义和 C ABI/N-API 桥接层。降采样行为完全由上游原版算法负责,ArkTS 只负责调用 Native、解析 JSON 和展示 PNG。

1.2 库提供的能力

Demo 提供一个真实的图表会话,对应桥接的全部入口:

  • 折线图(line):指定波形(正弦 / 心率)与点数(100…5000),数据在 Kotlin/Native 侧由可复现 LCG 生成器产出;多于 250 点自动 LTTB 降采样——与上游 LineChart 内部行为一致;
  • 大数据 LTTB(bigdata):点数 1 万…200 万,降采样档位 LTTB 250 / LTTB 1000 / 关闭,状态行直接显示 in → out 点数对比,把上游"200 万行 CSV 也能画"的核心卖点变成可验收项;
  • 柱状图(bar):values + labels 逗号串入参,三套预设(工作日 / 季度 / 六项)一键切换,对应上游 BarChartEntry 模型;
  • 样式参数:平滑曲线 / 数据点 / 区域填充三开关、线宽 1-8px、五套前景背景色板、画布尺寸与轴标签数量。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
kind / width / height回显图表类型与 PNG 尺寸。
inCount / outCount / bucketsLTTB 前后点数与桶数——验证降采样发生的直接证据。
minY / maxY数据值域,用于 JVM 交叉验证对账。
bytes / pngBase64PNG 字节数与 Base64 载荷。

引擎侧完整能力(LTTB 分桶、三角形面积选点、边界点保留、DataPoint / BarChartEntry 模型)全部在上游 commonMain 里原样随编译进入 .so,一行未改;JVM 侧保留 jvm() 目标做交叉验证,证明 ohos .so 里执行的降采样逻辑没有漂移。

1.3 实现适配

维度要求
代码复用commonMain 保留 8 个纯 Kotlin 文件(DataPoint、BarChartEntry、LTTB 采样全套 6 个)原样编译,不提取、不重写;唯一新增源文件是给桥用的 40 行入口 MultiplatformChart.kt。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建从 Groovy 替换为极简 ohos Kotlin DSL 构建(cmp_qrose 模式);上游 app(Android demo)、shared(iOS demo)、iosApp 留盘不进构建。
桥接稳定使用 1 个 C ABI(ChartRender)和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律库模块零第三方依赖(纯 Kotlin),不存在制品线问题;Kotlin 钉 2.2.21-1.0.0(qrose 同款已审钉法)。
UI 完整单页三 Tab 覆盖全部桥接入口:折线样式、大数据降采样对比、柱状图预设与自定义。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。鸿蒙侧没有 Compose Canvas,图表由 Kotlin/Native 软件光栅化输出 PNG,ArkTS Demo 只是把桥暴露的绘制管线画出来。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与源码 Compose 依赖度、确定交付物
第 2 阶段:目标与构建打通 ── 根构建替换(Groovy → KTS)、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 8.14.1
第 3 阶段:源码集拆分     ── 10 个 Compose 文件迁 composeMain、8 个纯 Kotlin 留 commonMain、新增 MultiplatformChart 入口
第 4 阶段:原生桥接与光栅化 ── @CName C ABI、Rasterizer 复现绘制语义、5x7 像素字体、PNG 编码、LCG 数据生成
第 5 阶段:ArkUI 宿主     ── 三 Tab 页面、参数状态机、Base64 → PixelMap 显示
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
multiplatformchart/                    上游图表库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
multiplatformchart/src/commonMain/     数据模型 + LTTB 采样全套 + MultiplatformChart 入口(8+1 个文件)
multiplatformchart/src/composeMain/    上游 Compose 组件 ×10(LineChart/LiveLineChart/BarChart/Axis/Config)
app/                                   上游 Android demo(AGP + Hilt),留盘不进 settings
shared/ + iosApp/                      上游 iOS demo,留盘不进 settings
example/nativeApp/                     Kotlin/Native 动态库(Bridge + Rasterizer + Font5x7 + PngEncoder + ChartData)
example/harmonyApp/                    DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md         鸿蒙适配说明(中英双份)
README.upstream.md                     上游原文保存
doc/img/                               真机效果图

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :multiplatformchart 与 :example:nativeApp,rootProject.name = "ohos_multiplatformchart"。上游的 maven-publish / vanniktech / signing 发布插件、AGP classpath、Android namespace 全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(Eclipse Temurin,org.gradle.java.home 写进 gradle.properties)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(wrapper 整体替换;2.2.21 线要求 Gradle 8.x)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.chart
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点

对库的 18 个源文件逐个 grep import,按"是否触碰 androidx.compose"分成两堆:

分类文件依赖形态处置
纯 Kotlin ×8DataPoint、BarChartEntry、sampling/{Point, Area, Bucket, Triangle, OnePassBucketizer, LTThreeBuckets}零依赖、零 UI、零 expect留在 commonMain 原样编译
Compose ×10LineChart、BarChart、Axis、XaxisLabel、Calculations、LabeledAxisConfig、LineConfig、BarConfig、LineDataSet、LineChartCalculationsandroidx.compose.{runtime, foundation, ui, material3, animation}迁入 composeMain,ohos 不编

这个分布说明图表库与存储库(KStore)的适配面完全不同:存储库的引擎全在纯 Kotlin 里,真编译整库即可;图表库的价值大头(绘制)恰恰在 Compose 侧。好在"能算"的部分——LTTB 降采样、数据模型——恰好是纯 Kotlin,而且是上游的核心卖点(200 万点能画)。拆分的红线是:留下的代码不引用移走代码的任何类型。逐文件核对后确认 DataPoint / BarChartEntry / sampling 六件套自洽,拆分是干净的;反向依赖(composeMain → commonMain)无所谓,因为它们本来就不参与 ohos 编译。

1.4 页面边界
标题区          multiplatformchart · OPENHARMONY / 副标题(CMP 图表库 · Kotlin/Native ohosArm64/ohosX64 · LTTB 降采样)
Tab 切换        折线图 / 大数据 LTTB / 柱状图
折线图 Tab      波形(正弦/心率)、点数(100/300/1000/5000)、平滑曲线/数据点/区域填充开关、strokeWidth 滑条
大数据 Tab      点数(1万/10万/100万/200万)、LTTB 档位(250/1000/关闭)、波形切换
柱状图 Tab      预设(工作日/季度/六项)、values/labels 可编辑文本域
公共区          五套色板、生成按钮、Image(PixelMap) 图表区、状态行、元信息行(kind/size/png/耗时 + LTTB in→out)

页面启动时 aboutToAppear 自动生成一次默认折线图,作为 L3 运行层的第一张图。

第 2 阶段:目标与构建打通

2.1 根构建替换(Groovy → Kotlin DSL)

上游根脚本是 Groovy 的 settings.gradle / build.gradle(AGP + Hilt 插件声明、Android 仓库配置),鸿蒙工具链下配置期直接死。整体替换:

// settings.gradle.kts
pluginManagement {
    repositories {
        // HarmonyOS Kotlin 工具链仓库必须排在最前,
        // 否则解析到官方 Kotlin 插件,ohosArm64()/ohosX64() 配置期 Unresolved
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
    repositories { /* 同上 */ }
}

rootProject.name = "ohos_multiplatformchart"

include(":multiplatformchart")
include(":example:nativeApp")
// build.gradle.kts(根)
plugins {
    alias(libs.plugins.kotlin.multiplatform) apply false
}

allprojects {
    group = "io.github.maximilianproell"          // 沿用上游 group
    version = providers.gradleProperty("version").orElse("2.1.0-ohos.1").get()
}

subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }        // 测试统一 JVM 跑
}

gradle.properties 写入 JDK 21 路径、version=2.1.0-ohos.1 与 kotlin.native.ignoreDisabledTargets=true;libs.versions.toml 收敛为只钉 kotlin = "2.2.21-1.0.0" 一个版本;wrapper 整体替换为 8.14.1(gradle-wrapper.jar + gradlew.bat 一并换掉,版本不配套的 wrapper jar 会校验失败)。

2.2 工具链钉版(为什么是 2.2.21-1.0.0 + Gradle 8.14.1)

社区已审钉法(qrose / haze):HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1 + JDK 21。本库零第三方依赖,不需要追新制品线(对比 KStore 为读 coroutines 2.4 元数据升到 2.4.20 线)——没有依赖推着升级时,严格沿用已审核的旧线,审核与复现成本最低。上游源码是 Kotlin 1.9.20 编的,2.2.21 向后兼容无压力。

2.3 编译算法内核

multiplatformchart/build.gradle.kts 从"AGP + CMP + 发布三件套"收敛为纯 KMP 模块:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

    jvm()            // 交叉验证用
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {}   // 纯 Kotlin,零依赖
    }
}

实测:

.\gradlew.bat :multiplatformchart:compileKotlinJvm
.\gradlew.bat :multiplatformchart:compileKotlinOhosArm64 :multiplatformchart:compileKotlinOhosX64
# 全部 BUILD SUCCESSFUL

LTTB 六件套与数据模型一个没动,ohos 编译器看不到任何 androidx.compose 符号。

2.4 上游遗留模块的处置

app(Android demo,Hilt/kapt/AGP 8.2)、shared(iOS framework)、iosApp(Xcode 工程)留盘不进 settings.gradle.kts——它们引用的 libs.plugins.android.* / compose-multiplatform 别名已从版本目录移除,拿来 sync 只会报别名找不到。与 KStore 处置 kstore-storage 同款:存在方式只有"留盘不进构建"这一种。

第 3 阶段:桥协议建模

3.1 JSON 契约
at.maximilianproell.multiplatformchart/**(上游模型 + LTTB,原样编译)
        ↓ DataPoint / BarChartEntry / LTThreeBuckets.sorted
Bridge.kt(@CName × 1,扁平 JSON 进出)
        ↓ 生成数据 / 降采样 / 光栅化 / PNG
Rasterizer + Font5x7 + PngEncoder
        ↓ UTF-8 JSON(含 pngBase64)
N-API chartRender()
        ↓
ArkUI Index.ets(三 Tab)

请求与响应:

请求 : {"kind":"line","gen":"sine","count":1000,"downsample":-1,
        "smooth":true,"dots":true,"fill":false,"strokeWidth":3,
        "fg":"#1D4ED8","bg":"#FFFFFF","yLabels":5,"xLabels":5}
       {"kind":"bar","values":"12,31,7,25,18","labels":"MO,DI,MI,DO,FR"}
响应 : {"ok":true,"kind":"line","width":960,"height":520,
        "inCount":1000,"outCount":252,"buckets":250,
        "minY":22.3,"maxY":80.4,"bytes":12345,"pngBase64":"..."}
       {"ok":false,"error":"..."}

三个刻意设计:其一,大数据不过 JSON——百万点在 KN 侧由 LCG 生成器产出,ArkTS 只传 gen/count/seed 三个数,既避免巨型字符串也测出 LTTB 真实收益;其二,downsample 默认 -1(自动:点数 >250 就降 250 桶),逐字对齐上游 LineChart 的内部行为,0 模拟 LiveLineChart 的不降采样路径;其三,seed 默认 42,同 seed 必出同序列,JVM 与真机可对账。

3.2 入口封装为什么需要 MultiplatformChart

上游 LTThreeBuckets.sorted 操作的是采样专用的 Point(x: Double, y: Double),图表 API 用的是 DataPoint(xValue: Float, yValue: Float),上游 LineChart 在两者间手动 map(commonMain → map(Point) → sorted → map(DataPoint))。桥不能碰 @Composable 内部,这段转换收进 commonMain 的公开入口:

public object MultiplatformChart {
    public fun downsample(data: List<DataPoint>, desiredBuckets: Int): List<DataPoint> {
        if (data.size <= desiredBuckets + 2) return data
        val sampled = LTThreeBuckets.sorted(
            input = data.map { Point(x = it.xValue.toDouble(), y = it.yValue.toDouble()) },
            inputSize = data.size,
            desiredBuckets = desiredBuckets,
        )
        return sampled.map { DataPoint(xValue = it.x.toFloat(), yValue = it.y.toFloat()) }
    }
}

这是全仓唯一新增的库源文件(对应 KStore 的 8 行 dispatcher actual 位置),算法本体零改动。

3.3 错误通道

导出整体 try/catch,Kotlin 异常永远不跨 FFI:

@CName("ChartRender")
fun chartRender(request: String): String = try {
    /* kind 分发 + 参数钳制 + 光栅化 */
} catch (t: Throwable) {
    """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}

非法 points 串、空 values、越界参数(钳制兜底)全部走同一个 ok:false 通道,HAP 不崩。

第 4 阶段:原生桥接与光栅化(技术难点)

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libchart_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohoschart.so)
  │ ChartRender
  ▼
UTF-8 JSON + DisposeString

HarmonyOS Kotlin 的 CAdapter 不会把 @CName 业务符号挂到 ArkTS 模块——libohoschart.so 里能 nm 到 ChartRender,但 ArkTS import 'libohoschart.so' 看不到它。所以 CMake 必须再包一层 NAPI so,ArkTS 只 import libchart_napi.so。

4.2 Kotlin/Native 导出函数
@CName("ChartRender")
fun chartRender(request: String): String   // line / bar 一口进,JSON 含 PNG Base64
C ABI 符号ArkTS 方法覆盖内核
ChartRenderchartRender(req)数据生成 + MultiplatformChart.downsample + 光栅化 + PNG

单符号设计:图表间差异全在 JSON 字段(kind / gen / values),不像存储库需要按 op 分发,一个入口足够,以后加图表类型只加字段不改签名。

4.3 光栅化:把 Compose 绘制语义翻译成像素

Rasterizer.kt 逐条复现上游的绘制语义,这是本次适配技术含量最高的部分:

上游(Compose Canvas)光栅化实现语义对齐点
drawYAxisWithLabels(Axis.kt)drawYAxis()标签值 = min + step*(n-1-i),画在 plotH/(n-1)*i 高度;最顶部不画辅助线;标签位于辅助线左上方、贴图内左侧
drawLine(alpha = 0.4F) 辅助线fillRect(alpha = 0.4)横贯绘图区、40% 透明
drawXLabel(clipOnBorder = false)drawXLabels()图表下方、按值居中、越界收回画布(左 <0 拉回 0,右溢出拉回 width-textWidth)
PathEffect.cornerPathEffect(strokeWidth)cornerSmooth()每个内部顶点沿入/出边各退 min(r, 边长/2),画 a → 顶点 → b 二次贝塞尔(8 段采样)
Brush.verticalGradient(lineColor → Transparent)fillPolygonVerticalGradient()扫描线逐行填充,alpha 从折线顶部到图底线性衰减(上限 0.45)
drawPoints + StrokeCap.Round(半径 4.dp)fillCircle(r = 4.0)点数 >600 自动关闭(250 桶 LTTB 后最多 252 点,恰好在阈值内)
BarChart 柱宽 12.dp + 剩余均分间距自适应柱宽plotW/n*0.55(clamp 3…64),间距公式保持上游形状;柱标签居中收回

坐标变换同样复刻 LineChartCalculations.dataToOffSet 的语义:x = left + (x-minX)/(maxX-minX)*plotW,y = bottom - (y-minY)/ySpan*plotH。

文字是图表轴标签的硬需求,而 Kotlin/Native 没有系统字体。Font5x7.kt 内置经典 5x7 点阵字体(数字、大写字母、- . : % / + 共 40 字形,列字节位 0 为顶行),2 倍缩放绘制——轴标签以数字为主,字形足够;实测真机轴刻度清晰可读。

PNG 编码(PngEncoder.kt)走 zlib stored 块(不压缩、零依赖):ARGB 像素 → 滤波 0 行 → stored DEFLATE 块 + adler32 → IHDR/IDAT/IEND + crc32。常规图表几十 KB,ArkTS Base64Helper.decodeSync + image.createImageSource 一次出 PixelMap。

数据生成(ChartData.kt)用 Numerical Recipes LCG:sine 是双正弦叠加微噪声、heartrate 是 70bpm 随机游走 + 偶发尖峰,对应上游 Demo 的两类数据形态(基础折线 / 心率 CSV)。

4.4 C++ N-API 与 CMake
napi_property_descriptor desc[] = {
    {"chartRender", nullptr, NapiChartRender, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr}};

回调统一三步:napi_typeof 校验 string、OH_LOG_INFO 打请求字节数、调 ChartRender 并包装返回。KN 字符串必须经 libohoschart_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。API 26 的 NAPI 头把 napi_get_value_string_utf8 缓冲区标成非 const——先量长度再开 std::vector<char> 拷贝,规避 std::string::data() 的 const 限制。

add_library(ohoschart SHARED IMPORTED)
set_target_properties(ohoschart PROPERTIES IMPORTED_LOCATION ${KN_SO} IMPORTED_NO_SONAME TRUE)

add_library(chart_napi SHARED napi_init.cpp)
target_link_libraries(chart_napi PUBLIC libace_napi.z.so libhilog_ndk.z.so ohoschart)

OHOS_ARCH 为空或 libohoschart.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath + POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 互相可见。

export const chartRender: (request: string) => string;

第 5 阶段:ArkUI 宿主

5.1 参数状态机与触发
private buildRequest(): string {
  if (this.tab === 'bar') {
    return JSON.stringify({ kind: 'bar', values: this.barValues,
      labels: this.barLabels, fg: this.fg, bg: this.bg });
  }
  if (this.tab === 'bigdata') {
    return JSON.stringify({ kind: 'line', gen: this.gen, count: this.bigCount,
      downsample: this.buckets, dots: false, smooth: true, fg: this.fg, bg: this.bg });
  }
  return JSON.stringify({ kind: 'line', gen: this.gen, count: this.count,
    downsample: -1, smooth: this.smooth, dots: this.dots, fill: this.fill,
    strokeWidth: this.strokeWidth, fg: this.fg, bg: this.bg });
}

private generate(): void {
  const started: number = Date.now();
  const raw: string = chartRender(this.buildRequest());
  this.lastMillis = Date.now() - started;         // 耗时入状态行
  const parsed: RenderOk = JSON.parse(raw) as RenderOk;
  /* inCount/outCount/buckets 入元信息行 → decodePng */
}

PNG 解码三步:Base64Helper.decodeSync → buffer.slice → createImageSource(...).createPixelMap(),Image(pixelMap) 全宽显示。大数据 Tab 固定 dots: false——百万点画圆点既慢又没意义,桥侧 >600 点也会自动忽略。

5.2 页面交互

三 Tab 自上而下:Tab 胶囊行 → 参数面板(随 Tab 切换)→ 五套色板(双色块预览 + 选中黑框)→ 生成按钮(防重入)→ 图表 Image 区 → 状态行 → 元信息行。每个参数按钮点击即重新生成,所见即所得;大数据 Tab 额外显示 LTTB: {in} 点 → {out} 点 (buckets={n}) 对账行和耗时毫秒数。

5.3 对比度和错误边界

选中态深色白字、未选中浅灰深字;ok:false 时错误原文进状态行;Native 调用本身抛错(理论不该发生)被 try/catch 收进状态行与 hilog。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohoschart)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       ├── Bridge.kt                         @CName("ChartRender") + MiniJson(继承自 cmp_qrose 审核模式)
│       ├── Rasterizer.kt                     绘制语义复现 + PxCanvas
│       ├── Font5x7.kt                        5x7 点阵字体
│       ├── PngEncoder.kt                     zlib stored PNG
│       └── ChartData.kt                      LCG 演示数据生成
└── harmonyApp/
    ├── build-profile.json5                   signingConfig=default(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.chart、versionName 2.1.0-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/libohoschart_api.h
        ├── src/main/cpp/types/libchart_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 三 Tab 验收页面
6.2 构建、签名和安装
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"

.\gradlew.bat :multiplatformchart:compileKotlinJvm `
              :example:nativeApp:linkDebugSharedOhosArm64 `
              :example:nativeApp:linkDebugSharedOhosX64

用 DevEco Studio 打开 example/harmonyApp(不要打开仓库根目录),File → Project Structure → Signing Configs 勾选自动签名,构建 HAP:

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

四、完整代码对照

4.1 整体架构

at.maximilianproell.multiplatformchart/**(上游模型 + LTTB 原样编译)
    │ DataPoint / BarChartEntry / LTThreeBuckets
MultiplatformChart.downsample(唯一新增入口)
    │ DataPoint ↔ Point 转换 + LTTB
Bridge.kt + MiniJson
    │ ChartRender
Rasterizer + Font5x7 + PngEncoder
    │ PNG Base64
libchart_napi.so
    │ chartRender
Index.ets
    │ JSON parse + PixelMap
    ▼
ArkUI 真机页面(三 Tab)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_multiplatformchart、app/shared 移出
gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 单版本钉版
build.gradle.kts(根)版本 2.1.0-ohos.1、全局禁用 Ohos*Test* 任务
multiplatformchart/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64
multiplatformchart/src/composeMain/(10 文件)上游 Compose 组件隔离(ohos 不编)
multiplatformchart/.../MultiplatformChart.kt唯一新增库源文件(downsample 入口)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt1 个 C ABI、参数钳制、MiniJson、错误通道
nativeApp/.../Rasterizer.kt绘制语义复现 + PxCanvas 像素画布
nativeApp/.../Font5x7.kt / PngEncoder.kt / ChartData.kt点阵字体 / 零依赖 PNG / LCG 数据
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鸿蒙适配主文档(20 节)

4.3 关键 API 对照

层次API作用
模型DataPoint(xValue, yValue)折线数据点(commonMain 原样编译)
模型BarChartEntry(xValue: String, yValue: Float)柱体 + 字符串标签
算法LTThreeBuckets.sorted(input, size, buckets)LTTB 主入口(上游原件)
算法OnePassBucketizer.bucketize分桶(余数均匀摊到前几个桶)
算法Triangle.result三桶滑动窗口里选最大三角形面积的点
入口MultiplatformChart.downsample(data, buckets)新增桥用入口(DataPoint ↔ Point 换装)
NativeChartRenderC ABI JSON 导出(line / bar 一口进)
N-APIchartRender(req)向 ArkTS 暴露能力
ArkTSbuildRequest() / generate()参数拼装与触发

4.4 ArkTS 与 Kotlin 的边界

const raw = chartRender(JSON.stringify({
  kind: 'line', gen: 'heartrate', count: 1000000,
  downsample: 250, smooth: true, dots: false, strokeWidth: 3,
  fg: '#1D4ED8', bg: '#FFFFFF'
}));
// JSON.parse(raw) → { ok:true, inCount:1000000, outCount:252, pngBase64:'...' }
val raw = /* 生成 100 万点 */
val buckets = if (raw.size > 250) 250 else 0
val points = if (buckets > 0) MultiplatformChart.downsample(raw, buckets) else raw
val image = Rasterizer.line(points, width, height, fg, bg, grid, text,
    smooth, dots, fill, strokeWidth, yLabels, xLabels, minY, maxY)

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

五、关键决策说明

决策 1:composeMain 隔离而不是砍掉 Compose 组件

上游 10 个 Compose 文件不是死代码:它们是 Android / iOS / 未来桌面端的正主。整目录迁入 src/composeMain(非 Kotlin 源集,ohos 编译不可见),既让 ohos 构建干净,又保留对照与回迁能力——等社区 compose.ui ohos 制品与上游 API 对齐,把目录改回 commonMain 即可接回,光栅化降级为 fallback。砍掉它们等于放弃 fork 的上游同步能力。

决策 2:光栅化放 Kotlin/Native,而不是返回坐标让 ArkTS 画 Canvas

社区里 Vico 走的是"数据过桥、ArkTS Canvas 绘制"。本仓选择 KN 光栅化(qrose 同构):绘制语义(标签位置、平滑算法、渐变规则)全部在 Kotlin 侧与 JVM 对照,ArkTS 只做 Image(pixelMap),验收面最小。代价是快照无交互;收益是绘制语义零复制、单端可对账。需要交互时再补 ArkTS Canvas 路径,桥协议不用变(加 kind=line_coords 返回点集即可)。

决策 3:单 C ABI + 扁平 JSON,大数据不过桥

ChartRender 一个符号覆盖折线与柱状,图表差异全在 JSON 字段。百万点数据在 KN 侧生成,ArkTS 只传 gen/count/seed——把 200 万点塞进 JSON 既慢又测不出 LTTB 真实收益。seed 保证可复现,JVM 与真机可逐位对账。

决策 4:严格沿用已审工具链旧线(2.2.21-1.0.0)

库零第三方依赖,没有制品线推着升级(对比 KStore 为 coroutines 元数据升 2.4.20 线)。没有理由时不动钉法:HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1 + JDK 21 与 qrose / haze 完全一致,审核与排错经验直接复用。

决策 5:保留 jvm() 做交叉验证

上游 commonTest 是空的(没有现成测试套件),交叉验证改用对账法:JVM 与 ohos 跑同一份 MultiplatformChart.downsample,同 seed 生成同序列,比较 outCount / minY / maxY。真机 1000 点正弦实测 outCount=252(1000 > 250 → 默认 250 桶 → 首尾 + 250 结果点),与算法推导逐位一致。

六、测试与验证

6.1 测试环境

本次验证使用 Windows 11 x64、JDK 21(Eclipse Temurin jdk-21.0.12.101-hotspot)、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

.\gradlew.bat :multiplatformchart:compileKotlinJvm    # BUILD SUCCESSFUL

上游 commonTest 为空,验证按对账法进行(方法详见仓库 README.OpenHarmony_CN.md 第 12 节):JVM 与 ohos 执行同一 MultiplatformChart.downsample,同 seed 序列输出应逐位一致。真机侧 1000 点正弦波(默认自动降采样)返回 inCount=1000、outCount=252、buckets=250,与 LTTB 输出规模推导(首尾点 + 每桶一个代表点 = 桶数 + 2)完全吻合;minY=22.3、maxY=80.4 与生成器公式(50 ± 20 主正弦 + 8 副正弦 + 微噪声)值域一致。

6.3 HAP 验证

:multiplatformchart:compileKotlinJvm / compileKotlinOhosArm64 / compileKotlinOhosX64: BUILD SUCCESSFUL
:example:nativeApp:linkDebugSharedOhosArm64: BUILD SUCCESSFUL(arm64-v8a/libohoschart.so ≈3.9MB)
:example:nativeApp:linkDebugSharedOhosX64: BUILD SUCCESSFUL(x86_64/libohoschart.so ≈3.6MB)
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs/<abi>/(含 libc++_shared.so ≈1.8/1.7MB)
llvm-nm -D: ChartRender 为全局 T 符号
BuildNativeWithCmake: finished(双 ABI 编出 libchart_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

CAdapter 链接期有一条已知 clang 警告(api.cpp:448: non-void function does not return a value in all control paths),属 HarmonyOS Kotlin 生成的 napi 胶水,非业务代码,.so 正常链接——与 qrose 仓记录一致。

6.4 功能用例

  1. 启动即自动生成默认折线图(正弦 1000 点、平滑 + 数据点开、填充关、线宽 3、蓝色板),无需点击;
  2. 折线图 Tab:Y 轴五档刻度(22.3 / 36.8 / 51.4 / 65.9 / 80.4)自上而下递减、辅助网格线贯穿、X 轴 0…999 五档标签居中——与上游 drawYAxisWithLabels / drawXLabel 语义一致;
  3. 切波形为"心率",曲线变为 70bpm 随机游走形态,偶发尖峰可见;
  4. 关闭"平滑曲线"后拐角变直角;打开"区域填充"后线下出现上深下浅渐变;开关"数据点"圆点随之显隐;
  5. 大数据 Tab:100 万点 + LTTB 250 秒级返回,in→out 行显示 1000000 → 252;切"LTTB 1000"变 1000000 → 1002;切"关闭降采样"耗时与 PNG 字节数显著增大——LTTB 的价值直接可见;
  6. 柱状图 Tab:默认工作日五柱(12/31/7/25/18,MO…FR 标签居中),切季度/六项预设即换数据,编辑 values 文本域自定义数值即刻生效;
  7. 五套色板切换,图表前景背景同步变化,反色板下轴标签仍清晰;
  8. 元信息行显示 kind=line size=960x520 png=…B …ms,与状态行互为印证。

6.5 验证结论

算法内核、数据模型、光栅化输出、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的图表会话:LTTB 降采样对账、绘制语义复现与柱状图模型闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/compose-multiplatform-chart.git
cd compose-multiplatform-chart
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
.\gradlew.bat :multiplatformchart:compileKotlinJvm                                # JVM 交叉验证目标
.\gradlew.bat :multiplatformchart:compileKotlinOhosArm64 :multiplatformchart:compileKotlinOhosX64   # klib
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64 # 双 ABI so
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.chart -m entry
hdc shell hilog | findstr Chart

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :multiplatformchart 真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. Gradle wrapper 升级要整体替换(gradle-wrapper.jar + properties + 脚本),只改 properties 里的版本号会因 jar 不配套校验失败;
  4. vararg Int 在函数体内已是 IntArray,对其再调 toIntArray() 编译不过——点阵字体表注册函数的实际返回类型要写对;
  5. 局部辅助函数命名不能与 MutableMap.put 撞名:buildMap 里写 fun put(...) = put(...) 会造成类型检查递归报错,改名 glyph 即可;
  6. alpha 混合公式里的除法必须是浮点:(dst - src) * a / 255(Int 除法)会截断为 0,渐变与辅助线全部消失,要写成 / 255.0;
  7. 根构建的 /build/ 是根锚定模式,盖不住嵌套目录——example/harmonyApp 的 oh_modules / .hvigor / entry/build / entry/libs 要单独追加 gitignore,否则 so 与构建产物混进提交;
  8. 上游 app / shared 引用的 libs.plugins.android.* 别名已从版本目录移除,任何把它们 include 进来的尝试都会在配置期报别名找不到——留盘不进 settings 是唯一处置;
  9. build-profile.json5 里 signingConfig 引用 default 而 signingConfigs 留空数组,由 DevEco 勾选自动签名后回填完整 material 配置——证书绝不进仓库;
  10. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;DevEco 模拟器是 x86_64,只编 arm64 会栽在这;
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  12. 打包 JDK 必须是 DevEco 自带 JBR,系统 JDK 8 会让 hvigorw assembleHap 中途报不相干的错。

8.2 已知问题

  • 鸿蒙侧没有 Compose Canvas,出图是 KN 光栅化近似;轴标签用 5x7 点阵字体(数字 + 大写字母),中文与小写不支持,需要富文本标签时在 ArkTS 侧叠加 Text;
  • 上游 LineChart 的捏合缩放 / 拖动手势(detectTransformGestures)、LiveLineChart 的实时流式重组、BarChart 的入场动画是 Compose 运行时能力,不在 PNG 快照路径上;
  • 多数据集(List<LineDataSet> 一图多线)暂未桥接:桥一次画一条线,多线需多次调用叠加或扩展 fg2/points2 字段;
  • PNG 为 zlib stored 未压缩,常规图表几十 KB,关闭降采样的百万点图会到 MB 级;
  • Android / iOS 目标与 Maven 发布剥离(源文件留在树里),本 fork 不发 Maven Central;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.chart 匹配的签名。

8.3 未来优化方向

  • 补 kind=line_coords 返回坐标点集的 ArkTS Canvas 路径,恢复捏合缩放与实时更新交互;
  • 多 LineDataSet 桥接(一图多线 + 图例),对齐上游 lineDataSets 参数;
  • x 轴时间文本格式化(xFormatter 字段),标签显示 HH:mm;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵与 uitest 自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 multiplatformchart(18 源文件,10 个 Compose)
    → 源码依赖度盘点(grep import 逐文件分类)
    → 工具链对齐 HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(已审旧线)
    → 根构建替换(Groovy → KTS,cmp_qrose 模式)
    → composeMain 隔离 ×10 / commonMain 保留 ×8 + MultiplatformChart 入口
    → C ABI(@CName × 1)+ 扁平 JSON + KN 侧大数据生成
    → 软件光栅化复现 Compose 绘制语义 + 5x7 字体 + 零依赖 PNG
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON/PNG 解析
    → ArkUI 真机三 Tab 验收(折线 / 大数据 LTTB / 柱状图)

9.2 封装层次

  • 模型层:上游原版 DataPoint、BarChartEntry;
  • 算法层:上游原版 LTTB 六件套 + 新增 MultiplatformChart.downsample 换装入;
  • 光栅化层:Rasterizer / Font5x7 / PngEncoder 把绘制语义翻译成像素与 PNG;
  • Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理三 Tab 参数状态机、JSON 拼装、PixelMap 解码与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 适配路线由库的"纯 Kotlin 含金量"决定:存储库(KStore)引擎全在纯 Kotlin,真编译整库 + 补一个 actual 即可;图表库的价值大头在 Compose 侧,必须做源码集隔离 + 绘制语义复刻。先逐文件盘点依赖度,再定"真编译 / 隔离 / 光栅化"的边界,顺序不能反;
  2. 绘制类 CMP 库的鸿蒙验收锚点是"语义对账"而不是"像不像":Y 轴标签在辅助线左上、X 轴标签越界收回、平滑半径等于线宽——每条规则都能在上游源码里指到出处,Demo 出的图才不是"另一个图表控件"而是"同一个库的鸿蒙渲染";
  3. 大数据能力要在桥的协议层就设计好:百万点不过 JSON、生成器进 KN、downsample 档位透出、in→out 进状态行——上游的核心卖点(200 万点能画)在鸿蒙 Demo 里一条链路全可验收,靠的是协议设计而不是事后补 Demo。

9.4 适配成果

当前适配已完成:

  • compose-multiplatform-chart 2.1.0 算法内核的双 ABI Kotlin/Native 编译(上游 commonMain 8 个纯 Kotlin 文件零重写,唯一新增 40 行入口);
  • Compose 组件 ×10 的 composeMain 隔离与回迁预留;
  • 1 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • 复现上游绘制语义的软件光栅化器(轴标签 / 网格线 / 拐角平滑 / 渐变填充 / 数据点 / 自适应柱宽)+ 5x7 点阵字体 + 零依赖 PNG 编码;
  • DevEco Stage ArkUI 三 Tab 验收页面(折线样式 / 100 万点 LTTB 对比 / 柱状图预设);
  • JVM 交叉验证目标 + HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证(提交 6a658df 已推送);
  • AtomGit 项目文档、OpenHarmony README(中英双份)、本地效果图与本文。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐