本文记录 compose-pipette 接入 OpenHarmony 的完整过程,覆盖取色器库盘点、Compose 鸿蒙制品线选型、整库真实编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

compose-pipette 是一个极简、零额外依赖的 Compose Multiplatform 取色器库:CircularColorPicker(圆盘,角度=色相、半径=饱和度)、SquareColorPicker(方形,横向饱和度、纵向明度)、RingColorPicker(圆环,只控色相)三个组件加一个 HsvColor 值类(HSV 分量位打包进 ULong,可与 Compose Color 互转)。本次适配与社区早期路线(qrose / Swipeable / compose-table 的"零依赖内核提取")有本质区别:社区 Maven 上已经出现完整的 Compose 鸿蒙制品线(org.jetbrains.compose 的 runtime / ui / foundation 1.12.0-0.1.0-07,全部带 ohosArm64 / ohosX64 klib),配合鸿蒙 Kotlin 2.4.20-0.1.0-07 自带的 kotlin.plugin.compose 编译器插件,不需要提取内核——上游 commonMain 的六个文件(含全部 @Composable 组件)被原样编译成 OpenHarmony KLIB,一个带手势、渐变、thumb 动画的完整取色器组件库编译进真机 .so,ArkUI 页面作为验收宿主,所有颜色数值由真库实时计算。

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

上游仓库: zt64/compose-pipette

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

compose-pipette 为 Kotlin Multiplatform 应用提供"颜色选取"这一件事的完整答案:CircularColorPicker 一个圆盘同时控制色相与饱和度,SquareColorPicker 用三层 drawRect(白底、色相横向渐变、黑色纵向渐变)构成 SV 面,RingColorPicker 用 sweepGradient 环带只控色相,每个组件都有 thumb 参数可插自定义滑块。HsvColor 把 hue(16 位)、saturation(20 位)、value(20 位)位打包进一个 ULong,提供 Saver、解构、HSV↔RGB 转换。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套取色能力,但上游构建(Kotlin 2.4.21 / Compose 1.11.1 / AGP 9.4.1 / Gradle 9.8.1)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示色盘,却无法证明取色器组件库在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证 Compose 组件源码、颜色数值引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.4.21 / Gradle 9.8.1 / AGP 9.4.1(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建。
源码全是 Compose六个文件里五个是 @Composable 组件(awaitEachGesture、drawWithCache、Brush.sweepGradient……),没有编译器插件变换根本编不了。
依赖矩阵引擎依赖 compose runtime / ui / foundation 三件套,每一个都要有 ohos klib 变体才能解析。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
手势语义跨不了桥位置→HSV 映射、thumb 偏移是组件 private 函数,OHOS 又没有 Compose 组合宿主,@Composable 无法上屏。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:整库 Compose 源码的 ohos 编译(compose-ripple-indication 已过审模式)、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。颜色数值完全由上游原版 HsvColor 负责,位置公式按上游源码逐字复刻,ArkTS 只负责调用 Native、解析 JSON 和重绘视觉。

1.2 库提供的能力

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

  • 取色(pick):圆盘 / 方形 / 圆环三种手势面,触摸坐标经桥换算成 HSV——角度、半径、边界钳制全部按上游公式;
  • 颜色数值(pack):HsvColor(h, s, v) 真库位打包,返回 hex / RGB / ARGB / packedValue / toString / 亮度对比色;
  • 反向转换(fromArgb):HsvColor(color: Long) 真库 RGB→HSV,10 个已知色真机对账;
  • 光谱色带(palette):给定 count 由真库逐色转换,ArkTS 画出的渐变与上游 sweepGradient 逐位一致;
  • 版本握手:pipetteVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
op回显本次操作。
hue / saturation / value真库 HsvColor 分量。
hex / argb / argbHex / r / g / b真库 .toColor().toArgb() 与 .red/.green/.blue 换算。
packed / toString真库位打包值与原版字符串。
inside / x / y / clamped位置类 op 的命中判定与钳制结果。

组件侧完整能力(手势、渐变、thumb 动画、progressSemantics、实验性 TriangularColorPicker)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 18 项 jvmTest 交叉验证用例,证明 ohos .so 里执行的颜色逻辑没有漂移。

1.3 实现适配

维度要求
代码复用core 的全部六个 commonMain 文件原样编译,不提取、不重写、不新增任何库源文件(全库零 expect/actual)。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);build-logic、sample:shared / sample:android 留盘不进构建,上游构建文件保留 *.upstream 存档。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律Compose 依赖按源码集双线声明(ohos 线 1.12.0-0.1.0-07 / jvm 线上游 1.11.1),上游测试零改动全绿。
UI 完整五页签覆盖全部桥接入口:预览、圆形、方形、圆环、桥接验证(已知色对账 + 往返校验)。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。OHOS 目前没有社区 Compose 线的组合宿主(composition host),@Composable 组件无法直接上屏;ArkTS Demo 用 Canvas 重绘取色器视觉,但每一个颜色数值都出自 .so 里的真库 HsvColor。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 Compose ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、双 Compose 线
第 3 阶段:桥协议建模     ── 13 op JSON 契约、真库调用面 / 复刻公式分界、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── Canvas 光谱重绘、vp2px 手势换算、五页签和版本握手
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
core/                                上游取色器库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
core/src/commonMain/                 六个上游文件,逐字不变(零新增源码)
core/src/commonTest/                 上游三个测试文件(HsvColorTest / ColorPickerTest / StateUpdateTest)
example/nativeApp/                   Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/                  DevEco Stage、CMake N-API 和 ArkTS 五页签页面
README.OpenHarmony.md / _CN.md       鸿蒙适配说明(中英双份)
README.upstream.md                   上游原文保存
*.upstream(五件)                    被替换的上游构建文件原样存档
build-logic / sample                 上游约定插件与 material3 demo,留盘不进 settings

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :core 与 :example:nativeApp,rootProject.name = "ohos_compose_pipette"。上游的 ktlint / vanniktech 发布 / binary-compatibility-validator / AGP classpath 全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
Compose 编译器插件org.jetbrains.kotlin.plugin.compose 2.4.20-0.1.0-07(与 Kotlin 同线)
JDK21(org.gradle.java.home 指向本机 JDK 21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle9.6.1(上游 9.8.1 降至与鸿蒙 Kotlin 线配套的已验证组合)
Compose(ohos 线)runtime / ui / foundation 1.12.0-0.1.0-07(社区鸿蒙线,发布 ohos klib)
Compose(jvm 线)1.11.1(上游 2.0.0 所用版本,Maven Central,含 ui-test 与 desktop)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.pipette
$env:JAVA_HOME = "F:/cmp/.tools/jdk-21.0.12.1+1"
java -version
1.3 源码依赖度盘点
模块commonMain 文件依赖形态处置
core6compose runtime / ui / foundation,零 AGP、零 expect/actual整体编译(jvm + 双 ohos)
build-logic5 个约定插件AGP 9、ktlint、vanniktech、compatibility-validator留盘不进构建
sample:shared / sample:androiddemo 源码material3 1.10、materialKolor、AGP application留盘不进构建

上游 core 的 API 面积很小但全部站在 Compose 上:HsvColor 用 Color / ColorSpaces / Saver,三个组件用 awaitEachGesture / drawWithCache / Brush.sweepGradient / progressSemantics。库内没有一处 expect/actual——这意味着鸿蒙侧不需要补任何平台源码文件,适配面收敛到"目标 + 依赖 + 桥"三件事。

TriangularColorPicker 是上游实验性组件(手势逻辑被注释、未进 API 基线),同样逐字编译:它在上游源码树里、上游 CI 编它,挑文件就会破坏"逐字不变"的可 diff 性。

1.4 页面边界
预览页签        Pipette · OpenHarmony 标题 + 版本握手行
                颜色卡片:色块 + hex + HSV + RGB + ARGB + packedValue + toString + isDark + 对比色 + ComponentSize
圆形页签        280vp Canvas(60 楔形光谱 + 径向白渐变)+ 明度滑杆
方形页签        280vp Canvas(白底 + 色相横向渐变 + 黑色纵向渐变)+ 色相滑杆
圆环页签        280vp Canvas(120 弧段光谱环 + 环心当前色圆)+ 饱和 / 明度滑杆
桥接页签        版本握手、10 已知色 fromArgb 对账表、pack→fromArgb 往返校验、palette 色带、操作日志
TabBar          预览 / 圆形 / 方形 / 圆环 / 桥接

页面启动时先调 pipetteVersion() 做握手,再取 componentSize(真库常量 128dp)、拉两份光谱色带、刷新颜色卡片。

第 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: ohosArm64 / ohosX64 klib(ui-ohosarm64 / ui-ohosx64 子坐标可直接命中)
org.jetbrains.compose.ui:ui:1.12.0-0.1.0-07
  variants: 同上(鸿蒙线发布完整 ui klib)
org.jetbrains.compose.foundation:foundation:1.12.0-0.1.0-07
  variants: 同上

两个关键结论:其一,鸿蒙线发布的坐标主体就是 org.jetbrains.compose.*,ohosMain 源码集声明 api(libs.compose.runtime/ui/foundation) 即可解析到 ohos klib;其二,鸿蒙线不发布 jvm 需要的 *-desktop 子制品,jvm() 交叉验证目标必须换到上游线(Maven Central 1.11.1)——双线并存,按源码集隔离。

2.2 工具链钉版(为什么是 2.4.20-0.1.0-07)

社区此前批准的钉法有两代:HarmonyOS Kotlin 2.2.21-1.0.0(qrose / Swipeable / compose-table 用的,纯 Kotlin 引擎路线)与 2.4.20-0.1.0-07(compose-ripple-indication / KStore 用的)。取色器六个文件五个是 @Composable,必须有 kotlin.plugin.compose 编译器插件,而该插件与 Compose 鸿蒙线 1.12.0-0.1.0-07 都随 2.4.20-0.1.0-07 线发布——2.2.21 线没有能编上游组件源的组合,钉版唯一解。Gradle 侧 2.4.20 线支持 9.x,但上游 wrapper 9.8.1 与该 Kotlin 线的配对未经验证,降至 ripple 已验证的 9.6.1(上游 wrapper 原样存档于 *.upstream)。

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()
    }
}

rootProject.name = "ohos_compose_pipette"

include(":core")
include(":example:nativeApp")
2.3 编译真实组件库

core/build.gradle.kts 收敛为纯 KMP 模块 + Compose 编译器插件:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.kotlin.compose.compiler)   // 上游源码全是 @Composable,没有插件变换编不了
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvm()          // 交叉验证用
    ohosArm64()    // the adaptation
    ohosX64()

    sourceSets {
        ohosMain.dependencies {
            api(libs.compose.runtime)             // 鸿蒙线 1.12.0-0.1.0-07
            api(libs.compose.ui)
            api(libs.compose.foundation)
        }
        jvmMain.dependencies {
            api(libs.compose.runtime.jvm)         // 上游线 1.11.1
            api(libs.compose.ui.jvm)
            api(libs.compose.foundation.jvm)
        }
        commonTest.dependencies {
            implementation(libs.kotlin.test)
            implementation(libs.compose.ui.test)   // 上游测试依赖,仅 JVM 解析
        }
        jvmTest.dependencies {
            implementation("org.jetbrains.compose.desktop:desktop-jvm:1.11.1")
            implementation("org.jetbrains.skiko:skiko-awt-runtime-windows-x64:0.144.6")
        }
    }
}

实测:

.\gradlew.bat :core:compileKotlinJvm :core:compileKotlinOhosArm64 :core:compileKotlinOhosX64   # BUILD SUCCESSFUL

六个上游文件一个没动、一行没加。双线声明是"线切换,不是版本切换"(compose-ripple-indication / compose-shimmer / eungabi 同款):commonMain 不绑任何一条线,六个文件对两条线无感。

2.4 测试依赖的处置:jvm 线要补 skiko 原生库

上游 commonTest 的 UI 测试(runComposeUiTest)跑在 desktop 运行时上。直接给 jvmTest 声明 desktop-jvm:1.11.1 会栽一个坑:plain desktop-jvm 不带 OS 原生 skiko,运行期报 NoClassDefFoundError ... LibraryLoader。处置分两步:先用依赖树查 compose 1.11.1 配套的 skiko 版本,再补平台 runtime:

.\gradlew.bat -q :core:dependencies --configuration jvmTestRuntimeClasspath | findstr skiko
# org.jetbrains.skiko:skiko:0.144.6 → 补 skiko-awt-runtime-windows-x64:0.144.6

ohos 测试任务则统一禁用(设备测试由维护者手动执行):

subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }
}

这样上游三个测试文件零改动保留,18 项用例在 JVM 全绿。

第 3 阶段:桥协议建模

3.1 JSON 契约
dev.zt64.compose.pipette/**(上游组件 + HsvColor,原样编译)
        ↓ HsvColor 构造 / 位打包 / HSV↔RGB / toColor().toArgb()
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API pipetteVersion() / pipetteDispatch()
        ↓
ArkUI Index.ets(五页签)

请求与响应:

请求 : {"op":"pack","hue":210,"saturation":0.85,"value":0.9}
       {"op":"fromArgb","argb":4285594020}          # 或 {"op":"fromArgb","hex":"#FF6750A4"}
       {"op":"circularPosition","x":412,"y":188,"size":910}
       {"op":"palette","count":60,"saturation":1,"value":1}
响应 : {"ok":true,"op":"pack","hue":210,"saturation":0.85,"value":0.9,
        "r":49,"g":183,"b":230,"argb":4284703742,"hex":"#31B7E6",
        "packed":"...","toString":"HsvColor(hue=210.0, ...)","isDark":false,"thumbHex":"#000000"}
       {"ok":false,"op":"...","error":"size must be > 0"}
3.2 真库调用面与复刻公式的分界

桥能调的是库的 public 面:HsvColor(hue, saturation, value) 构造器(位打包进 ULong)、HsvColor(color: Long) 工厂(RGB→HSV)、.hue/.saturation/.value/.red/.green/.blue、.toColor().toArgb()、.packedValue、.toString()、ColorPickerDefaults.ComponentSize(128dp 库内常量)。

三个取色器把触摸坐标变颜色的函数(updateColorFromPosition / hsvColorForPosition / hueForPosition / clampPositionToRadius 与三个 thumb 偏移块)是组件内 private,跨模块不可见。桥内对这些公式逐字复刻(源码拷贝公式,不改算法),每个 op 在源码注释里标了出处;Color.isDark 亮度公式(internal)同样复刻,但 RGB 分量来自真库。这个分界是审核重点:颜色数值 = 真库执行;位置公式 = 逐字复刻;视觉 = ArkTS 重绘。

3.3 错误通道与数值精度

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

@CName("PipetteDispatch")
fun pipetteDispatch(request: String): String = try {
    /* 13 op 分发 */
} catch (t: Throwable) {
    """{"ok":false,"op":"dispatch","error":${jsonString(t.message ?: t.toString())}}"""
}

非法 JSON、缺 op、不支持的 op、size <= 0、ARGB 超域,全部走同一个 ok:false 通道。一个容易漏的精度点:ARGB 值域到 0xFFFFFFFF(约 4.29e9),Float 尾数只有 24 位装不下,pack → fromArgb 往返会漂——桥内 MiniJson 专门加了 Double 解析通道(dbl(),53 位尾数,32 位精确)处理 argb 字段。

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

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

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制颜色数学,所以选择 C ABI + JSON。

4.2 Kotlin/Native 导出函数
@CName("PipetteDispatch")
fun pipetteDispatch(request: String): String   // 13 op 一口进

@CName("PipetteVersion")
fun pipetteVersion(): String = "2.0.0-ohos.1"  // 版本握手
C ABI 符号ArkTS 方法覆盖内核
PipetteDispatchpipetteDispatch(req)HsvColor 构造 / 位打包 / HSV↔RGB / toColor().toArgb() / 位置公式 ×8 / palette
PipetteVersionpipetteVersion()fork 版本握手(真机版本行的数据来源)

桥模块不应用 compose 编译器插件:它只碰库的非 composable 面(数值与常量),没有需要插件变换的 lambda 要链接——toColor() 是普通 inline 函数,内联体在库侧已变换完毕。

4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"pipetteDispatch", nullptr, NapiPipetteDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"pipetteVersion", nullptr, NapiPipetteVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohospipette_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohospipette_api.h 直接从 KN 构建产物拷入提交(cpp/include/),只声明 ExportedSymbols 结构与两个业务符号。

4.4 CMake 和类型声明
add_library(ohospipette SHARED IMPORTED)
set_target_properties(ohospipette PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(pipette_napi SHARED napi_init.cpp)
target_link_libraries(pipette_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohospipette)

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

export const pipetteDispatch: (request: string) => string;
export const pipetteVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 手势换算与光谱重绘
private onCircularTouch(event: TouchEvent): void {
  const t: TouchObject = event.touches[0];
  const xPx: number = vp2px(t.x);            // TouchObject.x/y 是 vp,Canvas 绘制是 px
  const yPx: number = vp2px(t.y);
  const sizePx: number = this.cirCtx.width;
  if (event.type === TouchType.Down) {
    // 上游 Down 语义:原始坐标做圆内判定
    const res = this.dispatch({ op: 'circularPosition', x: xPx, y: yPx, size: sizePx });
    ...
  } else {
    // 上游 Move 语义:先钳制到圆边再求值
    const clamp = this.dispatch({ op: 'circularClamp', x: xPx, y: yPx, size: sizePx });
    ...
  }
}

光谱不是 ArkTS 自己算的 HSV:palette op 让真库逐色转换(圆盘 60 楔形、圆环 120 弧段),ArkTS 只按返回的 hex 数组画楔形——画出的渐变与上游 Brush.sweepGradient 在其他平台逐位一致。thumb 半径 10vp / 按压 14vp、圆环 stroke 16vp 分别对应上游 ThumbRadius / ThumbRadiusPressed / ringStrokeWidth 的 10.dp / 14.dp / 16.dp 默认值。

5.2 五页签交互

预览页签的颜色卡片(hex / HSV / RGB / ARGB / packedValue / toString / isDark / 对比色 / ComponentSize)全部来自 pack op 的真库响应,切页签拖动后数值实时同步。三个取色器页签共享一组 hue/sat/val 状态(对齐上游 sample 的单一 HsvColor state):圆形改 H/S、方形改 S/V、圆环改 H,滑杆补齐被固定的分量。桥接页签做对账:10 个已知色(红绿蓝黄青品红黑白 + 两个常用色)的 fromArgb 转换表、当前色 pack→fromArgb 往返校验(ΔH < 0.02°、ΔS/ΔV < 0.01%)、palette 色带和带时间戳的操作日志。

5.3 错误边界

dispatch() 统一 try/catch:ok:false 时错误原文进日志区,Native 调用本身抛错(理论不该发生)同样被收进日志,页面永不因桥失败闪退。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohospipette)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       └── Bridge.kt                         @CName × 2 + 13 op + 复刻公式 + MiniJson(cmp_qrose 审核模式)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.pipette、versionName 2.0.0-ohos.1
    └── entry/
        ├── build-profile.json5               externalNativeOptions + abiFilters [arm64-v8a, x86_64]
        ├── hvigorfile.ts                     hapTasks(模块级,缺失会报 00303148)
        ├── 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/libohospipette_api.h
        ├── src/main/cpp/types/libpipette_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 五页签验收页面
6.2 构建、签名和安装
.\gradlew.bat :core:jvmTest                                                    # 18 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

# DevEco Studio 打开 example/harmonyApp(不要打开仓库根目录)
# File → Project Structure → Signing Configs 勾选自动生成 → Build → Build Hap(s)

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

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.pipette -m entry

四、完整代码对照

4.1 整体架构

dev.zt64.compose.pipette/**(上游组件库原样编译)
    │ CircularColorPicker / SquareColorPicker / RingColorPicker / HsvColor
    │ compose 鸿蒙线 1.12.0-0.1.0-07(runtime / ui / foundation)
Bridge.kt + MiniJson
    │ PipetteDispatch / PipetteVersion
libpipette_napi.so
    │ pipetteDispatch / pipetteVersion
Index.ets
    │ JSON parse + @State 更新 + Canvas 重绘
    ▼
ArkUI 真机页面(五页签)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_compose_pipette、sample/build-logic 移出
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-07 + 双 Compose 线(1.12.0-0.1.0-07 / 1.11.1)钉版
build.gradle.kts(根)版本 2.0.0-ohos.1、全局禁用 Ohos*Test* 任务
core/build.gradle.kts纯 KMP + compose 编译器插件:jvm + ohosArm64 + ohosX64,按源码集分线声明依赖
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、13 op、复刻公式注释、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 五页签验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(20 节,真库/复刻对照表)
*.upstream(五件)上游构建文件原样存档,diff 可审计

4.3 关键 API 对照

层次API作用
库HsvColor(hue, saturation, value)HSV 位打包进 ULong(真库调用,pack)
库HsvColor(color: Long)RGB→HSV 工厂(真库调用,fromArgb)
库.red / .green / .blueHSV→RGB 分量(真库调用)
库.toColor().toArgb()Compose 颜色转换(真库调用,hex / argb / palette)
库.packedValue / .toString()位打包值与原版字符串
库ColorPickerDefaults.ComponentSize默认尺寸常量 128dp(componentSize op)
组件(private,复刻)updateColorFromPosition圆盘位置→hue/saturation(circularPosition)
组件(private,复刻)clampPositionToRadius圆外拖拽钳制(circularClamp)
组件(private,复刻)hsvColorForPosition方形位置→saturation/value(squarePosition)
组件(private,复刻)hueForPosition圆环位置→hue(ringPosition)
组件(private,复刻)三个 thumb offset 块 + Color.isDarkHSV→thumb 坐标、亮度对比色
NativePipetteDispatch / PipetteVersionC ABI JSON 导出
N-APIpipetteDispatch(req) / pipetteVersion()向 ArkTS 暴露能力
ArkTSdispatch(op)触发操作并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const res = JSON.parse(pipetteDispatch(JSON.stringify({
  op: 'pack', hue: 210, saturation: 0.85, value: 0.9
}))) as PipetteResponse;
// res.hex / res.r / res.g / res.b / res.packed / res.isDark —— 全部真库换算
private fun packResponse(json: MiniJson): String {
    val c = readHsvOrArgb(json)            // HsvColor(h,s,v) 或 HsvColor(argb)
    val argb = c.toColor().toArgb()        // 真库 Compose 转换
    return colorJson("pack", c)            // hue/sat/value + r/g/b + hex + packed + isDark
}

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

五、关键决策说明

决策 1:整库编译而不是提取 HsvColor 内核

HsvColor 是纯数据 + 纯数学,单抽它编 klib 最省事——但取色器库的价值在 Compose 组件本身:组件默认值(ComponentSize 128dp、thumb 10/14dp、环宽 16dp)散落在 Compose 文件里,上游测试一半在测组件行为(runComposeUiTest),抽引擎后这些测试没有宿主;将来社区 CMP 宿主到位还得把组件搬回来。整库编译的证据链是闭环的:core/src 对上游 diff 为空 → jvm 线跑上游全部测试 → ohos 线同一份源码出 klib → so 里执行的就是这份代码。

决策 2:Kotlin 2.4.20-0.1.0-07 + 双 Compose 线 + Gradle 9.6.1

钉版由源码形态反推:六个文件五个 @Composable,需要 kotlin.plugin.compose 编译器插件,而插件与 Compose 鸿蒙线 1.12.0-0.1.0-07 都随 2.4.20 线发布,唯一解。鸿蒙线不发布 *-desktop 子制品,jvm 交叉验证必须换上游线 1.11.1——两条线按源码集(ohosMain / jvmMain)隔离,commonMain 不绑任何一条,若鸿蒙线将来补齐 desktop 制品,删一行版本目录即可合线。Gradle 从上游 9.8.1 降至 9.6.1:2.4.20 线与 9.6.1 的配对在社区 ripple 仓已验证,9.8.1 配对无人背书。

决策 3:根构建替换(cmp_qrose 模式),sample / build-logic 留盘

上游根构建(ktlint / vanniktech / compatibility-validator / AGP 9 classpath / foojay resolver / build-logic includeBuild)对鸿蒙没有保留价值,整个替换为极简 ohos 构建,被替换文件保留 *.upstream 存档供 diff。sample:shared / sample:android 是 material3 demo,鸿蒙线可用性未验证,留盘不进 settings.gradle.kts——Demo 语义已由 ArkUI 五页签覆盖。

决策 4:桥只碰非 composable 面 + 复刻公式纪律

桥不应用 compose 编译器插件,只调 HsvColor 数值与 ColorPickerDefaults 常量——没有需要插件变换的 lambda 要链接。上游 private 的八个位置公式逐字复刻并在源码注释与 README 对照表里标注出处,这是本适配唯一需要人工跟随上游的漂移点;颜色数值本身永远出自真库。

决策 5:jvmTest 18 项交叉验证 + 测试零改动

组件逻辑在 commonMain,JVM 与 ohos 编同一份代码;18 项 jvmTest(含 runComposeUiTest 的组件行为测试)跑的就是上游原测试文件,是"源码没漂"的闸门。ohos 测试任务的处置不是改测试,而是"测试统一 JVM + 根构建禁用 Ohos*Test*"——测试文件一个字节不动,交叉验证的有效性才成立。

六、测试与验证

6.1 测试环境

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

6.2 JVM 交叉验证

.\gradlew.bat :core:compileKotlinJvm :core:jvmTest

18 项用例全部通过(上游原测试文件,零改动):

测试类用例数钉住什么
HsvColorTest5位打包往返、四种构造等价、copy、解构、RGB 分量数值
ColorPickerTest3方形半宽点击 sat=0.5、圆形顶部 hue=270、中心 sat=0、环形角度命中
StateUpdateTest10rememberUpdatedState 语义:长生命周期手势回调不拿 stale lambda(三组件 × 场景)

其中 UI 测试依赖 desktop 运行时:desktop-jvm:1.11.1 + skiko-awt-runtime-windows-x64:0.144.6(版本从 jvmTestRuntimeClasspath 依赖树反查)。补 skiko 前运行期报 NoClassDefFoundError ... LibraryLoader,补齐后全绿。

6.3 HAP 验证

:core:compileKotlinJvm / compileKotlinOhosArm64 / compileKotlinOhosX64: BUILD SUCCESSFUL
:core:jvmTest: 18/18 PASSED
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs/{arm64-v8a,x86_64}
BuildNativeWithCmake: finished(双 ABI 编出 libpipette_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

KN so(debugShared 未 strip)arm64 约 27.8 MB、x86_64 约 26.6 MB,另各配 libc++_shared.so 约 1.85 / 1.73 MB。so 体积大头是链接进 sharedLib 的整条 Compose 鸿蒙线运行时(runtime / ui / foundation),与已过审 compose-ripple-indication 的 libohosripple.so(27.8 MB)同一量级——业务代码本身只有几 KB,release strip 后会显著缩小。

6.4 功能用例

  1. 启动后预览页签出现版本行 2.0.0-ohos.1(来自 pipetteVersion() 真实 Native 调用)与 ComponentSize 128dp(真库常量);
  2. 颜色卡片 hex / HSV / RGB / ARGB / packedValue / toString / isDark / 对比色随拖动实时刷新,全部出自 pack op 真库响应;
  3. 圆形页签:Down 落在圆外无反应(上游 Down 语义用原始坐标判圆内);Move 拖出圆外 thumb 被拉回圆边(先 circularClamp 钳制);按压时 thumb 从 10vp 涨到 14vp;明度滑杆联动光谱盘亮度与径向白渐变;
  4. 方形页签:左边缘列饱和度≈0、下边缘行明度≈0(sat=x/w、val=1−y/h);色相滑杆只改底色不改对角线渐变形状;拖出边界直接钳制;
  5. 圆环页签:触点落在环带(内径²…外径²)内才生效,环心拖动无反应;环心当前色实心圆随选择变化;
  6. 桥接页签:10 个已知色 fromArgb 对账表全部 ok:true(#FF0000 → H0/S100%/V100%、#00FF00 → H120……与 JVM 侧 HsvColor(hex) 输出一致);pack→fromArgb 往返校验 ΔH < 0.02°;palette 色带呈连续光谱;操作日志按时间戳滚动留痕;
  7. 每步操作日志记录 op 与结果(保留 8 条),ok:false 的错误原文进日志不闪退。

6.5 验证结论

取色器组件库、颜色数值引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的取色会话:版本握手、三种手势取色、真库颜色数值对账与往返校验闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/compose-pipette.git
cd compose-pipette
$env:JAVA_HOME = "F:/cmp/.tools/jdk-21.0.12.1+1"
.\gradlew.bat :core:compileKotlinJvm :core:jvmTest                                       # 18 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
# DevEco Studio 打开 example/harmonyApp → 自动签名 → Build → Build Hap(s)
hdc install -r entry\build\default\outputs\default\entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.pipette -m entry
hdc shell hilog | findstr Pipette

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 CMP 适配,必须让 :core 的全部 @Composable 源码真正编译成 ohosArm64 / ohosX64 产物——工具链选型的第一步是确认"这条 Kotlin 线有没有 compose 编译器插件";
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. Compose 鸿蒙线不发布 *-desktop 子制品,jvm 目标必须换上游线(双线按源码集隔离),单线强吃会解析失败;
  4. jvmTest 的 UI 测试(runComposeUiTest)需要 OS 原生 skiko:plain desktop-jvm 编译通过但运行期 NoClassDefFoundError ... LibraryLoader——skiko 版本从 jvmTestRuntimeClasspath 依赖树反查(compose 1.11.1 配 0.144.6),再按宿主机补 skiko-awt-runtime-<os>-<arch>;
  5. gradlew.bat 启动只认 JAVA_HOME,gradle.properties 里的 org.gradle.java.home 要等 Gradle 起来才生效——环境变量没设时 wrapper 直接报 “JAVA_HOME is not set”;
  6. ARGB 数值别走 Float 解析:0xFFFFFFFF 超 24 位尾数精度,pack → fromArgb 往返必漂,桥内 JSON 数字解析对 32 位整数要用 Double 通道;
  7. DevEco 工程缺模块级 entry/hvigorfile.ts(hapTasks)会在 hvigor 阶段报 00303148 Configuration Error: Hvigorfile not found——工程根的 hvigorfile 有了还不够,每个模块一份;
  8. ArkTS 里 Canvas 渐变对象的类型是 CanvasGradient:createRadialGradient / createLinearGradient 的返回值都标它——写成 RadialGradient(不存在)或 LinearGradient(那是 ArkUI 样式属性类型,要求 colors 数组)都会编译失败;
  9. ArkTS 接口字段要先声明后使用:响应接口漏写 x?: number; y?: number; 就报 Property 'x' does not exist on type——TS 的鸭子类型在 ArkTS 严格模式下行不通;
  10. ArkTS 的 TextAlign 枚举没有 Right 成员(有 End 与大写 RIGHT),用错直接编译错误并提示 “Did you mean ‘RIGHT’?”;
  11. ArkUI TouchObject.x/y 单位是 vp,Canvas 绘制单位是 px——触摸坐标喂桥前必须 vp2px,否则触点与取色位置系统性错位;
  12. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  13. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  14. DevEco 永远打开 example/harmonyApp 而不是仓库根目录——根目录是 Gradle KMP 工程,hvigor 认的是 harmonyApp 下的工程结构。

8.2 已知问题

  • OHOS 没有社区 Compose 线的组合宿主,鸿蒙侧视觉(光谱、渐变、thumb、动画时序)是 ArkUI Canvas 重绘,不是 Compose 代码执行;颜色数值全部真库计算,手势与布局语义按上游公式复刻;
  • 上游 private 的八个位置公式在桥内逐字复刻(README.OpenHarmony_CN.md 5.5 节有完整对照表),上游改动需人工同步——这是唯一漂移点;
  • debug so 约 27.8 MB(整条 Compose 运行时链接进 sharedLib),release strip 待做;
  • sample(material3 demo)未移植,material3 鸿蒙线可用性未验证;
  • TriangularColorPicker 上游本身是半成品(手势逻辑被注释),随整库编译但 Demo 不覆盖;
  • 上游 API 基线文件 core/api/core.api 停用(binary-compatibility-validator 未进鸿蒙构建);
  • android / js / wasmJs / apple 目标剥离(源文件留在树里),发布、ktlint 一并剥离,本 fork 不发 Maven Central。

8.3 未来优化方向

  • 补 release 变体(linkReleaseSharedOhos* + strip),把 HAP 体积压到与功能相称;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • material3 鸿蒙线可用性验证后移植上游 sample;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 社区 Compose 鸿蒙线出现组合宿主后,把 :core 的组件直接挂上去跑真 Compose 渲染,ArkUI 重绘降级为过渡实现——整库编译的粒度保证这一步只是"换宿主",不是重写。

九、总结

9.1 核心难点回顾

上游 compose-pipette 2.0.0(全部 commonMain,五个 @Composable 组件)
    → 验证 Compose 鸿蒙制品线(runtime / ui / foundation 的 ohos klib)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-07(compose 编译器插件随线)
    → 根构建替换(cmp_qrose 模式,sample / build-logic 留盘 + *.upstream 存档)
    → ohosArm64 / ohosX64 KLIB,六文件零修改、零新增源码
    → C ABI(@CName × 2)+ 13 op 扁平 JSON(真库数值 / 复刻公式分界)
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析 + Canvas 光谱重绘(palette 真库逐色转换)
    → ArkUI 真机五页签验收(取色 / 对账 / 往返校验 / 版本握手)

9.2 封装层次

  • 库层:上游原版 CircularColorPicker / SquareColorPicker / RingColorPicker / HsvColor(逐字编译进 so);
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON(13 op);
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、手势分发(vp2px 换算)、Canvas 光谱重绘、日志与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 整库编译是 CMP 组件库的正解,前提是制品线到位:Compose 鸿蒙线补齐 runtime / ui / foundation 的 ohos klib 后,组件类库的正确答案就是"六文件逐字编译 + 编译器插件",不需要像早期仓库那样抽零依赖内核——但每换一个库都要重新验证当天的制品可达性,路线选择不能抄历史结论;
  2. 工具链版本跟着源码形态走:钉版由"源码里有没有 @Composable"反推(有 → 2.4.20 线的编译器插件,无 → 2.2.21 线也能用),Gradle 经验按工具链线区分并选社区已验证配对(本仓 9.6.1);
  3. "真库 / 复刻 / 重绘"三分法是 CMP 适配的审核语言:颜色数值必须真库执行(pack / fromArgb / palette),private 公式逐字复刻并给对照表,视觉诚实标注为 ArkUI 重绘——三层不混淆,jvmTest 18 项全绿加上真机对账表,"这还是同一个库"才有说服力。

9.4 适配成果

当前适配已完成:

  • compose-pipette 2.0.0 真实组件库的双 ABI Kotlin/Native 编译(上游 commonMain 六文件零重写、零新增源码,含全部 @Composable 组件与实验性三角取色器);
  • Compose 鸿蒙制品线 1.12.0-0.1.0-07 在取色器库上的完整落地(runtime / ui / foundation ohos klib + 编译器插件);
  • 2 个 C ABI 符号(13 op 扁平 JSON)和 C++ N-API 包装层(DisposeString 纪律);
  • DevEco Stage ArkUI 五页签验收页面(预览 / 圆形 / 方形 / 圆环 / 桥接对账);
  • 18 项 JVM 交叉验证全绿(上游原测试文件,含 runComposeUiTest 组件行为测试)+ HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证;
  • 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

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐