本文记录 capsule 接入 OpenHarmony 的完整过程,覆盖平滑圆角形状库盘点、Compose 鸿蒙制品线选型、真实几何引擎编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

capsule 是基于 Compose Multiplatform 的平滑圆角形状库:ContinuousRoundedRectangle / ContinuousCapsule 用 G2 曲率连续的"贝塞尔 + 圆弧"混合转角替换 RoundedCornerShape 的"直线接圆弧"硬转角,G2ContinuityProfile 四参数调节转角曲线族,concentricInset 做同曲率中心内缩,全部形状实现 Interpolatable 可插值。本次适配与社区早期路线(qrose 的"composeMain 隔离、内核提取")有本质区别:社区 Maven 上的 HarmonyOS Compose 制品线(1.12.0-0.1.0-07,runtime / foundation / ui 全部带 ohosArm64 / ohosX64 klib 变体)已经就位,因此不需要隔离任何源码——上游 commonMain 全部 16 个文件(形状、连续性层级、Double 精度 PathSegment 几何模型、toPath / toSvg)一个字没改,原样编译成 OpenHarmony KLIB,一个曲率连续的完整几何引擎运行在真机上,ArkUI Canvas 页面作为验收宿主。

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

上游仓库: Kyant0/Capsule

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

capsule 为 Compose 应用提供"圆角"这一件事的完整答案:ContinuousRoundedRectangle(16.dp) 一行替换 RoundedCornerShape,转角从 G1(切线连续、曲率跳变)升到 G2(曲率连续);ContinuousCapsule 是平滑胶囊;G2ContinuityProfile(extendedFraction, arcFraction, bezierCurvatureScale, arcCurvatureScale) 把转角曲线族暴露成四个可调参数,预置 RoundedRectangle / Capsule / G1Equivalent 三档;concentricInset(padding) 让内缩后的内容保持同曲率中心(角半径随内缩减小、钳到 0);所有形状实现 Interpolatable,lerp 手工插值或交给 Compose Styles API 动画。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套形状能力,但上游构建(Kotlin 2.4.20 / Gradle 9.8.0 / AGP 9.4.1 / Compose 1.12.1)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS 画几个圆角矩形,页面可以显示图形,却无法证明公共几何引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证形状引擎、Compose 类型边界、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.4.20 / Gradle 9.8.0 / AGP 9.4.1(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建。
依赖矩阵库依赖 compose.runtime / compose.foundation / compose.ui 三件套,上游版本 1.12.1,鸿蒙侧必须有带 ohos klib 变体的替代线。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,更不能持有 Compose Shape / Outline,需要 C ABI、C++ N-API 和 JSON。
渲染语义跨不了桥CornerBasedShape / CornerSize / Dp / Density 是 Compose 概念,OHOS 上没有组合宿主(无 ComposeUIViewController 等价物),Shape 编译得过但渲染不了。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:完整形状库的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。几何计算完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和把 PathSegment 模型画到 Canvas 上。

1.2 库提供的能力

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

  • 形状(shape):Continuity.createRoundedRectanglePathSegments(w, h, tl, tr, br, bl) 按连续性(G0 直角 / G1 圆弧 / G2 平滑)产出 Double 精度路径段序列,支持统一半径、百分比半径(50% 即胶囊)与四角独立覆盖;
  • 胶囊(capsule):AdvancedContinuity.createCapsulePathSegments(w, h) 走真实的横 / 竖 / 圆三分支捷径;G0 / G1 回落为全半径圆角矩形,对应上游 Continuity.createCapsuleOutline 的默认实现;
  • 插值(lerp):Continuity.lerp(from, to, fraction) 真实执行——G1→G2 的中间态是 G2ContinuityProfile 逐参数插值出的 G2Continuity;
  • 同心(concentric):按上游 ConcentricCornerSize.toPx 语义(角半径随内缩减小、钳 0,内容盒四边内缩)逐圈重应用;
  • 版本握手:capsuleVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
op / kind回显本次操作与形状类别(rounded-rect / capsule)。
radii / profile生效的四角半径;g2 时回显 profile 四参数。
segments路径段数组(move / line / arc / cubic / circle 五种编码,Double 坐标)。
svg桥内自建的 SVG path 字符串(诊断与文档用)。
count路径段数量。

引擎侧完整能力(四类 CornerBasedShape 子类、Interpolatable 插值、LayoutDirection 镜像、concentric / lerp 装饰器)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 21 项 jvmTest 交叉验证用例(上游 LerpTest 12 项 + 新增纯几何 CapsuleGeometryTest 9 项),证明 ohos .so 里执行的几何逻辑没有漂移。

1.3 实现适配

维度要求
代码复用:capsule 全部 16 个 commonMain 文件原样编译,不提取、不重写、不隔离;库侧零新增源文件(全仓源码级变动仅两处:LerpTest 从 commonTest 移到 jvmTest,新增纯几何测试 CapsuleGeometryTest)。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 android / macos / ios / js / wasmJs 目标与 dokka / vanniktech 发布段随替换剥离;5 个上游构建脚本封存为 *.upstream。
桥接稳定使用 2 个 C ABI 和 UTF-8 扁平 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律jvm 交叉验证线用上游声明原版本 Compose 1.12.1(Maven Central),ohos 线用社区 1.12.0-0.1.0-07——按目标分线、同代换线(换线不换版)。
UI 完整四页签覆盖全部桥接入口:形状 playground、G0/G1/G2 对比、连续性插值、同心多圈,外加版本握手行。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。OHOS 上没有组合宿主,@Composable 编译得过但渲染不了;capsule 恰好零 @Composable、零 expect/actual,Compose 只出现在 Shape / Outline 类型边界上——几何引擎过桥,渲染职责交给 ArkUI Canvas,与上游"Compose 拿 Outline 画 Canvas"的分工一一对应。

二、实现路线图

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

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
capsule/                               上游形状库(进 ohos 构建,jvm + ohosArm64 + ohosX64,16 文件零改动)
capsule/src/commonTest/                新增:CapsuleGeometryTest(9 项纯几何交叉验证)
capsule/src/jvmTest/                   上游 LerpTest 移入(Compose 线交叉验证,12 项)
example/nativeApp/                     Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/                    DevEco Stage、CMake N-API 和 ArkTS 四页签页面
example/BRIDGE_CONTRACT.md             桥接契约(Kotlin 桥与 ArkTS 页共同遵守的协议)
README.OpenHarmony.md / _CN.md         鸿蒙适配说明(中英双份)
README.upstream.md                     上游原文保存
*.upstream                             上游 5 个构建脚本封存(settings / root build / gradle.properties /
                                      libs.versions.toml / capsule build),不参与构建

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :capsule 与 :example:nativeApp,rootProject.name = "ohos_capsule"。上游的 dokka / vanniktech 发布插件、GitHub Packages 发布段、Android / apple / js / wasmJs 目标全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21(org.gradle.java.home 指向 Eclipse Adoptium jdk-21.0.12.101-hotspot)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle9.6.1(上游 9.8.0 降级;2.4.20 线已验证组合,见 2.2)
Compose(ohos 线)org.jetbrains.compose.*:1.12.0-0.1.0-07(runtime / foundation / ui,ohosArm64 / ohosX64 klib)
Compose(jvm 线)1.12.1(Maven Central,上游 libs.versions.toml.upstream 声明的原版本)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.capsule
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点
分层文件依赖形态处置
纯数学MathUtils.kt零依赖整体编译
几何代数core/Point.kt、core/CubicBezier.kt仅 @Immutable/@Stable 注解整体编译
路径模型path/PathSegment.kt、PathSegments.kt、PathSegmentsBuilder.kt自研 Double 精度段模型;toPath() 触碰 Compose Path整体编译
连续性策略continuities/G0/G1/G2/G2ContinuityProfileG2 数学全在 Double 域;G1 有一条 Outline.Rounded 捷径整体编译
形状口径Continuity.kt、AdvancedContinuity.kt接口含 Size/Outline 方法整体编译
Compose 集成Continuous/Absolute/Concentric/LerpContinuousRoundedRectangle.ktCornerBasedShape / CornerSize / Dp / Density 深度绑定整体编译(ohos 线提供全部类型)

expect / actual 声明全库 0 处;@Composable 函数全库 0 个。几何计算全部在 Double 域完成,Compose 类型只出现在接口末端的 Outline / Shape 层——这就是"整库零手术"成立的根本原因:挡住 qrose 的是"ohos 上没有 Compose 制品",本次适配首先验证这个障碍已经被社区 Compose ohos 线(1.12.0-0.1.0-07)消除。qrose 需要 composeMain 目录隔离把 Painter 挪出不编译;capsule 什么都不用挪。

全仓源码级变动只有两处:其一是上游 LerpTest.kt 从 commonTest 移到 src/jvmTest——它测的是 Shape 层插值(Density / dp / RoundedCornerShape),属于 jvm 交叉验证靶的职责;其二是新增 commonTest/CapsuleGeometryTest.kt(9 项纯几何用例),见 6.2。

1.4 页面边界
标题区          CAPSULE · OPENHARMONY / 版本握手行(KMP 平滑圆角形状库 · Kotlin/Native ohosArm64/ohosX64 · v1.2.0-ohos.1)
Tab 切换        形状 / 对比 / 插值 / 同心
形状页          宽 / 高 / 圆角百分比 slider、G0/G1/G2 单选、profile 预置芯片(Custom 展开四参数 slider)、
                填充 / 描边开关、Canvas 重绘与信息行(count / radii / profile)
对比页          共享圆角百分比 slider + G0 直角 / G1 圆弧 / G2 平滑三联画(200×140 定尺寸)
插值页          G1 → G2 fraction slider(0–1 步进 0.01),resolved 连续性与 fraction 信息行
同心页          rings(1–6)/ inset(2–32)/ radiusPercent(10–50)三 slider,单 Canvas 多圈色阶绘制
关于区          版本握手、库简介、仓库与社区链接

页面启动时先调 capsuleVersion() 做握手;四个页签的每个参数变化都实时发起 capsuleDispatch 调用重算几何。

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

2.1 制品线验证(本适配的成败手)

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

org.jetbrains.compose.runtime:runtime:1.12.0-0.1.0-07
  variants: ohosArm64Api-published / ohosX64Api-published
org.jetbrains.compose.foundation:foundation:1.12.0-0.1.0-07
  variants: 同上
org.jetbrains.compose.ui:ui:1.12.0-0.1.0-07
  variants: 同上

三个关键结论:其一,社区线版本主体 1.12.0 与上游声明的 1.12.1 同代相邻(上游是它的 patch 跟进版),API 面一致,源码级兼容成本为零;其二,该线不发布 jvm 消费所需的 desktop / jvm 子制品,所以 jvm 交叉验证目标不能从这条线解析——必须分线:ohosMain 走社区线、jvmMain 走 Maven Central 的上游原版本 1.12.1,这就是 krogress / bubbles-compose / eungabi / FlowTab-CMP 已经用过的"按目标分线"写法;其三,androidx.annotation.FloatRange 等 Android 生态注解随 ui klib 传递可用,上游源码的 import 一行不用改。

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

上游 capsule 本来就是 Kotlin 2.4.20 构建,鸿蒙化 Kotlin 钉同代的 2.4.20-0.1.0-07,源码级兼容成本为零,也不需要像 qrose(2.2.21 旧线)那样把 Gradle 降到 8.14.1。Gradle 方面上游 wrapper 是 9.8.0,本机实测该发行版下载超时不可达,改钉 9.6.1——这是与 2.4.20 鸿蒙线共同验证过的组合(KStore / krogress 同款),gradle/wrapper/gradle-wrapper.properties 一行改动即完成。

另一个必须写进 gradle.properties 的网络事实:eazytec Nexus 是懒加载代理,首次请求一个它没缓存过的制品时,回源时间可能超过 Gradle 默认的 30 秒 socket 超时;超时还会被 Gradle 缓存成"制品缺失",后续重试秒失败。处置是双管齐下:

org.gradle.internal.http.socketTimeout=180000
org.gradle.internal.http.connectionTimeout=180000

加超时余量;万一已经缓存了缺失结论,用 --refresh-dependencies 强制重查一次即可恢复。

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_capsule"

include(":capsule")
include(":example:nativeApp")
2.3 编译真实形状库

capsule/build.gradle.kts 收敛为纯 KMP 模块:

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

kotlin {
    explicitApi()

    applyDefaultHierarchyTemplate()

    jvm {
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_11)
        }
    }

    ohosArm64()    // the adaptation
    ohosX64()

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

        ohosMain.dependencies {
            api(libs.compose.runtime)          // 1.12.0-0.1.0-07
            api(libs.compose.foundation)
            api(libs.compose.ui)
        }

        jvmMain.dependencies {
            api(libs.compose.runtime.jvm)      // 1.12.1(上游原版本,Maven Central)
            api(libs.compose.foundation.jvm)
            api(libs.compose.ui.jvm)
        }
    }
}

两个 capsule 特有的差异点:其一,Compose 依赖必须按目标线声明(ohosMain / jvmMain 各自 api),不能写在 commonMain——同坐标两版本跨源集会冲突,这正是 2.1 结论二的落地;其二,不需要 compose compiler 插件——capsule 全库零 @Composable,不存在需要编译器变换的内容(krogress 的指示器库是 @Composable 重度用户,两个模块都要挂插件;capsule 的库模块和桥模块都可以省)。上游的 explicitApi() 保留,公开 API 本来就是显式标记的。实测:

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

上游 16 个 commonMain 文件一个没动,在两条制品线上各编译了一遍。

2.4 测试依赖的处置:LerpTest 触碰 Compose Shape 层

上游 LerpTest 断言的是 ContinuousRoundedRectangle.lerp 折算像素后的值(Density(1f) / CornerSize.toPx / RoundedCornerShape),这些类型在 ohos 线上同样存在,但它是行为验证而非几何验证,归属 jvm 交叉验证靶更干净——移到 src/jvmTest(文件内容零改动)。commonTest 新增纯几何的 CapsuleGeometryTest(只依赖 kotlin.test 与库自身的 PathSegment 模型,jvm / ohos 都能编)。ohos 测试任务照旧全局禁用:

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

第 3 阶段:桥协议建模

3.1 JSON 契约
com.kyant.capsule/**(上游形状库 + PathSegment 模型,原样编译)
        ↓ Continuity.createRoundedRectanglePathSegments / createCapsulePathSegments / lerp
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API capsuleVersion() / capsuleDispatch()
        ↓
ArkUI Index.ets(四页签 + Canvas)

请求与响应:

请求 : {"op":"shape","width":240,"height":160,"radiusPercent":25,"continuity":"g2","profile":"rounded"}
       {"op":"capsule","width":280,"height":96,"continuity":"g2"}
       {"op":"lerp","lerpFrom":"g1","lerpTo":"g2","lerpFraction":0.5,...}
       {"op":"concentric","width":280,"height":200,"rings":4,"inset":14,...}
响应 : {"ok":true,"op":"shape","kind":"rounded-rect","width":...,"height":...,
        "continuity":"g2","radii":{...},"profile":{...},"count":16,
        "segments":[[0,0,72],[3,...],[2,...],...],"svg":"M0 72 C... Z"}
       {"ok":false,"op":"...","error":"..."}
3.2 载荷类型为什么是 segments 数组

上游的渲染边界是 PathSegments.toPath() → Outline.Generic(path) → Compose Canvas;ohos 侧没有这个宿主,桥就把"最后一公里"的原料直接送出去——PathSegment 的四种子类型(Line / Arc / Circle / Cubic)编码成紧凑数组:

编码含义ArkTS Canvas 对应
[0, x, y]moveToctx.moveTo(x, y)
[1, x, y]lineToctx.lineTo(x, y)
[2, cx, cy, r, a0, sweep]圆弧(弧度制)ctx.arc(cx, cy, r, a0, a0+sweep, sweep<0)
[3, x1,y1, x2,y2, x3,y3]三次贝塞尔ctx.bezierCurveTo(...)
[4, cx, cy, r]整圆ctx.arc(cx, cy, r, 0, PI*2)

三条设计纪律:其一,弧度制与 PathSegment.Arc 的存储形式一致,ArkTS arc() 恰好也吃弧度,零换算;其二,坐标保留 Double 精度(序列化时裁到 4 位小数防科学计数法泄漏),缩放由 ArkTS 侧 ctx.scale 统一处理;其三,SVG 字符串在桥内自建而不是调用上游 toSvg()——那个实现借道 toPath().getBounds()(Compose Path),桥的运行路径刻意不触碰 Compose 图形栈,几何序列化与 Compose 解耦。

同心 op 的每圈 segments 已经加上 (left, top) 偏移,ArkTS 单画布整体绘制即可;每圈半径按上游 ConcentricCornerSize.toPx 语义(radius - inset 钳 0)在 Kotlin 侧算好回传。

3.3 错误通道

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

@CName("CapsuleDispatch")
fun capsuleDispatch(request: String): String {
    var op = "shape"
    return try {
        val json = MiniJson.parse(request)
        op = json.str("op").ifBlank { "shape" }
        when (op) { "shape" -> …; "capsule" -> …; "lerp" -> …; "concentric" -> …
            else -> err(op, "unsupported op …") }
    } catch (t: Throwable) {
        err(op, t.message ?: t.toString())
    }
}

非法 JSON、缺字段、不支持的 op、半径越界(全部参数 coerceIn 限幅 + 默认值兜底),全部走同一个 ok:false 通道,ArkTS 侧统一渲染到信息行。

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

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

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

4.2 Kotlin/Native 导出函数
@CName("CapsuleVersion")
fun capsuleVersion(): String = "1.2.0-ohos.1"  // 版本握手

@CName("CapsuleDispatch")
fun capsuleDispatch(request: String): String   // shape / capsule / lerp / concentric 一口进
C ABI 符号ArkTS 方法覆盖内核
CapsuleDispatchcapsuleDispatch(req)createRoundedRectanglePathSegments + 胶囊捷径 + Continuity.lerp + concentric 语义
CapsuleVersioncapsuleVersion()fork 版本握手(真机版本行的数据来源)

llvm-nm -D 验证(双 ABI 均为全局 T 符号):

arm64 : 000000000109d80c T CapsuleDispatch   000000000109d990 T CapsuleVersion
x64   : 000000000106e2e0 T CapsuleDispatch   000000000106e460 T CapsuleVersion
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"capsuleDispatch", nullptr, NapiCapsuleDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"capsuleVersion", nullptr, NapiCapsuleVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohoscapsule_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohoscapsule_api.h 直接采用 KN 构建生成的原版头(5,106 字节,linkDebugShared 产物拷入 cpp/include/),napi_init.cpp 链接期绑定两个业务符号。

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

add_library(capsule_napi SHARED napi_init.cpp)
target_link_libraries(capsule_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohoscapsule)

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

export const capsuleDispatch: (request: string) => string;
export const capsuleVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 版本握手和触发重算
aboutToAppear(): void {
  this.version = capsuleVersion();   // 版本握手:来自真实 Native 调用
}

private refreshShape(): void {
  const req: ShapeRequest = {
    op: 'shape', width: this.width, height: this.height,
    radiusPercent: this.radiusPercent, continuity: this.continuity,
    profile: this.profile, …
  };
  const res = JSON.parse(capsuleDispatch(JSON.stringify(req))) as SegmentsResp;
  this.apply(res);                   // @State 刷新 Canvas + 信息行
}

每个 slider / 单选 / 开关的 onChange 都走 refreshXxx(),一次交互就是一次真机 Native 几何重算;Canvas 的 onReady 触发各页签首次绘制。

5.2 页面交互

四页签自上而下:形状页是完整 playground(尺寸 / 圆角 / 连续性 / profile 四参数 / 填充描边开关,信息行回显 count、radii 与 profile 数值);对比页共享一个圆角百分比,G0 直角 / G1 圆弧 / G2 平滑三联画并排(200×140 定尺寸,肉眼可直接看出 G2 转角没有曲率突变的光泽感);插值页拖动 fraction 从 G1 渐变到 G2,信息行显示 resolved 连续性;同心页三 slider 控制圈数 / 内缩 / 半径,多圈以 #7EC8FF → #2C5F8A 蓝阶着色叠加在单画布上。

5.3 绘制映射和错误边界

统一绘制辅助按响应的 width / height 等比缩放居中(四周留 10% 边距),描边宽度按缩放倒数补偿为恒定视觉宽度;五类段编码逐一映射到 moveTo / lineTo / arc / bezierCurveTo,收尾 closePath + fill + stroke。ok:false 时错误原文进信息行红字;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进信息行。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── BRIDGE_CONTRACT.md                       桥接契约(两侧共同遵守的字段 / 编码 / 错误通道)
├── nativeApp/
│   ├── build.gradle.kts                     双 ABI sharedLib(baseName=ohoscapsule)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       └── Bridge.kt                        @CName × 2 + MiniJson + segments/SVG 序列化
└── harmonyApp/
    ├── build-profile.json5                  signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                   bundleName org.terminator.ohos.capsule、versionName 1.2.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/libohoscapsule_api.h   KN 生成的原版导出头
        ├── src/main/cpp/types/libcapsule_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets     ArkUI 四页签验收页面
6.2 构建、签名和安装
.\gradlew.bat :capsule:jvmTest                                # 21 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,勾选自动签名后由 IDE 回填带 material 的完整配置)。之后直接 Run 或命令行:

cd example\harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

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

四、完整代码对照

4.1 整体架构

com.kyant.capsule/**(上游形状库原样编译)
    │ Continuity 层级(G0/G1/G2)+ Double 精度 PathSegment 模型
Bridge.kt + MiniJson
    │ CapsuleDispatch / CapsuleVersion
libcapsule_napi.so
    │ capsuleDispatch / capsuleVersion
Index.ets
    │ JSON parse + segments 数组 → Canvas 路径
    ▼
ArkUI 真机页面(四页签)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_capsule
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-07 + 双 Compose 线钉版(ohos 1.12.0-0.1.0-07 / jvm 1.12.1)
build.gradle.kts(根)版本 1.2.0-ohos.1、全局禁用 Ohos*Test* 任务
capsule/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,Compose 按目标线声明、零 compose compiler 插件
capsule/src/commonTest/.../CapsuleGeometryTest.kt新增纯几何交叉验证(9 项)
capsule/src/jvmTest/.../LerpTest.kt上游测试移入 jvm 线(内容零改动,12 项)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、四 ops、segments/SVG 序列化、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 四页签验收页面(Canvas 段绘制)
README.OpenHarmony_CN.md鸿蒙适配主文档(15 章)
example/BRIDGE_CONTRACT.md桥接契约(两侧的字段级协议)
*.upstream(5 个)上游构建脚本封存,不参与构建

4.3 关键 API 对照

层次API作用
引擎Continuity.createRoundedRectanglePathSegments(w,h,tl,tr,br,bl)按连续性产出 Double 精度路径段(shape op 内核)
引擎AdvancedContinuity.createCapsulePathSegments(w,h)胶囊横 / 竖 / 圆三分支捷径(capsule op 内核)
引擎Continuity.lerp(stop, fraction)连续性插值,G1↔G2 中间态为 profile 插值(lerp op 内核)
引擎ConcentricCornerSize.toPx(语义)同心内缩:半径减 padding 钳 0(concentric op 语义来源)
引擎G2ContinuityProfile(四参数 + 三预置)转角曲线族调节(custom profile 透传)
桥CapsuleDispatch / CapsuleVersionC ABI JSON 导出(四 ops 一口进)
N-APIcapsuleDispatch(req) / capsuleVersion()向 ArkTS 暴露能力
ArkTSdrawSegments(ctx, segments, …)段数组 → Canvas 路径(等比缩放居中 + 描边补偿)

4.4 ArkTS 与 Kotlin 的边界

const res = JSON.parse(capsuleDispatch(JSON.stringify({
  op: 'lerp', lerpFrom: 'g1', lerpTo: 'g2', lerpFraction: 0.5,
  width: 240, height: 160, radiusPercent: 30
})));
// res.ok === true;res.lerp.resolved === 'g2';res.segments 即插值连续性的路径段
val from = G1Continuity
val to = G2Continuity()
val resolved = from.lerp(to, 0.5)          // G2Continuity(profile = lerp(G1Equivalent, RoundedRectangle, 0.5))
val segments = resolved.createRoundedRectanglePathSegments(240.0, 160.0, r, r, r, r)

两者之间只传输扁平 JSON 字符串与段数组,不传输 Kotlin 对象;每一笔圆角都是 G2Continuity 的真实数学在设备上算出来的。

五、关键决策说明

决策 1:真编译整库而不是 qrose 式内核隔离

qrose 抽内核隔离 Painter,是因为当时 ohos 上没有 Compose 制品,只能把纯 Kotlin 编码器单独编译。本次适配先验证制品线(runtime / foundation / ui 全部存在 ohos klib 变体),障碍已消失——再隔离等于把 ContinuousRoundedRectangle 一族从库里切出去,反而制造两套几何。真编译让 G2 求解、胶囊捷径、插值、同心语义全部是上游原件,且库侧一行新增源码都没有(KStore 尚需 8 行 dispatcher actual,capsule 连这个都不需要——它零 expect/actual)。

决策 2:双 Compose 制品线,换线不换版

社区 ohos 线 1.12.0-0.1.0-07 不发布 jvm 子制品,jvm 交叉验证只能走 Maven Central;上游声明的 1.12.1 原样保留给 jvm,ohos 用同代社区线。两线 API 面一致(1.12.0 与 1.12.1 相邻 patch),同一份 commonMain 在两条线上各编译一遍,本身就是"API 没漂"的证明。依赖声明必须放在 ohosMain / jvmMain 源集而不是 commonMain——同坐标两版本跨源集冲突。

决策 3:根构建替换(cmp_qrose 模式)+ *.upstream 封存

上游根构建(dokka / vanniktech 发布 / GitHub Packages / AGP classpath / 六平台目标)对鸿蒙没有保留价值,整个替换为极简 ohos 构建。与 KStore 一致,但本仓把被替换的 5 个上游构建脚本显式封存为 *.upstream(settings / root build / gradle.properties / libs.versions.toml / capsule build)留在树里——审阅者能逐行对照"改了什么、没改什么",上游升级时也有三方合并的基线。

决策 4:几何 JSON 过桥,而不是位图光栅

qrose 的 Painter 产出的是成品图像,所以桥走"Kotlin 软光栅化 + PNG + ImageKit";capsule 的产出是矢量几何,位图化反而有损(分辨率钉死、放大发虚)。桥直接送 PathSegment 段数组,ArkTS Canvas 矢量重绘——与上游分工严格对应:上游是"Compose 拿 Outline 画",鸿蒙是"ArkTS 拿段数组画",库的数学始终是唯一几何源。SVG 字符串在桥内自建(不经 toSvg() 的 Compose Path 依赖),仅作诊断输出。

决策 5:jvmTest 交叉验证 + LerpTest 搬家不改动

LerpTest 是上游的行为测试(Shape 层插值折算像素),移到 jvmTest 后内容零改动地跑在上游原版 Compose 1.12.1 上;commonTest 换成纯几何的 CapsuleGeometryTest(段数 / 链式连续 / 有界性 / 胶囊路由 / 插值 profile / 确定性),jvm 与 ohos 共同编译、jvm 执行。测试文件的"只搬家不改写"保住了交叉验证的说服力。

六、测试与验证

6.1 测试环境

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

6.2 JVM 交叉验证

.\gradlew.bat :capsule:jvmTest

21 项用例全部通过:

测试类用例数钉住什么
LerpTest(上游原件,移入 jvmTest)12Shape 层插值:Dp / 百分比圆角插值、continuity 插值、RectangleShape / 未知 Shape 边界、concentric 后插值、Interpolatable 双向
CapsuleGeometryTest(新增,纯几何)9G0 全直线段、G1 四段四分之一圆弧、G2 默认 16 段且链式连续 + 有界、四角独立半径、G1Equivalent 扫满弧、胶囊三分支路由与整圆、全半径胶囊捷径、Continuity.lerp 混合 profile、确定性

LerpTest 在上游 Compose 1.12.1 上照跑全绿、CapsuleGeometryTest 与 ohos KLIB 同源编译——"换线不换版"在形状库上的直接证据。

6.3 HAP 验证

:capsule:compileKotlinJvm / compileKotlinOhosArm64 / compileKotlinOhosX64: BUILD SUCCESSFUL
:capsule:jvmTest: 21 项全绿
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: CapsuleDispatch / CapsuleVersion / libohoscapsule_symbols 均为全局 T 符号(双 ABI)
BuildNativeWithCmake: finished(双 ABI 编出 libcapsule_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

产物尺寸(debug 未 strip,含 Kotlin/Native 运行时与两条 Compose 线链接进来的 klib):libohoscapsule.so arm64 28,728,368 字节、x86_64 27,508,032 字节;libc++_shared.so 分别 1,850,184 / 1,731,432 字节;另产出 capsule-jvm-1.2.0-ohos.1.jar。

6.4 功能用例

  1. 启动后标题区出现 v1.2.0-ohos.1(来自 capsuleVersion() 真实 Native 调用,非静态文字);
  2. 形状页拖动圆角百分比,Canvas 实时重绘,信息行 count 随 G2 的 16 段结构稳定回显;
  3. 切到 G1,段数变为 8(4 线 + 4 弧),转角变为标准圆弧;切到 G0 只剩直线段;
  4. Custom profile 展开四参数 slider,extFrac 拉大可见转角过渡沿边缘延伸,信息行回显生效 profile 数值;
  5. 对比页三联画同参数并排:G0 直角、G1 圆弧、G2 平滑肉眼可辨,G2 转角无曲率突变的高光过渡连续;
  6. 插值页 fraction 从 0 拖到 1,形状从 G1 圆弧连续渐变为 G2 平滑,resolved 恒为 g2(中间态是 profile 插值);
  7. 同心页增减圈数 / 内缩,多圈蓝阶叠加,内圈半径按 radius - inset 递减钳 0,圆角耗尽时退化为直角内圈;
  8. 填充 / 描边开关即时生效,描边宽度在任意缩放下视觉恒定。

6.5 验证结论

形状引擎、Compose 类型边界、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的几何会话:版本握手、三种连续性的形状生成、胶囊捷径、连续性插值与同心多圈(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/capsule.git
cd capsule
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
.\gradlew.bat :capsule:compileKotlinOhosArm64 :capsule:compileKotlinOhosX64   # klib
.\gradlew.bat :capsule:jvmTest                                               # 21 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example\harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry\build\default\outputs\default\entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.capsule -m entry
hdc shell hilog | grep Capsule

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :capsule 真正编译成 ohosArm64 / ohosX64 KLIB 并链出动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. eazytec Nexus 是懒加载代理:首次请求未缓存制品会超时,且超时会被 Gradle 缓存成"缺失"导致后续秒败——socketTimeout / connectionTimeout 提到 180 秒,已缓存缺失时用 --refresh-dependencies 强制重查;
  4. Gradle wrapper 发行版下载本身也可能超时不可达:改钉本机已有且与鸿蒙 Kotlin 2.4.20 线共同验证过的 9.6.1(KStore / krogress 同款),一行 distributionUrl 完成;
  5. Compose 依赖不能写在 commonMain:ohos 线与 jvm 线同坐标不同版本,跨源集直接冲突——按 ohosMain / jvmMain 分线声明(krogress 先例);
  6. capsule 零 @Composable,库与桥都不需要 compose compiler 插件;有 @Composable 的库(如 krogress)必须挂——两类库的插件需求不能互相套用;
  7. ArkUI 没有 Switch 组件,开关是 Toggle({ type: ToggleType.Switch })——组件名按 ArkUI 手册写,Android / Flutter 的惯性命名过不了 CompileArkTS;
  8. build-profile.json5 的 signingConfigs 条目缺 material 字段会被 hvigor schema 拒绝——仓库内留空数组且 product 不引用签名,勾选自动签名后由 IDE 回填完整配置;
  9. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  10. DevEco 打开的是 example/harmonyApp 子目录而不是仓库根目录——根目录是 Gradle 工程,DevEco 会当错误工程类型处理;
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  12. 签名材料由 DevEco 自动写入 build-profile.json5,提交前用 git update-index --skip-worktree 保护,证书绝不进入 AtomGit。

8.2 已知问题

  • OHOS 无组合宿主:@Composable 与 Shape 渲染链路编译得过但跑不起来,Demo 由 ArkTS Canvas 重绘段模型承担渲染(这也是本批 CMP 库的共同边界);
  • 桥只送几何不送渲染:描边样式、渐变填充等视觉参数留在 ArkTS 侧,上游对应能力(Modifier.background(shape) 全家桶)不在桥的范围内;
  • MiniJson 只支持扁平 JSON(qrose / krogress 同款纪律),嵌套请求载荷按设计拒绝;
  • 验证产物是 debug so(约 27 MB,未 strip),release 链接与裁剪未做;
  • android / js / wasmJs / apple 目标剥离(源文件留在树里),发布、dokka 一并剥离,本 fork 不发 Maven Central;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.capsule 匹配的签名。

8.3 未来优化方向

  • release 模式链接与符号裁剪,把 .so 从 27 MB 级压到可用体积;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 把 toSvg() 的 Compose Path 依赖解掉后直接复用上游实现(当前桥内是自建序列化);
  • 在 CI 中加入 ohos 编译、llvm-nm 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)验收矩阵、四页签自动截图脚本与 G0/G1/G2 曲率数值断言。

九、总结

9.1 核心难点回顾

上游 capsule(全部 commonMain,16 文件)
    → 验证 Compose ohos 制品线(runtime / foundation / ui @ 1.12.0-0.1.0-07)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1)
    → 根构建替换(cmp_qrose 模式,5 个上游脚本封存 *.upstream)
    → ohosArm64 / ohosX64 KLIB,库侧零新增源码
    → C ABI(@CName × 2,四 ops)+ PathSegment 段数组载荷
    → C++ N-API(DisposeString 纪律)
    → ArkTS 段数组 → Canvas 矢量重绘
    → ArkUI 真机四页签验收(形状 / 对比 / 插值 / 同心)

9.2 封装层次

  • 引擎层:上游原版 Continuity 层级(G0/G1/G2)、G2ContinuityProfile 曲线族、Double 精度 PathSegment 模型;
  • 形状层:上游原版 ContinuousRoundedRectangle / Absolute… / ContinuousCapsule、concentric / lerp 装饰器(随编译进 .so,Compose 宿主就绪即可用);
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、请求组包、段数组到 Canvas 的矢量重绘与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 先验证生态制品线再定适配路线:Compose ohos 线补齐后,形状类库的正确答案就是"整库真编译 + 几何 JSON 过桥"——capsule 零 expect/actual、零 @Composable,适配面比 KStore(8 行 actual)还小;路线选择必须基于当天的制品可达性,而不是 qrose 时代的历史结论;
  2. 渲染职责跟着宿主走而不是跟着库走:上游的渲染边界在 Compose Canvas(Outline),鸿蒙的宿主是 ArkUI Canvas,桥就把"边界前的原料"(PathSegment)送过去——矢量过桥优于位图过桥,库的数学始终是唯一几何源;
  3. 交叉验证要"同源双编"才有说服力:同一份 commonMain 在上游线(Compose 1.12.1)与社区线(1.12.0-0.1.0-07)各编译一遍、上游测试文件只搬家不改写、21 项 jvmTest 全绿——三条合起来才是"换线不换版"的完整证据链。

9.4 适配成果

当前适配已完成:

  • capsule 1.2.0(上游由 Kyant 创建、xfqwdsj 维护,Apache-2.0)完整形状库的双 ABI Kotlin/Native 编译(上游 commonMain 16 文件零改动,库侧零新增源文件);
  • HarmonyOS Compose 制品线在形状库上的完整落地(runtime / foundation / ui 1.12.0-0.1.0-07,jvm 线保持上游 1.12.1);
  • 2 个 C ABI 符号(llvm-nm 双 ABI 验证全局 T 符号)和 C++ N-API 包装层;
  • DevEco Stage ArkUI 四页签验收页面(形状 playground、G0/G1/G2 对比、连续性插值、同心多圈、版本握手);
  • 21 项 JVM 交叉验证全绿(上游 LerpTest 12 + 新增纯几何 9)+ 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐