开源鸿蒙平台 CMP 三方库「skiko」适配全流程

本文记录 skiko 接入 OpenHarmony 的完整过程,覆盖 Skia 二进制供体选型、社区 ohos klib 消费、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

skiko(Skia for Kotlin)是 JetBrains 的 Kotlin Multiplatform 图形库:把 Skia 的 Canvas / Paint / Path / Shader / 图像编解码 / 文本排版暴露为 org.jetbrains.skia 包下的 Kotlin API,是 Compose Multiplatform 在桌面与 iOS 上的渲染底座。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取"、KStore 的"真编译整库")有本质区别:skiko 的核心是 C++ 桥接层 + Skia 静态库二进制 ,上游从 JetBrains/skia GitHub releases 按平台下载预编译产物,而 Skia m155 没有 ohos 制品 ,自行编译 Skia 在评审环境不可复现。因此本仓采用"社区 klib 供体 "路线:消费 CPF-KMP-CMP 社区已发布在 eazytec Maven 上的 org.jetbrains.skiko:skiko:0.9.22.2-1.0.0(ohos 版 Skia 静态内嵌于 klib),上游源码零改动 保留,新增 Kotlin/Native 桥 @CName("SkikoRender") 与 CMake NAPI 包装,把 org.jetbrains.skia 公共 API 的真实渲染结果(PNG)送到 ArkUI 真机页面上。

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

上游仓库: JetBrains/skiko

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

skiko 为 Kotlin 应用提供"2D 渲染"这一件事的完整答案:Surface 建画布,Canvas + Paint 画图形,Path 描述任意轮廓,Shader 出渐变,MaskFilter / ImageFilter / PathEffect 做滤镜与描边特效,Image 负责 PNG/JPEG 解码回编,FontMgr + Font + Canvas.drawString 负责文本。Compose Multiplatform 桌面端的每个像素都经过这套 API。OpenHarmony 应用同样需要高性能 2D 渲染能力,但上游构建(Kotlin 2.4.20 / Gradle 9.5.0 / Skia m155)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明 org.jetbrains.skia 渲染引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证:Skia 二进制在 ohos 侧的可达性、Kotlin/Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍 具体问题
Skia 二进制缺失 上游按平台从 JetBrains/skia releases 下载静态库(Skia-m155-...-<os>-<bt>-<arch>.zip),没有 ohos 产物;自编译需 depot_tools/GN + OHOS NDK,~30GB 构建树不可复现。
构建体系重型 上游 buildSrc 自定义 OS 枚举、cinterop 生成、MergeStaticArchivesTask、C++ 交叉编译全链路,为 ohos 改造等于重写发布流水线。
工具链不一致 上游 Kotlin 2.4.20 / Gradle 9.5.0 与鸿蒙工具链(HarmonyOS Kotlin 2.2.21 线 / Gradle 8.x 验证区间)无法共存于一个 Gradle 构建。
语言边界不同 ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
渲染语义跨不了桥 SkiaLayer、GPU 上下文、DirectContext 是平台 UI 侧概念,C ABI 只能同步一来一回。
交付链路复杂 .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:社区 klib 供体的消费与验证、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。渲染语义完全由社区 klib 里的上游原版 org.jetbrains.skia 实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示 PNG。

1.2 库提供的能力

Demo 提供九个真实渲染场景,对应桥接的全部入口:

  • 握手(info) :返回桥版本、klib 坐标、场景清单,并探测系统 FontMgr 字体族数------启动即自检 JSON 链路;
  • 基础图形(shapes) :drawRect / drawRRect / drawOval / drawCircle / drawLine / drawArc / drawPoints / drawRectShadow 九宫格,前景色五选一、描边宽度 1...20 实时可调;
  • 渐变(gradient) :linear / radial / sweep 三种 Shader,暖冷两套色标,叠加 BlendMode.MULTIPLY 圆;
  • 路径(path) :五角星 PathFillMode.WINDING vs EVEN_ODD 中心对比、conicTo / cubicTo / arcTo,端帽(BUTT/ROUND/SQUARE)与拐角(MITER/ROUND/BEVEL)对照;
  • 路径效果(patheffect) :PathEffect.makeDash / makeDiscrete / makeCompose 作用在波浪线与五角星描边上;
  • 模糊(blur) :MaskFilter.makeBlur 四种 FilterBlurMode(NORMAL/SOLID/OUTER/INNER)网格,sigma 2...60 滑杆实时调节;
  • 混合模式(blend) :8 种 BlendMode 双色圆交叠网格;
  • 图像(image) :内嵌 6.8KB 示例 PNG 经 Image.makeFromEncoded 解码 → 棋盘格透明背景 → 旋转 8° / 缩放绘制 → 左上角 2× 局部放大 → encodeToData(PNG) 回编;
  • 文本(text) :FontMgr.default 默认字体族多字号渲染(含 CJK),notes 回传 ascent / descent 度量与字体族诊断。

响应包含:

字段 作用
ok 操作是否成功,失败时 error 带原文。
png / bytes 渲染结果 PNG 的 Base64 与字节数(info 场景无此字段)。
kind / width / height 回显场景与画布尺寸。
ms 桥内耗时(TimeSource.Monotonic 计时)。
notes 场景附注(image 的解码诊断、text 的字体度量)。

引擎侧完整能力(Canvas/Paint/Path/Shader/编解码/FontMgr/skparagraph 等约 240 个 Kotlin API 文件)全部随社区 klib 静态链接进 .so,真机页面上看到的所有像素都是 org.jetbrains.skia 真实渲染的产物,而非 ArkTS 自绘。

1.3 实现适配

维度 要求
代码复用 上游 skiko/ 源码零改动 ;适配代码全部集中在 example/(桥 576 行 + MiniJson 235 行 + 工程配置)。
平台目标 example/nativeApp 声明 ohosArm64 + ohosX64 出双 ABI 动态库 libohosskiko.so;上游构建独立保留(JVM/桌面可对照验证)。
构建隔离 根构建替换为极简 ohos 构建(cmp_qrose 模式,Gradle 8.14.1);skiko/ 自带 9.5.0 wrapper 原样可跑,benchmarks/ samples/ 留盘不进构建。
桥接稳定 使用 1 个 C ABI(SkikoRender)和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律 不重复发布任何 ohos 二进制:消费社区 skiko:0.9.22.2-1.0.0(其元数据声明的 stdlib 约束恰为 2.2.21-1.0.0,与 qrose 已过审工具链完全同线)。
UI 完整 单页覆盖全部桥接入口:八场景 chip、参数控件、画布尺寸、notes 诊断列表和效果图显示。
仓库规范 项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。skiko 渲染管线的鸿蒙化(XComponent + OpenGL ES 自渲染、Fusion Renderer)属于 CPF-KMP-CMP 主线工程;本仓刻意收敛在"公共 API 是否可用"这一层,demo 走 CPU 光栅(Surface.makeRaster)。

二、实现路线图

text 复制代码
第 1 阶段:项目初始化     ── 盘点上游构建与 Skia 二进制边界、确认社区 klib 供体、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 与供体成对钉版
第 3 阶段:桥协议建模     ── 九场景 JSON 契约、PNG Base64 载荷、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── info 握手、场景 chip、参数控件、Base64 解码显示与 notes 列表
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
text 复制代码
skiko/                               上游 JetBrains skiko 原样保留(独立 Gradle 构建,自带 9.5.0 wrapper,不进根 settings)
benchmarks/ samples/                 上游附属 includeBuild,留盘不进构建
example/nativeApp/                   Kotlin/Native 动态库(SkikoBridge + MiniJson)
example/harmonyApp/                  DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md       鸿蒙适配说明(中英双份,CN 版 19 节主文档)
README.upstream.md                   上游原文保存
doc/                                 博客文章与效果图

构建拓扑走 cmp_qrose 模式 (根构建即 ohos 构建):settings.gradle.kts 只 include :example:nativeApp,rootProject.name = "ohos_skiko"。上游的 dokka / 加密签名 / benchmark / 发布插件全部随根构建替换剥离------本 fork 不发 Maven Central,也不重新发布任何 ohos 二进制。

1.2 工具链矩阵
项目 配置
Kotlin Multiplatform HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven,"公司 OHOS Konan",含 HiLog/FFRT 平台 cinterop)
JDK 21(Temurin 21.0.12,org.gradle.java.home 钉死)
OpenHarmony 目标 ohosArm64 + ohosX64(上游 skiko/ 构建独立保留作对照)
Gradle 8.14.1(根适配构建,qrose 同款;上游 skiko/ 保留自带 9.5.0 wrapper)
供体 klib org.jetbrains.skiko:skiko:0.9.22.2-1.0.0(ohosArm64/ohosX64 双 klib,Skia 静态库打包于 included/,27.0 MB)
传递依赖 kotlinx-coroutines-core 1.10.2-1.0.0、atomicfu 0.31.0-1.0.0(由 klib 元数据解析)
DevEco product 5.1.0(18) 兼容
DevEco Studio 5.1.0 Release
ABI arm64-v8a + x86_64
Bundle org.terminator.ohos.skiko
powershell 复制代码
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 供体制品验证(本适配的成败手)

先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 skiko 的 ohos 制品。用 .module 元数据验证:

text 复制代码
org.jetbrains.skiko:skiko:0.9.22.2-1.0.0
  variants: ohosArm64Api-published / ohosX64Api-published(klib, org.jetbrains.kotlin.native.target = ohos_arm64 / ohos_x64)
  dependencies: kotlinx-coroutines-core 1.10.2-1.0.0 + atomicfu 0.31.0-1.0.0
  dependencyConstraints: kotlin-stdlib → 2.2.21-1.0.0

三个关键结论:其一,skiko 的 ohos klib 由社区 skiko fork(develop-0.9.22.2-OH 线)生产,Skia 静态库(libskia.a、libicu.a、libharfbuzz.a、libskparagraph.a、libsvg.a 及编解码库共 ~17 个)整体打进 klib 的 included/ 目录 ,链接期静态并入产物------不需要任何 Skia 下载;其二,klib manifest 依赖 org.jetbrains.kotlin.native.platform.HiLog / .FFRT 平台 cinterop,这些随 HarmonyOS Kotlin 发行版发布,工具链必须是同线版本;其三,.module 里的 stdlib 约束版本就是应选的 Kotlin 版本------这条反推规则是本次适配最重要的经验(见第 2 阶段)。

skiko-ohosarm64 还发布了 sources jar(242 个 Kotlin 文件),供体 API 面可直接核对:FontMgr.default、Canvas.drawString、Image.encodeToData 等桥接所需 API 全部在 commonMain,无 ohos 特化缺口。

1.4 页面边界
text 复制代码
标题区          skiko · OpenHarmony / 桥信息行(SkikoRender/1 ... org.jetbrains.skiko:skiko 0.9.22.2-1.0.0)
场景区          八 chip:基础图形 / 渐变 / 路径 / 路径效果 / 模糊 / 混合模式 / 图像 / 文本
参数区          随场景切换:色板+描边滑杆(shapes)/ 暖冷(gradient)/ 效果三选(patheffect)/ sigma 滑杆(blur)...
画布区          560 / 720 / 1080 三档
操作区          生成 (SkikoRender) 按钮
状态行          生成成功 shapes 720x720 png=28077B / 失败原文
notes 区        场景诊断(字体族、图像解码尺寸、FontMetrics 等)
图像区          Base64 解码为 PixelMap 全宽显示

页面启动时先发 scene:'info' 做握手:一行调用同时验证 N-API 导入、KN 链路、JSON 往返和 FontMgr 探针,把"桥是否活着"变成页面顶部可见的文字。

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

2.1 根构建替换

settings.gradle.kts 把 eazytec 仓库写在最前:

kotlin 复制代码
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_skiko"

include(":example:nativeApp")

根 build.gradle.kts 收敛为版本声明 + 全局禁用 ohos 测试任务(ohos 工具链没有测试运行器):

kotlin 复制代码
plugins {
    alias(libs.plugins.kotlin.multiplatform) apply false
}

allprojects {
    group = "org.jetbrains.skiko"
    version = providers.gradleProperty("version").orElse("0.9.22.2-ohos.1").get()
}

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

gradle/libs.versions.toml 只钉两个版本:

kotlin 复制代码
[versions]
kotlin = "2.2.21-1.0.0"      # 必须等于供体 klib 的 stdlib 约束版本
skiko = "0.9.22.2-1.0.0"     # CPF-KMP-CMP 社区 ohos klib

[libraries]
skiko-ohos = { module = "org.jetbrains.skiko:skiko", version.ref = "skiko" }

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }

根构建与上游构建解耦 :上游 skiko/ 是重型构建(buildSrc、Gradle 9.5.0、Skia 下载、C++ 交叉编译),根 settings 不再 includeBuild 它------组合构建会强迫上游降到 8.14.1。上游构建入口仍是 skiko/gradlew.bat(9.5.0),随时可跑 JVM 目标做对照,这正是"零改动"的可信来源。

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

最初按较新的 2.2.21-1.2.0-02 线配对(本机 ~/.konan 已有该发行版缓存),compileKotlinOhosArm64 直接报:

text 复制代码
Execution failed for task ':example:nativeApp:compileKotlinOhosArm64'.
> java.lang.ClassNotFoundException: org.jetbrains.kotlin.cli.utilities.MainKt

排查结论:HarmonyOS Kotlin 的 KN 编译器以 in-process 模式运行,编译器类来自 ~/.konan/kotlin-native-prebuilt-windows-x86_64-<ver>/konan/lib/kotlin-native-compiler-embeddable.jar;本机缓存的 1.2.0-02 / 1.1.0-05 发行版该目录为空(eazytec Maven 上也无此 jar,HTTP 404),只有 2.2.21-1.0.0 发行版完整 。而 skiko:0.9.22.2-1.0.0 klib 的元数据约束恰好就是 kotlin-stdlib 2.2.21-1.0.0------工具链与供体成对切到 1.0.0 线后一次通过。

规则沉淀:供体 klib 的 .module 里 stdlib 约束版本就是应选的 Kotlin 版本,不要按"越新越好"配对。

2.3 编译桥模块

example/nativeApp/build.gradle.kts:

kotlin 复制代码
plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()

    ohosArm64 {
        binaries.sharedLib { baseName = "ohosskiko" }   // → libohosskiko.so
    }
    ohosX64 {
        binaries.sharedLib { baseName = "ohosskiko" }
    }

    sourceSets {
        ohosMain.dependencies {
            // Gradle 模块元数据自动解析 ohosArm64/ohosX64 变体,
            // 并传递拉取 coroutines-core / atomicfu 的 ohos 制品
            implementation(libs.skiko.ohos)
        }
    }
}

fun cxxShared(triple: String): File {
    val deps = file("${System.getProperty("user.home")}/.konan/dependencies")
    val hits = deps.listFiles().orEmpty()
        .map { File(it, "lib/$triple/libc++_shared.so") }.filter { it.isFile }
    check(hits.isNotEmpty()) { "libc++_shared.so for $triple not found under $deps" }
    return hits.first()
}

tasks.register<Copy>("copyKnSoArm64") {
    from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) { include("libohosskiko.so") }
    from(cxxShared("aarch64-linux-ohos"))
    into(rootProject.layout.projectDirectory.dir("example/harmonyApp/entry/libs/arm64-v8a"))
}
// copyKnSoX64 同构;linkDebugSharedOhos{Arm64,X64} finalizedBy 拷贝任务

实测:

powershell 复制代码
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64   # BUILD SUCCESSFUL in 49s
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosX64     # BUILD SUCCESSFUL in 34s

产物自动落入 example/harmonyApp/entry/libs/<abi>/:

产物 arm64-v8a x86_64
libohosskiko.so(含静态 Skia 全家) 22,847,736 B ≈ 21.8 MB 22,419,000 B ≈ 21.4 MB
libc++_shared.so(konan sysroot 随附) 1,850,184 B 1,731,432 B

so 内确认导出 SkikoRender 与 libohosskiko_symbols 两个 C ABI 符号;22 MB 的体积就是 Skia 静态库(libskia、libskunicode_icu、libicu、libharfbuzz、libskparagraph、libsvg、编解码库等)整体链接的结果,与 skiko 在 iOS 上的产物同量级。

第 3 阶段:桥协议建模

3.1 JSON 契约
text 复制代码
org.jetbrains.skia/**(社区 klib,Skia 静态内嵌,原样链接)
        ↓ Surface.makeRaster / Canvas / Paint / Shader / Image / FontMgr
SkikoBridge.kt(@CName × 1,扁平 JSON 进、PNG Base64 出)
        ↓ UTF-8 JSON
N-API skikoRender()
        ↓
ArkUI Index.ets(八场景单页)

请求与响应:

text 复制代码
请求 : {"scene":"shapes","width":720,"height":720,"fg":"#7C3AED","strokeWidth":4}
       {"scene":"gradient","width":720,"height":720,"warm":true}
       {"scene":"image","width":720,"height":720,"image":"<base64 PNG>"}
响应 : {"ok":true,"kind":"shapes","png":"<base64>","width":720,"height":720,"bytes":28077,"ms":37}
       {"ok":false,"error":"...","ms":2}

两个设计选择:png 以 Base64 字符串承载(ArkTS util.Base64Helper 一次解码成 PixelMap);所有场景参数(fg / bg / strokeWidth / warm / effect / sigma / image / family)都是可选键,缺省值在桥内收敛,非法值走 ok:false 通道------任何异常都被捕获并转成 JSON,ArkTS 侧不会因非法输入崩溃。

3.2 序列化为什么手写 MiniJson

桥内 JSON 解析/序列化是 235 行手写 MiniJson(解析器 + 逗号状态机 Writer),不引入 kotlinx.serialization:序列化运行时进 so 的体积代价远超 30 行解析逻辑,qrose 适配时论证过同一取舍。真机闭环证明这个决定需要配套纪律------第一版 Writer 的逗号状态机有 bug(键值对之间漏逗号,输出 {"ok":true"kind":...} 非法 JSON),本机双 ABI 编译全绿也没暴露,直到真机 JSON.parse 抛 SyntaxError 才抓出来 。修复后 info 握手成为每次启动的序列化自检(见 8.1 踩坑 #5)。

3.3 错误通道

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

kotlin 复制代码
@CName("SkikoRender")
fun skikoRender(request: String): String {
    val started = TimeSource.Monotonic.markNow()
    return try {
        dispatch(parse(request) as JsonValue.Obj, started)
    } catch (t: Throwable) {
        fail("${t::class.simpleName ?: "error"}: ${t.message ?: "no message"}", started)
    }
}

非法 JSON、未知 scene、image 场景缺 image 字段、坏 Base64、字体后端缺失,全部走同一个 ok:false 通道,ArkTS 侧统一渲染错误原文。

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

4.1 三层桥接
text 复制代码
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libskiko_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosskiko.so)
  │ SkikoRender
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复刻渲染语义,所以选择 C ABI + JSON。与 KStore 的两个符号不同,skiko 只需要一个入口:场景分发本来就是 JSON 里的一个键,渲染参数天然结构化。

4.2 Kotlin/Native 导出函数
kotlin 复制代码
@CName("SkikoRender")
fun skikoRender(request: String): String   // info + 八渲染场景一口进
C ABI 符号 ArkTS 方法 覆盖内核
SkikoRender skikoRender(req) Surface.makeRaster 建画布 + 九场景全部 org.jetbrains.skia 调用 + encodeToData(PNG) 回编
4.3 C++ N-API 方法分发
cpp 复制代码
napi_property_descriptor desc[] = {
    {"skikoRender", nullptr, NapiSkikoRender, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr}};

回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosskiko_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosskiko_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构前缀与 SkikoRender 业务符号),与 KN 生成的完整头逐项核对过 ABI 兼容,不拖整份生成头。

4.4 CMake 和类型声明
cmake 复制代码
add_library(ohosskiko SHARED IMPORTED)
set_target_properties(ohosskiko PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(skiko_napi SHARED napi_init.cpp)
target_link_libraries(skiko_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosskiko)

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

typescript 复制代码
export const skikoRender: (request: string) => string;

第 5 阶段:ArkUI 宿主

5.1 握手与分发
typescript 复制代码
aboutToAppear(): void {
  this.runInfo();                    // scene:'info' → 桥信息 + FontMgr 字体族数进顶栏
}

private generate(): void {
  const scene = this.scenes[this.sceneIndex].id;
  const req: RenderRequest = {
    scene: scene,
    width: this.canvasSize,
    height: this.canvasSize
  };
  if (scene === 'shapes') {
    req.fg = this.fgs[this.fgIndex];       // 参数按场景补可选字段
    req.strokeWidth = this.strokeWidth;
  } /* ... gradient/patheffect/blur/image ... */
  const res = JSON.parse(skikoRender(JSON.stringify(req))) as RenderResponse;
  if (!res.ok || res.png === undefined) {
    this.status = `失败: ${res.error ?? 'no png'}`;
    return;
  }
  this.decodePng(res.png);           // Base64 → PixelMap → @State 刷新
}

RenderRequest / RenderResponse 都是显式声明的 interface(可选字段用 ?:),ArkTS 严格模式(arkts-no-untyped-obj-literals)下对象字面量必须对应显式类型 ------第一版用 Record<string, Object> 动态构造请求被编译器拒绝,改为 interface 化后通过(见 8.1 踩坑 #6)。

5.2 Base64 到像素
typescript 复制代码
private decodePng(b64: string): void {
  const bytes: Uint8Array = new util.Base64Helper().decodeSync(b64);
  const sliced = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
  image.createImageSource(sliced).createPixelMap().then((pm: image.PixelMap) => {
    this.pixelMap = pm;              // Image(this.pixelMap) 全宽显示
  });
}
5.3 页面交互

单页自上而下:标题与桥信息行、八场景 chip、画布尺寸三档、随场景切换的参数区(色板圆点 + 描边滑杆 / 暖冷切换 / dash·discrete·compose 三选 / sigma 滑杆 / 场景说明文字)、生成按钮、状态行(生成成功 shapes 720x720 png=28077B)、notes 诊断列表(字体族、解码尺寸、FontMetrics)、渲染结果图。文本场景的说明直接标注"失败时会显示错误信息"------字体后端属系统侧能力,页面把不确定性如实交给用户。

第 6 阶段:示例与验证

6.1 工程结构
text 复制代码
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosskiko)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       ├── SkikoBridge.kt                    @CName("SkikoRender") + 九场景(576 行)
│       └── MiniJson.kt                       手写 JSON 解析/序列化(235 行)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.skiko、versionName 0.9.22.2-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/libohosskiko_api.h
        ├── src/main/cpp/types/libskiko_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面
6.2 构建、签名和安装
powershell 复制代码
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

# DevEco Studio 打开 example/harmonyApp(勿开仓库根目录)
# File → Project Structure → Signing Configs 勾选自动签名,Build HAP(s)

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

powershell 复制代码
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.skiko -m entry

四、完整代码对照

4.1 整体架构

text 复制代码
org.jetbrains.skia/**(社区 klib 原样链接,Skia 静态内嵌)
    │ Surface / Canvas / Paint / Path / Shader / Image / FontMgr
SkikoBridge.kt + MiniJson
    │ SkikoRender(九场景分发 + PNG Base64)
libskiko_napi.so
    │ skikoRender
Index.ets
    │ JSON parse + Base64 解码 + @State 更新
    ▼
ArkUI 真机页面(八场景单页)

4.2 文件清单

文件 职责
settings.gradle.kts 极简 ohos 构建、eazytec Maven 置前、ohos_skiko、上游 includeBuild 剥离
gradle/libs.versions.toml Kotlin 2.2.21-1.0.0(= 供体 stdlib 约束)+ skiko 0.9.22.2-1.0.0 钉版
build.gradle.kts(根) 版本 0.9.22.2-ohos.1、全局禁用 Ohos*Test* 任务
example/nativeApp/build.gradle.kts 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../SkikoBridge.kt 1 个 C ABI、九场景、错误通道
nativeApp/.../MiniJson.kt 手写 JSON 解析器 + Writer
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 验收页面(interface 化请求/响应类型)
README.OpenHarmony_CN.md 鸿蒙适配主文档(19 节,含去重定位与故障排查)

4.3 关键 API 对照

层次 API 作用
引擎 Surface.makeRaster(ImageInfo.makeN32Premul) CPU 光栅画布(RGBA8888 预乘)
引擎 Canvas.drawRect/drawRRect/drawCircle/drawArc/drawPoints/drawString 图元与文本绘制
引擎 Paint(mode/color/strokeWidth/blendMode/maskFilter/pathEffect/shader) 画笔全参数
引擎 Shader.makeLinear/Radial/SweepGradient 三种渐变
引擎 PathEffect.makeDash/makeDiscrete/makeCompose、MaskFilter.makeBlur 描边特效与模糊
引擎 Image.makeFromEncoded / Image.encodeToData(PNG) 图像解码 / 回编
引擎 FontMgr.default / Font / Font.metrics 系统字体、度量
Native SkikoRender C ABI JSON 导出(九场景一口进)
N-API skikoRender(req) 向 ArkTS 暴露能力
ArkTS generate() / decodePng() 触发渲染并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

typescript 复制代码
const req: RenderRequest = { scene: 'shapes', width: 720, height: 720, fg: '#7C3AED', strokeWidth: 4 };
const res = JSON.parse(skikoRender(JSON.stringify(req))) as RenderResponse;
// res.ok === true;res.png 是 Skia 渲染结果的 PNG Base64,decodePng 后成 PixelMap
kotlin 复制代码
val surface = Surface.makeRaster(ImageInfo.makeN32Premul(w, h))
shapesScene(surface.canvas, w, h, req)          // drawRect / drawRRect / drawCircle / ...
val data = surface.makeImageSnapshot().encodeToData(EncodedImageFormat.PNG, 100)
val png = Base64.Default.encode(data.bytes)      // → 响应 JSON

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

五、关键决策说明

决策 1:社区 klib 供体,而不是自编译 Skia 或改造上游构建

上游 ohos 编译的三条路(给 buildSrc 加 OS.Ohos、自编译 Skia、等官方发布 ohos zip)全部卡在"Skia m155 没有 ohos 二进制"这一件事上。社区 fork(develop-0.9.22.2-OH)已经解决了这件事并把 klib 发布在 eazytec Maven------再自己编一遍既是重复劳动,也无法在评审环境复现。消费制品让"Skia 可用"从构建问题降级为依赖声明问题。

决策 2:上游源码零改动 + 双构建解耦

本仓对上游的唯一改动是根构建与 example/ 新增文件,skiko/ 目录一个字节没动,master 可以随时 fast-forward。根构建(8.14.1,HarmonyOS Kotlin 验证区间)与上游构建(9.5.0)各自独立,互不拖累------组合构建会强迫上游降级,风险不可控。

决策 3:工具链与供体成对,版本由元数据反推

先查 klib .module 的 stdlib 约束,再钉 Kotlin 版本:skiko:0.9.22.2-1.0.0 → 2.2.21-1.0.0。反过来按"本机缓存最新"配对(1.2.0-02)会踩进 konan 发行版 konan/lib 为空的坑,报出毫无指向性的 ClassNotFoundException: MainKt。

决策 4:单 C ABI + JSON-in/PNG-out + CPU 光栅

渲染管线的鸿蒙化(XComponent + GLES 自渲染、Fusion Renderer)是 CPF-KMP-CMP 主线的战场。本仓把验证面收敛在"公共 API 是否可用":一个符号、一份 JSON 契约、CPU 光栅出 PNG------问题定位最直接,也不与主线重复。GPU 路径留给供体升级时评估。

决策 5:真机闭环 + info 握手自检

编译全绿证明不了序列化正确性:第一版 JsonWriter 逗号 bug 只有真机 JSON.parse 能暴露。scene:'info' 被设计成每次启动的必经握手------桥版本、klib 坐标、字体族数一次调用全回来,序列化链路坏没坏在页面顶部直接可见。

六、测试与验证

6.1 测试环境

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

6.2 本机构建验证

text 复制代码
:example:nativeApp:linkDebugSharedOhosArm64 : BUILD SUCCESSFUL(49s,含 klib 下载与链接)
:example:nativeApp:linkDebugSharedOhosX64   : BUILD SUCCESSFUL(34s)
libohosskiko.so 导出符号检查 : SkikoRender / libohosskiko_symbols 均存在
libc++_shared.so 随拷贝任务进入 entry/libs/{arm64-v8a, x86_64}
手写 libohosskiko_api.h 与 KN 生成头逐项核对 ABI 兼容

6.3 HAP 验证

text 复制代码
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
BuildNativeWithCmake: finished(双 ABI 编出 libskiko_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

HAP 内双 ABI 各三件套(libskiko_napi.so / libohosskiko.so / libc++_shared.so);KN so 因静态含 Skia 全家(arm64 ≈ 21.8 MB)明显大于纯 Kotlin 桥的库,属预期体积。

6.4 功能用例

真机已验证(截图见下节):

  1. 启动即握手:标题下出现 SkikoRender/1 (ohos_skiko example) | org.jetbrains.skiko:skiko 0.9.22.2-1.0.0 (CPF-KMP-CMP)------来自真实 Native 调用,同时证明 JSON 序列化链路正常;
  2. 基础图形场景:生成成功 shapes 720x720 png=28077B,3×3 网格完整呈现矩形、圆角矩形、椭圆、圆、交叉线、弧、点阵、阴影方块与胶囊------全部为 org.jetbrains.skia 真实渲染;
  3. 参数联动:色板选中紫色(#7C3AED)与描边 4 直接体现在渲染结果上;
  4. 首次调用含静态初始化的耗时在 ms 字段可见,第二次起回落稳定值。

验收清单(逐场景过点,随真机测试回填):

  1. 渐变 / 路径 / 路径效果 / 模糊 / 混合模式五场景图形正确、参数即时生效;
  2. 图像场景:内嵌示例 PNG 解码绘制、透明区呈棋盘格、旋转缩放无锯齿崩坏、notes 回传解码尺寸;
  3. 文本场景:CJK 渲染或明确 fontError 诊断(后端缺失不崩溃),notes 显示 ascent/descent;
  4. 非法输入(未知 scene、缺 image 字段)返回 ok:false 原文,应用不崩溃;
  5. hdc shell hilog | grep SkikoNapi 可见 request bytes 打点。

6.5 验证结论

社区 klib 供体、Kotlin/Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面顶部握手信息与基础图形场景渲染结果均为真实 Native 调用产物(见下节效果图),其余场景按验收清单进行中。

七、运行效果

7.1 真机截图

7.2 命令速查

powershell 复制代码
git clone https://atomgit.com/oh-tpc/skiko.git
cd skiko
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
# DevEco Studio 打开 example/harmonyApp,自动签名后 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.skiko -m entry
hdc shell hilog | grep SkikoNapi

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 工具链与供体必须成对 :按本机缓存最新配 Kotlin 2.2.21-1.2.0-02 报 ClassNotFoundException: org.jetbrains.kotlin.cli.utilities.MainKt------该发行版 konan/lib 为空且 Maven 上无 embeddable jar;正确做法是读供体 klib .module 的 stdlib 约束反推版本(本例 0.9.22.2-1.0.0 → 2.2.21-1.0.0);
  2. gradlew.bat 报 JAVA_HOME is not set:gradle.properties 的 org.gradle.java.home 只在 JVM 起来后生效,wrapper 启动阶段只认环境变量------先 set JAVA_HOME 再执行;
  3. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则 ohos 制品与 HarmonyOS Kotlin 插件都解析不到;
  4. 鸿蒙工具链把 kotlin.system.getTimeMillis 的弃用当 error 报,计时改 TimeSource.Monotonic.markNow() / elapsedNow().inWholeMilliseconds;skiko API 全 Float,参数解析 Double 要显式 .toFloat();
  5. 编译全绿 ≠ 序列化正确 :第一版手写 JsonWriter 的逗号状态机漏了键值对间隔符,输出 {"ok":true"kind":...} 非法 JSON,本机双 ABI 构建毫无异常,真机 JSON.parse 才抛 SyntaxError------修复后把 scene:'info' 设计成每次启动的握手自检;
  6. ArkTS 严格模式(arkts-no-untyped-obj-literals)拒绝 Record<string, Object> 动态构造请求与裸对象字面量入 JSON.stringify:请求/响应全部 interface 化(可选字段 ?:),组件 Options 字面量(Slider({...}) 等)不受限;
  7. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;
  8. dlopen 找不到 KN so:CMake 设 BUILD_RPATH/INSTALL_RPATH $ORIGIN + POST_BUILD 把 NAPI so、KN so、libc++ 拷到同一输出目录;
  9. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();手写 api 头只声明 ExportedSymbols 结构前缀与业务符号,并与 KN 生成头核对;
  10. ArkTS 只能 import NAPI 模块(libskiko_napi.so),不能 import KN so(libohosskiko.so)------CAdapter 不会把 @CName 业务符号挂给 ArkTS;
  11. 签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持 signingConfigs 空数组,证书绝不进入 AtomGit;
  12. 全仓清扫第三方镜像链接(含文档、配置、示例),社区与仓库链接统一 AtomGit(oh-tpc / CPF-KMP-CMP)。

8.2 已知问题

  • Demo 走 CPU 光栅(Surface.makeRaster);DirectContext/GPU、XComponent 自渲染与 Fusion Renderer 不在本仓验证范围(属 CPF-KMP-CMP 主线);
  • 文本场景依赖系统 FontMgr:后端缺失时 text 场景返回 ok:false 与诊断串(info 的 fontFamilies/fontError 可提前探知),其余场景不受影响;
  • 供体 klib 是 0.9.22.2 线(Skia m150 时代),上游 master 已到 m155:org.jetbrains.skia 公共 API 两线间高度稳定,但 m155 新增符号不在验证面内;
  • linkReleaseSharedOhos* 未在本机跑(文档体积数据以 debugShared 为准);
  • 上游 skiko/ 构建未编排进根构建,JVM 对照(:compileAwtKotlinJvm)需独立执行且依赖宿主 Skia 下载与 MSVC 工具链;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.skiko 匹配的签名。

8.3 未来优化方向

  • 社区 0.150.1 线(Kotlin 2.4.20-0.1.0)转正后切换供体:改 gradle/libs.versions.toml 两行版本号 + 按编译错误微调场景调用;
  • 桥增加 skparagraph / skottie 场景(klib 已静态含 libskparagraph.a / libskottie.a,只差桥接入口);
  • 评估 XComponent + OpenGL ES 的 GPU 光栅化 demo,与社区自渲染主线对表;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)验收矩阵与自动化截图脚本,scene:'info' 接入冒烟断言。

九、总结

9.1 核心难点回顾

text 复制代码
上游 skiko master @ 8f09bee48(Kotlin 2.4.20 / Skia m155)
    → 验证 Skia ohos 二进制不可达 → 选定社区 klib 供体(0.9.22.2-1.0.0)
    → 工具链成对钉版 Kotlin 2.2.21-1.0.0(供体 stdlib 约束反推)
    → 根构建替换(cmp_qrose 模式,Gradle 8.14.1),上游构建零改动解耦
    → ohosArm64 / ohosX64 双 ABI libohosskiko.so(Skia 静态内嵌)
    → C ABI(@CName × 1,九场景 JSON-in/PNG-out)
    → C++ N-API(DisposeString 纪律)
    → ArkTS interface 化请求 + Base64 解码
    → ArkUI 真机单页验收(握手 + shapes 已验证,效果图留档)

9.2 封装层次

  • 供体层:社区 klib 里的上游原版 org.jetbrains.skia(Canvas/Paint/Path/Shader/编解码/FontMgr)+ 静态 Skia 全家;
  • 桥层:九场景分发、参数收敛、PNG 回编与错误通道(手写 MiniJson,零额外运行时依赖);
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、场景与参数、Base64 解码显示、notes 诊断;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 二进制型库的适配答案是消费社区制品,而不是重造二进制:skiko 的价值载体是 Skia 静态库,"把上游 Kotlin 源码编进 ohos"这条路天然不通;识别"供体已存在"并明确与供体仓库的分工(本仓不重复发布任何 ohos 二进制),比改造上游构建体系更快也更可复现;
  2. 工具链版本由供体元数据反推 :klib .module 的 stdlib 约束就是应选的 Kotlin 版本;"本机缓存最新"的配对会踩进发行版残缺的坑,报出毫无指向性的 ClassNotFound;
  3. 真机闭环是序列化正确性的唯一验证 :编译期一切正常的手写 Writer 仍可能带着逗号 bug 出厂;把握手(info)做成启动必经路径,让"桥活着"在页面顶部可见,问题从用户反馈降级为自检红灯。

9.4 适配成果

当前适配已完成:

  • 上游 master(8f09bee48,Kotlin 2.4.20 / Skia m155)源码零改动的 OpenHarmony 验证仓(根构建 cmp_qrose 模式,唯一改动是根配置与 example/ 新增文件);
  • 社区 klib 供体的双 ABI Kotlin/Native 落地(libohosskiko.so arm64 ≈ 21.8 MB / x86_64 ≈ 21.4 MB,Skia 静态内嵌,C ABI 符号验证);
  • 1 个 C ABI 符号 + 手写 MiniJson 的九场景渲染桥(576 + 235 行 Kotlin,全部在 example/);
  • C++ N-API 包装层(libskiko_napi.so,DisposeString 纪律 + hilog 打点);
  • DevEco Stage ArkUI 单页验收页面(八场景 chip、参数联动、notes 诊断、Base64 像素显示);
  • 真机安装运行验证(握手 + 基础图形场景效果图留档,其余场景按验收清单推进);
  • AtomGit 项目文档、OpenHarmony README(中英双份,CN 版 19 节)和本地效果图。

适配过程 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。

参考文档

相关推荐
2501_944525761 小时前
开源鸿蒙平台 KMP 三方库「KMPalette」适配全流程
华为·开源·harmonyos
OH_TPC1 小时前
【鸿蒙优选三方库】@ohos/ijkplayer:B 站同源播放器,啥格式都能播
华为·harmonyos·鸿蒙
传奇开心果编程1 小时前
组合优先开发范式:从汉字造字原理到声明式UI组件组合思想
android·flutter·ios·swiftui·移动开发·harmonyos·android jetpack
网络豆2 小时前
开源鸿蒙平台 CMP 三方库「kmp-onboarding」适配全流程
华为·开源·harmonyos
miofly2 小时前
GitHub 日榜趋势速报 | 2026-10-28
开源·github
孙琦Ray2 小时前
Nature子刊发布MyGPT开源平台:本地检索防泄密
开源·大模型幻觉·数据隐私·检索增强生成·mygpt
SelectDB技术团队2 小时前
ClickHouse SQL 好学吗?从 MySQL 迁移 Doris 的完整兼容验证
人工智能·sql·mysql·clickhouse·开源·apache doris·增量计算
网络豆2 小时前
开源鸿蒙平台 CMP 三方库「dotsindicator」适配全流程
华为·开源·harmonyos
resh_people2 小时前
开源鸿蒙平台 CMP 三方库「kmp-wheel-picker」适配全流程
华为·开源·harmonyos