本文记录 Vico 图表库接入 OpenHarmony 的完整过程,覆盖工程盘点、ohosArm64 示例架构、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI Canvas 展示、HAP 签名和真机验收。

与“把图表数据重写一份 ArkTS”不同,本次适配复用 Kotlin 侧的图表模型和验收逻辑,让数据经过真实的 Kotlin/Native 产物传给 ArkUI。这样可以验证共享代码确实在 OpenHarmony 设备上运行,而不是只验证一套页面副本。

本项目的核心是 KMP/CMP 图表库,示例 UI 使用 ArkTS Stage 页面和 ArkUI Canvas;工程分层与原生桥接保持清晰,页面采用独立的图表仪表盘布局,展示折线、柱状、组合、饼图、环形和蜡烛六种场景。

项目地址: AtomGit/oh-tpc/ohos_vico

开发工具: DevEco Studio


一、背景

1.1 为什么 KMP/CMP 项目需要 OpenHarmony 目标

Vico 是一个面向 Compose Multiplatform 的多平台图表库。它的核心价值不在
某一个页面,而在于图表模型、数据范围、坐标轴、图层和交互状态可以在共享
代码中组织,然后交给不同平台的渲染层显示。

OpenHarmony 应用不能直接把 JVM 或 Android Compose 产物安装到 ARM64 设备。
如果只把页面重新写成 ArkTS,虽然可以画出几条线,却无法验证 Kotlin 共享
模型是否真的在鸿蒙设备上运行,也容易在数据范围、刷新和格式化上出现第二套
实现。适配需要解决下面几个问题:

障碍具体问题
目标缺失示例工程默认没有 ohosArm64(),无法生成 OpenHarmony KLIB 和 ARM64 动态库。
工具链不一致Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。
渲染层不同Compose 图表层不能直接作为 ArkUI Stage 页面使用,需要一个 OpenHarmony 宿主渲染层。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,必须经过 C ABI、C++ N-API 和 JSON。
消费验证不足根工程编译通过,不代表独立的 example 工程能链接动态库和打包 HAP。
交付链路复杂原生库、CMake、HAP、签名、设备安装和 ARM64 ELF 依赖都需要单独检查。

因此,本次实现把适配边界放在三个地方:Kotlin/Native 目标配置、原生桥接
层和 ArkUI 展示层。图表数据和验收逻辑仍由 Kotlin 维护,ArkTS 只负责页面
状态、Canvas 绘制和用户操作。

1.2 库提供的能力

Vico 的公共能力包括图表模型、数据范围、坐标轴、图层、标记、格式化和交互
状态。本次示例没有把这些能力重新实现成一套 ArkTS 图表库,而是选择最容易在
设备上验证、又能体现渲染差异的六类场景:

  • 折线图:七个数据点、连线、重点点和水平坐标轴。
  • 柱状图:按数值范围绘制柱子,突出最高值,并展示目标线概念。
  • 组合图:同一张图中同时绘制柱状序列和折线序列。
  • 饼图:渠道占比、切片标签和图例,数据总和必须为 100%。
  • 环形图:平台占比、中心空洞、百分比标签和图例。
  • 蜡烛图:开盘、收盘、最高、最低和涨跌颜色。

页面另外提供刷新和缩放。刷新不是在 ArkTS 中随机生成数据,而是把刷新编号
传给 Kotlin/Native,由共享模型根据编号产生新的确定性数据。缩放则修改
ArkUI 状态并重新调用 Canvas 绘制逻辑。

项目地址: https://atomgit.com/oh-tpc/ohos_vico

1.3 实现目标

维度要求
代码复用图表定义、数值范围、百分比校验和刷新逻辑由 Kotlin 共享。
平台目标为示例模块增加 ohosArm64,生成 libvico_sample.so
桥接稳定使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象直接暴露给 ArkTS。
UI 完整页面要能切换六种图表、刷新数据、调整缩放并显示能力标签。
可测试JVM 测试、Native 链接、ArkUI/HAP 构建和真机交互分别验收。
签名安全仓库只保留未签名工程,证书、profile 和密码由开发者手动配置。
仓库规范README、文章、效果图和项目地址统一使用 AtomGit。

说明: 本次交付提供源码适配和独立 OpenHarmony 示例,不新增一个替代
Vico 公共 API 的 ArkTS 图表库。这样可以保持上游 API 和平台实现边界清晰。


二、实现路线图

第 1 阶段:项目初始化     ── 盘点 Vico 模块、公共能力和示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、仓库和独立消费工程
第 3 阶段:数据与序列化   ── 建立图表模型、JSON 契约和共享验收
第 4 阶段:原生桥接       ── Kotlin/Native C ABI、C++ N-API 和内存释放
第 5 阶段:能力封装       ── ArkUI Canvas、六种图表、刷新和缩放
第 6 阶段:示例与验证     ── HAP 构建、签名、设备安装和真机验收

每个阶段都使用真实产物作为下一阶段输入:shared 先验证模型,nativeApp
再把模型链接为 ARM64 动态库,ohosApp 通过 CMake 和 N-API 加载动态库,
最后由 DevEco 负责 HAP 打包和签名。


三、逐步实现过程

第 1 阶段:项目初始化

1.1 盘点原库源码和公共 API

根工程保留 Vico 原有的多平台模块和 Compose 代码。OpenHarmony 示例放在
example/ 下,避免把 DevEco 工具链、ArkTS 文件和证书配置混入库模块:

vico/                         核心图表库模块
vico/compose/                 Compose Multiplatform 图表实现
example/shared/               OpenHarmony 示例共享模型和 JVM 测试
example/nativeApp/            ohosArm64 Kotlin/Native 动态库
example/ohosApp/              DevEco Stage 应用和 ArkUI 页面
scripts/                      构建、签名工程和 HAP 辅助脚本
docs/openharmony/             验收记录和效果图

example 是一个独立的 Gradle 工程。它不会把 DevEco 工程当成 Kotlin 子模块,
因此可以分别执行 Gradle 和 Hvigor,也可以将 ohosApp 复制到另一个目录完成
手动签名。

1.2 固定工具链和版本矩阵

本次示例使用 Kotlin Multiplatform 2.2.21-1.0.0、Gradle Wrapper、JDK 21、
OpenHarmony API 20 ARM64 Native SDK 和 arm64-v8a HAP ABI。根 Vico 模块继续
保留原有平台矩阵,OpenHarmony 的 Kotlin/Native 依赖由 example/settings.gradle.kts
中的社区 Maven 仓库解析。

example/ohosApp
      ↓ CMake + N-API
example/nativeApp/libvico_sample.so
      ↓ project dependency
example/shared
      ↓ shared Kotlin chart model
Vico chart concepts

这里的 shared 不是 ArkUI 页面数据的临时缓存,而是明确的跨平台模型层。
它定义 ChartKindChartPointChartDefinition,并负责生成六种场景。
nativeApp 只负责把模型转换成 C ABI 可返回的 JSON,ohosApp 只负责读取
JSON 并绘图。

1.3 创建适配示例目录

参考 diff 工程的主要交互是编辑两段文本并比较差异。Vico 的核心能力是图表
图层和数据绘制,如果沿用文本输入框布局,会掩盖图表能力,也不能体现线、柱、
饼、环形和蜡烛等不同模型。因此页面选择了一个图表仪表盘:上方切换图表,
中间展示当前图表,底部展示缩放和能力标签。

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

2.1 加入 ohosArm64 目标

共享模块保留 JVM 测试,同时增加 ohosArm64()

plugins {
  kotlin("multiplatform")
}

kotlin {
  jvm()
  jvmToolchain(21)
  ohosArm64()

  sourceSets {
    commonTest.dependencies {
      implementation(kotlin("test"))
    }
  }
}

JVM 目标让图表定义可以在不连接设备的情况下运行测试,ohosArm64 则让同一
commonMain 代码进入 Kotlin/Native 动态库。

2.2 focused build 的作用

Native 模块构建一个 shared library,并用 linker map 限制导出的符号:

kotlin {
  jvmToolchain(21)
  ohosArm64 {
    binaries.sharedLib {
      baseName = "vico_sample"
      linkerOpts(
        "--entry=0",
        "--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
      )
      linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
    }
  }
}

OpenHarmony 动态库不是直接由 ArkUI 加载的 JavaScript 包,而是通过 CMake
链接到 libentry.so。因此构建时需要同时处理 Kotlin/Native 链接器参数和
OpenHarmony NDK 库。

2.3 配置插件仓库和依赖仓库

Native 编译产物位于 Gradle 的 build/bin/ohosArm64/debugShared。为了让
DevEco 工程能按固定路径找到文件,prepareOhos 会复制:

libvico_sample.so      → entry/libs/arm64-v8a/
libvico_sample_api.h   → entry/src/main/cpp/include/

动态库和生成头文件属于构建产物,.gitignore 会排除它们。仓库保留复制任务,
这样每台开发机都可以从源码重新生成与本机工具链匹配的文件。

2.4 通过发布坐标消费库

example/settings.gradle.kts 同时配置 mavenLocal()、OpenHarmony 社区
Maven、Maven Central 和 Gradle Plugin Portal:

pluginManagement {
  repositories {
    maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
    mavenCentral()
    gradlePluginPortal()
  }
}

dependencyResolutionManagement {
  repositories {
    mavenLocal()
    maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
    mavenCentral()
  }
}

OpenHarmony 示例的 Gradle 脚本使用 JDK 21。执行脚本前先确认:

export JAVA_HOME="/path/to/jdk-21"
java -version

第 3 阶段:数据与序列化

3.1 为什么需要统一 JSON 契约

ArkTS、C++ 和 Kotlin/Native 之间不能直接共享 Kotlin 对象。为了让边界稳定,
示例使用一个 JSON 响应描述图表模型:

VicoChartEngine
        ↓ ChartDefinition
Kotlin/Native JSON encoder
        ↓ UTF-8 buffer
C++ N-API string
        ↓
ArkTS JSON.parse
        ↓
Canvas renderer

这样图表定义始终在 Kotlin common 逻辑中生成,ArkTS 不需要了解 Kotlin data
class 的内部结构,也不会重新计算范围和百分比。JSON 字段可以随着图表能力
扩展,而 C ABI 入口仍然保持稳定。

3.2 ChartDefinition 设计

页面需要的不只是点列表,还需要标题、范围、摘要和能力标签:

public data class ChartDefinition(
  val id: String,
  val title: String,
  val subtitle: String,
  val kind: ChartKind,
  val unit: String,
  val points: List<ChartPoint>,
  val minValue: Double,
  val maxValue: Double,
  val highlightedIndex: Int,
  val summary: String,
  val features: List<String>,
)

minValuemaxValue 在 Kotlin 中统一计算。ArkTS 只把值映射到 Canvas
绘图区;饼图和环形图固定使用 0 到 100 的百分比范围,避免不同刷新数据出现
两套坐标规则。

3.3 ChartPoint 和六种场景模型

每个点至少有标签和值,组合图和蜡烛图还可以通过 secondary 携带第二个数值:

public data class ChartPoint(
  val label: String,
  val value: Double,
  val secondary: Double? = null,
)

VicoChartEngine.chart(index, refresh) 通过索引选择场景:

public fun chart(index: Int, refresh: Int = 0): ChartDefinition = when (index.mod(6)) {
  0 -> lineChart(refresh)
  1 -> columnChart(refresh)
  2 -> comboChart(refresh)
  3 -> pieChart(refresh)
  4 -> donutChart(refresh)
  else -> candlestickChart(refresh)
}

折线、柱状、饼图和环形图只需要 value,组合图把转化率放在 secondary
蜡烛图把开盘价放在 secondary、收盘价放在 value。刷新编号参与固定公式,
不使用随机数和网络,因此测试可以准确比较刷新前后的模型。

3.4 错误响应和长度限制

Native 层返回的对象包含以下字段:

{
  "id": "weekly-active-users",
  "title": "Weekly active users",
  "subtitle": "Line layer with points and marker focus",
  "kind": "LINE",
  "unit": "k users",
  "minValue": 35.7,
  "maxValue": 96.6,
  "highlightedIndex": 6,
  "summary": "84k today · +100% this week",
  "features": ["line", "points", "marker", "horizontal axis"],
  "points": [
    {"label": "Mon", "value": 42},
    {"label": "Sun", "value": 84}
  ]
}

secondary 是可选字段。Kotlin/Native 计算异常会转换成带 error 的 JSON,
C++ 层创建 ArkTS 字符串后立即调用 VicoSampleFree,避免 Native 堆内存一直
由 JavaScript 持有。索引和刷新参数在 N-API 层检查数字类型,避免无效输入
直接进入图表模型。

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

4.1 问题:Kotlin/Native 对象不能直接交给 ArkTS

Kotlin/Native 的 ChartDefinitionChartPointList 和异常对象属于
Kotlin 运行时对象。ArkTS 通过 N-API 接收到的是 JavaScript 值,C++ 不能把
Kotlin 对象地址直接当成 JavaScript 对象使用。

最终采用三层桥接:

ArkTS
  │ JSON string
  ▼
C++ N-API entry
  │ const char*
  ▼
Kotlin/Native C ABI
  │ shared chart model
  ▼
UTF-8 JSON + explicit free
4.2 方案对比
方案优点缺点选用
直接导出 Kotlin 对象代码少ABI、生命周期和类型不可控
导出基础字段数组不需要 JSON字段扩展和错误处理困难
C ABI + JSON边界清晰、易扩展、易调试有一次序列化开销
在 ArkTS 重写图表模型页面调用简单逻辑重复,结果可能与 Kotlin 不一致
4.3 Kotlin/Native 导出函数

NativeBridge.kt 导出四个函数:

@CName("VicoSampleCatalog")
public fun catalogNative(): CPointer<ByteVar> = ...

@CName("VicoSampleChart")
public fun chartNative(index: Int, refresh: Int): CPointer<ByteVar> = ...

@CName("VicoSampleRunChecks")
public fun checksNative(): CPointer<ByteVar> = ...

@CName("VicoSampleFree")
public fun freeNative(pointer: CPointer<ByteVar>?) { ... }

返回值使用 nativeHeap.allocArray<ByteVar> 分配,并以 0 结尾,满足 C 字符串
约定。所有返回字符串都必须由 VicoSampleFree 释放。

4.4 C++ N-API 方法分发

C++ 模块注册三个 ArkTS 方法:

napi_property_descriptor methods[] = {
    {"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"getChart", nullptr, Chart, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
        napi_default, nullptr},
};

getChart 检查参数数量和数字类型;三个方法都遵循“调用 Native → 生成 ArkTS
字符串 → 释放 Native 缓冲区”的顺序。CMake 将 Kotlin/Native 动态库作为
imported library:

add_library(vico_sample SHARED IMPORTED)
set_target_properties(vico_sample PROPERTIES
  IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libvico_sample.so")

add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE vico_sample libace_napi.z.so)
4.5 N-API 生命周期
ArkTS getChart(index, refresh)
        │
        ▼
ReadNumber + argument check
        │
        ▼
VicoSampleChart(index, refresh)
        │
        ▼
napi_create_string_utf8(...)
        │
        ▼
VicoSampleFree(nativeBuffer)
        │
        ▼
return JS string

C++ 负责把 Native 缓冲区转换成 ArkTS 字符串,并在返回前释放缓冲区。页面调用者
不需要知道 Kotlin/Native 的堆实现,也不会因为刷新次数增加而积累 Native 内存。

第 5 阶段:能力封装

5.1 六种图表能力封装

共享层先把 Vico 图表概念封装成六种可验证模型:

模型页面标题表现的能力
LINEWeekly active users点、连线、重点标记、水平坐标轴
COLUMNOrders by day柱子、最高值、目标线、数值标签
COMBORevenue and conversion柱状序列、折线序列、双序列、图例
PIEAcquisition mix扇区、切片标签、图例、100% 总和
DONUTPlatform share环形扇区、中心空洞、百分比、图例
CANDLESTICKMarket movement开收盘、最高最低、涨跌颜色

模型还提供摘要文本和 features,让页面可以在不理解 Kotlin 业务代码的情况下
展示当前场景的能力说明。

5.2 ArkTS 页面分区

Index.ets 只有一个 Stage 页面,按垂直方向分成:

标题区          Vico for OpenHarmony + 7/7 checks
选择区          Line / Columns / Combo / Pie / Donut / Candles
图表卡片        标题、说明、摘要、刷新按钮和 Canvas
缩放区          Zoom 数值、减号和加号
能力区          当前图表的 features 标签

这个布局避免了参考 diff 工程中的双文本编辑器交互,同时让每一种图层都能在
同一个页面里快速切换。

5.3 ArkTS 状态和错误状态

页面状态只有当前图表、索引、刷新编号、自检数量和缩放值:

@State private chart: ChartModel = EMPTY_CHART;
@State private chartIndex: number = 0;
@State private refresh: number = 0;
@State private checksPassed: number = 0;
@State private checksTotal: number = 0;
@State private zoom: number = 1;

loadChart 捕获 JSON 解析和 Native 调用异常并显示错误标题,不会保留上一张
图表的旧数据。刷新时只递增 refresh,缩放时限制在 0.82.0,并重新
调用 drawChart

5.4 页面交互预设

页面提供六个图表按钮、图表卡片刷新按钮和缩放加减按钮:

  • 切换按钮更新 chartIndex,并重新请求对应 ChartDefinition
  • 刷新按钮递增 revision,验证 Kotlin/Native 返回了新数据;
  • 加减按钮改变横向坐标比例,验证 Canvas 重绘;
  • 自检状态显示 runChecks() 的通过数;
  • 能力标签从当前模型的 features 直接生成。

第 6 阶段:示例与验证

6.1 example 工程结构
example/
├── build.gradle.kts
├── settings.gradle.kts
├── shared/
│   ├── build.gradle.kts
│   └── src/commonMain + src/commonTest/
├── nativeApp/
│   ├── build.gradle.kts
│   └── src/ohosArm64Main/
│       ├── kotlin/.../NativeBridge.kt
│       └── linker/shared-library.map
└── ohosApp/
    └── entry/
        ├── src/main/ets/pages/Index.ets
        └── src/main/cpp/napi_init.cpp

三个模块分别承担共享模型、Kotlin/Native 动态库和 DevEco Stage 页面职责。

6.2 原生模块注册

entry/src/main/cpp/napi_init.cpp 注册名为 entry 的 N-API 模块:

static napi_module vicoModule = {
    1, 0, nullptr, Init, "entry", nullptr, {0}
};

extern "C" __attribute__((constructor))
void RegisterVicoModule() {
    napi_module_register(&vicoModule);
}

注册的方法与 ArkTS 类型声明一致:

export const getCatalog: () => string;
export const getChart: (index: number, refresh: number) => string;
export const runChecks: () => string;
6.3 Native 动态库准备

先在仓库根目录执行:

export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

或者只执行示例任务:

(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)

成功后应存在:

example/ohosApp/entry/libs/arm64-v8a/libvico_sample.so
example/ohosApp/entry/src/main/cpp/include/libvico_sample_api.h
6.4 构建与安装

准备签名工程:

python3 scripts/prepare-signing-project.py "$HOME/vico_ohos_signing"

在 DevEco Studio 中手动配置 API 20 ARM64 签名后构建 HAP:

./scripts/build-hap.sh "$HOME/vico_ohos_signing"

安装并启动:

hdc list targets
hdc -t <target-id> install -r   entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <target-id> shell aa start   -a EntryAbility   -b com.patrykandpatrick.vico.ohos.sample

最后按六种图表、刷新、缩放和 7/7 checks 验收页面。

四、完整代码对照

4.1 整体架构

VicoChartEngine
    ├─ ChartKind / ChartPoint / ChartDefinition
    ├─ catalog()
    ├─ chart(index, refresh)
    └─ runVicoChecks()
             ↓ JSON
NativeBridge.kt
    ├─ VicoSampleCatalog
    ├─ VicoSampleChart
    ├─ VicoSampleRunChecks
    └─ VicoSampleFree
             ↓ C ABI
napi_init.cpp
    ├─ getCatalog()
    ├─ getChart(index, refresh)
    └─ runChecks()
             ↓ ArkTS
Index.ets
    ├─ JSON.parse
    ├─ Canvas drawChart
    ├─ refreshChart
    └─ adjustZoom

4.2 文件清单

文件作用
example/shared/src/commonMain/.../VicoChartEngine.kt六种图表模型、范围计算、刷新和自检。
example/shared/src/commonTest/.../VicoChartEngineTest.ktJVM 侧图表层、百分比和组合序列测试。
example/nativeApp/.../NativeBridge.ktKotlin/Native C ABI、JSON 和内存释放。
example/nativeApp/.../shared-library.map限制导出的 Native 符号。
example/ohosApp/entry/src/main/cpp/napi_init.cppC++ N-API 方法和模块注册。
example/ohosApp/entry/src/main/cpp/CMakeLists.txtimported library 和 N-API 链接。
example/ohosApp/entry/src/main/ets/pages/Index.etsArkUI 状态、Canvas 和交互。
scripts/build-openharmony.sh根测试、示例测试和 Native 准备。
scripts/prepare-signing-project.py复制无签名材料的 DevEco 工程。
scripts/build-hap.sh调用 Hvigor 构建 HAP。
scripts/check-native-deps.py检查 ARM64 ELF 强依赖。
docs/openharmony/images/vico-openharmony-line.jpg真机 Line 图表效果图。

4.3 关键 API 对照

VicoSampleCatalog    → 所有六种图表定义
VicoSampleChart      → 一个图表定义
VicoSampleRunChecks  → 七项共享验收检查
VicoSampleFree       → 释放 Native 字符串

ArkTS 侧只依赖三个方法,避免把 Native 指针、Kotlin 对象和内存管理细节带入
页面逻辑。

4.4 ArkTS 与 TypeScript / Kotlin 的语法差异(本次实际踩到的)

能力Kotlin/NativeArkUI
图表类型生成 ChartKind根据 kind 选择绘制分支
数据点生成 ChartPoint映射到 Canvas 坐标
数值范围生成 minValue / maxValue使用统一坐标换算
摘要文本生成 summary显示在图表卡片
能力说明生成 features渲染标签
刷新根据 revision 生成新数据递增 refresh 状态
缩放不持有 UI 缩放状态修改 zoom 并重绘

五、关键决策说明

决策 1:把 ohosArm64 加入示例的公共构建约定

示例模块加入 ohosArm64(),让同一份共享模型进入 Kotlin/Native 动态库。Vico
的 Compose 渲染实现和 ArkUI Stage 的 Canvas 生命周期不同,因此让 Kotlin 负责
跨平台模型,让 ArkUI 负责 OpenHarmony 原生绘制,在不改变 Vico 公共 API 的情况
下验证真实的 KMP/CMP 数据链路。

决策 2:独立消费者必须通过 Maven 坐标

example/sharedexample/nativeAppexample/ohosApp 按独立工程组织,
先验证共享模型,再准备动态库,最后由 DevEco 打包。这样可以同时覆盖 Gradle
变体、KLIB、CMake、N-API 和 HAP 链路,避免根工程通过但下游无法消费。

决策 3:JSON 作为跨语言数据契约

如果在 ArkTS 中生成数据,Native 层只剩一个空壳,无法证明 Kotlin/Native
代码被设备真正调用。现在由 VicoChartEngine 生成标题、点、范围、摘要和
能力,ArkTS 只做 JSON 解析和绘制。

决策 4:桥接层只开放四个 C ABI 入口

图表定义字段会随场景增加而变化,JSON 可以向后兼容可选字段。C ABI 只保留
索引、刷新编号和字符串返回,不需要为每一个图表属性扩展 N-API 方法签名。

决策 5:页面按照库能力重新设计

参考 diff 工程的文本编辑交互不适合图表库,仪表盘把六种图表放在同一个选择区,
用户可以在一次运行中对比不同图层,并在每种图表下看到对应能力标签。刷新与缩放
状态留在 ArkTS 页面层,不改变共享模型的原始数据。

决策 6:把库验证和设备验证分开

随机数会让 JVM 测试、截图和设备复现变得困难,因此刷新编号参与固定计算公式,
每次刷新能产生变化,但同样的输入仍然得到同样的输出。JVM 测试验证模型和数值,
Kotlin/Native 编译验证目标,Hvigor 验证 HAP,真机验证 N-API、Canvas 和触摸操作。
证书、profile 和密码属于开发机材料,不应放入 AtomGit;开发者在 DevEco Studio
中手动签名,签名工程与源码工程分离。


六、测试与验证

6.1 测试环境

本次真机验收使用:

项目
设备HUAWEI Mate 60 Pro
设备系统HarmonyOS 6.1.1 (24)
ABIARM64
DevEcoDevEco Studio 6 系列
HAP 包名com.patrykandpatrick.vico.ohos.sample
入口EntryAbility
示例版本3.3.1-ohos.1

6.2 静态检查与单元测试

共享测试覆盖:

(cd example && ./gradlew :shared:jvmTest)

Native 动态库准备任务为:

(cd example && ./gradlew :nativeApp:prepareOhos)

检查点包括六种图表数量、组合图第二序列、饼图/环形图合计 100、蜡烛图开收盘
值和刷新前后数据变化。

6.3 原生桥接和 HAP 验证

DevEco 构建阶段需要确认:

  1. CMake 能找到 libvico_sample.so
  2. Ninja 能生成 libentry.so
  3. ArkTS 编译可以解析 libentry.so 的类型声明;
  4. HAP 包含 libs/arm64-v8a 下的两个动态库;
  5. 签名由当前开发机的 DevEco 配置完成。

可选的 Native 依赖检查:

mkdir -p /tmp/vico-hap
unzip -o entry-default-signed.hap 'libs/*' -d /tmp/vico-hap
python3 scripts/check-native-deps.py \
  "$DEVECO_SDK_HOME/default/openharmony/native" \
  /tmp/vico-hap/libs/arm64-v8a

6.4 功能验证用例

用例 1:默认页面和自检

启动应用后,页面显示 Vico for OpenHarmony7/7 checks、六个图表按钮和
默认 Line 卡片。标题为 Weekly active users,页面底部显示 Zoom 1.0×

用例 2:六种图表切换

按顺序点击 Line、Columns、Combo、Pie、Donut、Candles:

按钮页面标题主要验证点
LineWeekly active users点、连线、重点标记和坐标轴
ColumnsOrders by day柱子、最高值和目标线说明
ComboRevenue and conversion柱状序列和折线第二序列
PieAcquisition mix切片、图例和 100% 占比
DonutPlatform share中心空洞、图例和百分比
CandlesMarket movement开收盘实体、最高最低影线

每次切换都会更新卡片标题、摘要、Canvas 和能力标签。

用例 3:刷新数据

点击图表卡片右侧的刷新按钮,ArkTS 将 refresh 加一并重新调用
getChart(index, refresh)。验收时可观察摘要或点位变化,同时选中的图表类型
不改变。

用例 4:缩放和横向重绘

点击加号和减号,zoom0.1 为步长变化,边界为 0.8×2.0×。页面
文字和 Canvas 都会更新。缩放状态保留在 ArkTS 页面,不会改变 Kotlin 模型的
原始数据。

用例 5:Native 异常边界

Native getChart 对缺少索引或非数字参数抛出 N-API 类型错误,Kotlin 图表
计算异常转换为 JSON 错误字段。页面不会因为一次错误调用而持有未释放的 Native
缓冲区。

用例 6:库自检和错误处理

启动时执行 runChecks(),首页显示 7/7 checks。对缺少索引、非数字参数和
Native 计算异常进行验证,确认 N-API 返回类型错误或 JSON 错误对象,并且每个
Native 返回缓冲区都在创建 ArkTS 字符串后释放。

6.5 验证结论

本次验收结果:

  • shared JVM 验收通过;
  • nativeApp ARM64 动态库生成成功;
  • ArkUI/Hvigor HAP 构建成功;
  • 签名 HAP 安装成功并启动 EntryAbility
  • 六种图表切换成功;
  • 刷新能够改变 Native 数据;
  • 缩放值可以从 1.0× 调整到其他值并恢复;
  • 首页自检显示 7/7 checks

七、运行效果

7.1 获取运行截图

下面是 HUAWEI Mate 60 Pro 上运行的 Line 场景截图。页面包含标题、自检状态、
六种图表按钮、折线图、刷新按钮、缩放区和能力标签。

在这里插入图片描述

7.2 界面文本快照

截图区域对应实现
Vico for OpenHarmonyArkUI 页面标题和平台说明。
7/7 checksrunChecks() 返回的共享模型自检数量。
Line / Columns / Combo第一行图表选择按钮。
Pie / Donut / Candles第二行图表选择按钮。
Weekly active usersChartDefinition.title
Line layer with points and marker focusChartDefinition.subtitle
黄色折线和白色圆点Canvas 的 line、point 和 highlightedIndex 绘制。
refreshChart(),触发 Native 新数据。
Zoom 0.8× / 1.0×ArkTS zoom 状态和横向坐标缩放。
line / points / marker / horizontal axis当前图表的 features 标签。
其他五种场景
  • Columns:蓝色柱子表示每天订单,最高柱子对应模型中的重点值。
  • Combo:蓝色柱子和黄色折线同时出现,分别表达两个序列。
  • Pie:彩色扇区和渠道标签表达 100% 获客占比。
  • Donut:彩色环形扇区和中心空洞表达平台份额。
  • Candles:绿色/粉色实体和影线表达上涨、下跌、最高和最低。

7.3 验证命令速查

# 根工程测试和 OpenHarmony 原生库准备
./scripts/build-openharmony.sh

# 单独运行共享测试和 Native 复制任务
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)

# 准备不带个人证书的 DevEco 工程
python3 scripts/prepare-signing-project.py "$HOME/vico_ohos_signing"

# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh "$HOME/vico_ohos_signing"

# 安装和启动
hdc -t <target-id> install -r entry-default-signed.hap
hdc -t <target-id> shell aa start -a EntryAbility -b com.patrykandpatrick.vico.ohos.sample

八、遗留问题与改进方向

8.1 踩坑复盘

  1. Native 文件必须先复制:如果没有执行 prepareOhos,CMake 的 imported
    library 路径存在但文件不存在,Ninja 会直接失败。
  2. JDK 版本必须匹配:OpenHarmony 示例脚本使用 JDK 21,不能用不兼容的
    JDK 版本绕过 Kotlin DSL 或 Native 编译器检查。
  3. 签名配置不能进源码:本机 DevEco 生成的 profile 可能带有绝对路径和
    密码,必须使用独立签名工程。
  4. 多设备 hdc 需要指定目标:同时连接真机和其他设备时,安装命令要加
    -t,否则 hdc 会提示需要确认设备。
  5. Canvas 和模型范围要一致:饼图按百分比绘制,笛卡尔图按模型范围绘制,
    不能在 ArkTS 中用另一套默认范围。

8.2 已知问题

  • 当前示例使用 Canvas 绘制基础图层,尚未实现完整 Compose Vico 的手势、动画、
    滚动和复杂标记系统。
  • 饼图和环形图展示图例和比例,切片点击命中测试仍是后续扩展点。
  • Native 模型以 JSON 返回,数据量很小时足够直观;大数据集需要进一步评估
    编码开销和增量更新策略。
  • HAP 需要开发者在 DevEco Studio 中完成签名,仓库不会提供可直接发布的证书。

8.3 未来优化方向

  1. 将 Canvas 绘图拆成可复用的 Line、Column、Pie 和 Candle 组件。
  2. 为图表增加触摸命中、长按标记和十字线状态,并通过 ArkUI 手势传回模型。
  3. 在保持 JSON 契约稳定的前提下增加窗口化数据和增量刷新。
  4. 为 OpenHarmony 目标建立独立 CI,自动执行 JVM、Native、HAP 和依赖审计。
  5. 如果后续发布 OpenHarmony 变体,再为消费者提供明确的 Maven 坐标和版本策略。

九、总结

9.1 核心难点回顾

Vico OpenHarmony 适配的难点不是把按钮画出来,而是让同一份 Kotlin 图表
模型真正经过 Kotlin/Native、C ABI 和 N-API 到达设备上的 ArkUI Canvas:

共享模型 → Kotlin/Native → C ABI → C++ N-API → ArkTS JSON → Canvas

每一层都有清晰的输入和输出,出现问题时可以分别检查模型、动态库、符号、
HAP 或页面状态。

9.2 封装层次

Vico root modules
    └── example
        ├── shared       图表模型、范围、刷新和 JVM 检查
        ├── nativeApp    ohosArm64 动态库、C ABI、内存释放
        └── ohosApp      Stage、N-API、Canvas、按钮和 HAP

9.3 三条经验

  1. 先固定数据契约,再写 UI。 ChartDefinition 让六种图表共享一套桥接
    方式,ArkTS 不需要猜测 Kotlin 返回值。
  2. 把工具链问题和业务问题分开。 JVM、Native、CMake、Hvigor 和真机分层
    验证,能迅速定位是依赖、符号、签名还是绘图问题。
  3. 签名工程必须隔离。 开源仓库提交源码和脚本,开发者在 DevEco 中手动
    完成签名,既满足真机运行,也避免泄露本机密钥。

9.4 适配成果

  • OpenHarmony 示例具备 ohosArm64 Kotlin/Native 构建链路;
  • Kotlin 共享层提供六种图表模型和七项自检;
  • Native 层通过四个 C ABI 函数向 ArkTS 提供 JSON;
  • ArkUI 页面拥有独立的图表仪表盘 UI;
  • 真机可以切换六种图表、刷新数据和调整缩放;
  • 中文、英文 README、本文、效果图和仓库地址统一使用 AtomGit;
  • 签名材料、HAP 和原生构建产物不进入源码仓库。

参考文档

Logo

一站式 AI 云服务平台

更多推荐