本文记录 Korner 接入 OpenHarmony 的完整过程,覆盖平滑圆角形状库盘点、Compose Shape 隔离、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、软件光栅化与 SVG pathData 双输出、ArkUI 真机页面、HAP 构建和真机验收。

Korner 是基于 Compose Multiplatform 的 iOS 风格平滑圆角 / 超椭圆(squircle)形状库:用「圆弧 + 两侧对称三次贝塞尔曲线」替代传统圆角矩形,AbsoluteSmoothCornerShape 与 RTL 感知的 SmoothCornerShape 支持每角独立的 radius 与 smoothing(0…100),几何推导源自 racra 的 smooth-corner-rect-android-compose。本次适配与 KStore 的"真编译整库"路线有本质区别:kotlinx 制品线在 ohos 上已经补齐,但 androidx.compose.ui / foundation 仍然没有 ohos 制品——Shape 类继承的 CornerBasedShape 根本无法编译。因此按社区已过审的 qrose 路线做「几何复用、Shape 冷藏」:把纯 Kotlin 的 SmoothCorner 几何与 CornerSmoothing 留在 commonMain 原样编译,新增 Korner.cornerPath() 公开入口逐行镜像上游 genericSmoothOutline 的指令序列,Compose Shape 类与 Demo UI 挪进不参与编译的 composeMain 冷藏;example/nativeApp 把轮廓段软件光栅化为 PNG 并同步输出 SVG pathData,一个带四角独立参数、RTL 镜像、性能基准的完整几何引擎运行在真机上,ArkUI 三 Tab 页面作为验收宿主。

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

上游仓库: dev778g-me/Korner

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

Korner 为 Compose Multiplatform 应用提供"矩形 ↔ 平滑圆角"这一件事的完整答案:AbsoluteSmoothCornerShape(cornerRadius, cornerSmoothing) 一行建形,四角 radius 与 smoothing 各自独立,CornerSmoothing 预设 Subtle / Balanced / Smooth / Continuous 百分制连续可调,SmoothCornerShape 在 LayoutDirection.Rtl 下自动镜像 start/end 角。核心算法在 SmoothCorner 一支笔算完:角度 α/β/θ、距离 a/b/c/d/e/k/l 的几何推导,产出每角「锚点 + 双控制点 + 圆弧段」的绘制参数,上游再由 genericSmoothOutline 拼成 Compose Path(moveTo / cubicTo / arcToRad 指令序列)。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套 squircle 能力,但上游构建(Kotlin 2.3.21 / Gradle 9.1.0 / Compose 1.11.0 / AGP 9.0.1)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮和滑条,却无法证明几何引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证几何算法、轮廓段模型、Native 动态库、C ABI、N-API、光栅化输出、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.3.21 / Gradle 9.1.0 / AGP 9.0.1(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建;鸿蒙线钉 Kotlin 2.2.21-1.0.0,必须降 wrapper 到 8.14.1。
Compose 依赖不可编译核心公开 API 继承 androidx.compose.foundation.shape.CornerBasedShape,依赖 Outline / Path / Size / RoundRect——compose 制品线没有 ohos 变体,这是与 KStore 类库最大的不同。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
渲染语义跨不了桥Shape 是 Compose 渲染管线的概念(createOutline 由框架回调),鸿蒙侧需要把"轮廓"翻译成 ArkTS 可消费的形式。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:几何算法的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层 + 双输出(PNG 光栅化 / SVG pathData)。几何语义完全由上游原版实现负责,ArkTS 只负责组装参数、调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

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

  • 统一四角(shape):一个 cornerRadius + 一个 smoothing 画出超椭圆,滑条实时重生成;
  • 四角独立:每角各自的 radius 与 smoothing(左上 48/100、右上 16/25、右下 32/75、左下 16/50 之类任意组合),同一张轮廓里四种曲率各自连续;
  • RTL 镜像:rtl: true 时 start/end 角物理互换,映射逐行取自上游 SmoothCornerShape.createOutline 的 RTL 分支;
  • 对比(compare):同半径下 smoothing=N% 与 0% 并排渲染——squircle 直线→圆弧连续过渡,经典圆角切点处曲率跳变;
  • 性能(perf):Korner.cornerPath 路径构建基准(100…5000 次迭代),返回 avg / p50 / min / max 微秒统计;
  • 双输出:每次 shape 调用同时返回 pngBase64(光栅化图,Image 组件直接显示)与 pathData(SVG 路径字符串,Path / Path2D / clip 原生消费)。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
kindshape / perf。
pngBase64 / width / height / bytes光栅化 PNG 与尺寸、体积。
pathData / segmentsSVG 路径字符串与轮廓段数(矩形捷径 5、圆角捷径 9、平滑 17)。
pathUs / rasterUs单次路径构建 / 光栅化+PNG 耗时。
pathAvgUs / pathP50Us / pathMinUs / pathMaxUs / iterationsperf 模式统计。

引擎侧完整能力(角度推导、半径夹取、插值退化为圆角、四角缓存 map)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 5 项 jvmTest 交叉验证用例钉住段数与捷径分支,证明 ohos .so 里执行的几何逻辑没有漂移。

1.3 实现适配

维度要求
代码复用SmoothCorner.kt 逐字节零改动;CornerSmoothing 从 Shape 文件抽出为独立纯 Kotlin 文件(唯一源码级微调:剥 @JvmInline);新增 Korner.kt 公开入口 + PathSegment 模型(上游模块内唯一新增源文件 ×2)。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);androidApp / desktopApp / iosApp / webApp 留盘不进构建。
桥接稳定使用 1 个 C ABI(KornerRender)和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
几何零漂移Korner.cornerPath 与上游 genericSmoothOutline 指令序列逐行一一对应(moveTo↔Move、cubicTo↔Cubic、arcToRad↔Arc),捷径分支同样对齐。
UI 完整三 Tab 覆盖全部桥接入口:自定义(统一/四角/RTL/色板/pathData)、对比、性能。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。Korner 上游的全部价值在 Shape 类型——而这正是 ohos 编不了的部分;ArkTS Demo 把桥暴露的几何闭环画出来,真实业务集成走 pathData 无损通道。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 compose 制品线缺失、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 8.14.1 降级
第 3 阶段:桥协议建模     ── PathSegment 轮廓段模型、shape/perf JSON 契约、RTL 映射、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、软件光栅化、SVG 序列化、C++ N-API 和内存释放
第 5 阶段:ArkUI 宿主     ── 三 Tab、滑条与开关、ShapeRequest 显式类、PixelMap 解码
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
shared/                               上游形状库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
shared/src/commonMain/                纯 Kotlin 几何:SmoothCorner.kt + CornerSmoothing.kt + Korner.kt
shared/src/composeMain/               上游冷藏:两个 Shape 类 + genericSmoothOutline + Demo UI(不编译)
example/nativeApp/                    Kotlin/Native 动态库(Bridge + Rasterizer + SvgPath + PngEncoder)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.upstream.md                    上游原文保存
androidApp/ desktopApp/ iosApp/ webApp/   上游四端 Demo,留盘不进 settings

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :shared 与 :example:nativeApp,rootProject.name = "ohos_korner"。上游的 AGP、Compose 编译器、vanniktech maven-publish 发布段全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(org.gradle.java.home 指向 Temurin 21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle8.14.1(上游 9.1.0 降级;2.2.21 旧线不支持 9.x,随社区标准钉死)
依赖commonMain 零第三方依赖(几何只吃 kotlin.math)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.korner
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点
文件依赖形态处置
SmoothCorner.kt纯 kotlin.math,零 UI原样保留 commonMain(逐字节零改动)
AbsoluteSmoothCornerShape.ktCornerBasedShape / Outline / Path / Dp,Compose 全家桶拆分:CornerSmoothing(纯 Kotlin)抽回 commonMain,其余挪 composeMain 冷藏
App.kt / BenchmarkScreen.kt / PerfBenchmarkScreen.ktMaterial3 Demo UI(三 Screen:Customize/Compare/Perf)挪 composeMain 冷藏
Greeting*.kt / Platform.kt + 各平台 actual模板遗留,仅 Demo 用挪 composeMain 冷藏(jvmMain 的 actual 必须一起挪,否则 jvm 编译挂)
composeResources/Compose 资源目录挪 composeMain 冷藏

shared 模块没有 expect/actual 几何声明,唯一的 expect 是 Demo 用的 getPlatform()——这是与 KStore(全库 1 个 expect 补 8 行 actual)相反的处境:KStore 缺的是平台实现,Korner 多的是 UI 包袱。上游核心类 SmoothCorner 是 internal、genericSmoothOutline 返回 Compose Outline,ohos 侧需要一个新的 public 入口把几何放出来,这就是 Korner.kt 的由来。

1.4 页面边界
标题区          korner · OpenHarmony / 副标题(CMP 平滑圆角库 · Kotlin/Native ohosArm64/ohosX64)
Tab 切换        自定义 / 对比 / 性能
自定义 Tab      尺寸 / 圆角半径 / 平滑度三滑条、四角独立开关(四组半径+平滑度滑条)、
                RTL 镜像开关、五套色板、生成按钮、PNG 展示、段数与耗时统计、pathData 文本
对比 Tab        同半径下滑条、左(平滑 N%)右(圆角 0%)并排双图、差异说明文字
性能 Tab        尺寸 / 半径 / 迭代次数滑条、运行基准按钮、统计文本、验证 PNG

页面启动时 aboutToAppear 自动触发一次生成,进 App 即见形状。

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

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

先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 compose 的 ohos 制品——结论是没有:org.jetbrains.compose.ui:ui 与 androidx.compose.foundation 均无 ohosArm64 / ohosX64 变体,社区 CMP 线停在 1.9.2,上游已经用 Compose 1.11 / Kotlin 2.3。这个结论决定了整个路线:Shape 类无法编译,但几何算法不需要 Compose——SmoothCorner 只吃 kotlin.math,genericSmoothOutline 的指令序列可以镜像到一个平台无关的 PathSegment 模型上。库模块 commonMain 零第三方依赖,反而不用像 KStore 那样核对 kotlinx 四件套的制品线。

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

上游 Kotlin 2.3.21 / Gradle 9.1.0 / AGP 9.0.1 与鸿蒙工具链无法共存。本库没有 kotlinx 依赖反推版本的压力,直接落回社区此前批准的钉法:HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 同款),该线只支持 Gradle 8.x,wrapper 从 9.1.0 降到 8.14.1(连同 gradle-daemon-jvm.properties 一起删除,foojay resolver 移出 settings)。

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

include(":shared")
include(":example:nativeApp")

gradle.properties 承载版本与工具链:

org.gradle.java.home=C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.12.101-hotspot

version=2.0.1-ohos.1

kotlin.native.ignoreDisabledTargets=true
kotlin.native.distribution.downloadFromMaven=true
kotlin.apple.deprecated.allowUsingOpenSourceCompiler=true
2.3 编译真实几何

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

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

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

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

    sourceSets {
        commonMain.dependencies {}              // 几何零依赖
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

上游模块里所有依赖 androidx.compose.* 的文件 git mv 进 src/composeMain/——不注册 source set,Gradle 直接忽略,源码完整保留等 CMP 工具链对齐后接回。实测:

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

几何算法文件一个没动,只有 CornerSmoothing 的抽取与 Korner.kt 是新的。

2.4 @JvmInline 的处置:ohos native 不认

上游 CornerSmoothing 是 @JvmInline value class。JVM 目标编译通过,compileKotlinOhosArm64 直接报:

CornerSmoothing.kt:10:2 Unresolved reference 'JvmInline'

鸿蒙线 native 目标不提供 kotlin.jvm.JvmInline。剥掉注解保留 value class 又过不了 JVM 目标(@JvmInline 是 common 值类硬要求),所以处置是普通类精确复刻值类语义:

public class CornerSmoothing(public val percent: Int) {
    init { require(percent in 0..100) { "Corner smoothing must be between 0 and 100" } }
    override fun equals(other: Any?): Boolean = other is CornerSmoothing && other.percent == percent
    override fun hashCode(): Int = percent
    override fun toString(): String = percent.toString()   // 值类 toString 本就委托底层值,
                                                           // 内部缓存 map 的 key 形态与上游一致
    public companion object {
        public val Subtle: CornerSmoothing = CornerSmoothing(25)
        public val Balanced: CornerSmoothing = CornerSmoothing(50)
        public val Smooth: CornerSmoothing = CornerSmoothing(75)
        public val Continuous: CornerSmoothing = CornerSmoothing(100)
    }
}

第 3 阶段:桥协议建模

3.1 轮廓段模型(几何接缝的核心)

genericSmoothOutline 往 Compose Path 上发四类指令:moveTo / lineTo / cubicTo / arcToRad。Korner.cornerPath 把同样的指令序列发给一个平台无关的密封接口:

public sealed interface PathSegment {
    public data class Move(public val x: Float, public val y: Float) : PathSegment
    public data class Line(public val x: Float, public val y: Float) : PathSegment
    public data class Cubic(public val c1x: Float, ..., public val y: Float) : PathSegment
    public data class Arc(public val left: Float, ..., public val sweepAngleRadians: Float) : PathSegment
    public data object Close : PathSegment
}

Arc 保留 Compose 语义:圆弧内切于 left/top/right/bottom,从 startAngleRadians(0 = 右,PI/2 = 下,y 轴向下)顺时针扫 sweepAngleRadians。cornerPath 的 else 分支对四个角依次发 Move/Cubic/Arc/Cubic(首角)与 Line/Cubic/Arc/Cubic(后三角)加 Close,连上游的 smoothCornersMap 角参数缓存都原样保留;三个捷径分支同样对齐——四角 radius 全 0 → 5 段折线(对应上游 Outline.Rectangle),四角 smoothing 全 0 → 9 段(Move + 4 弧 + 3 线 + Close,对应 Outline.Rounded),完整平滑 17 段。

3.2 JSON 契约
org.dev.korner/**(上游几何 + Korner 入口,原样编译)
        ↓ Korner.cornerPath -> List<PathSegment>
Bridge.kt(@CName × 1,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API kornerRender()
        ↓
ArkUI Index.ets(三 Tab)

请求与响应:

请求 : {"mode":"shape","width":300,"height":300,"cornerRadius":32,"smoothing":75,
        "rtl":false,"fg":"#111827","bg":"#F9FAFB","ss":3}
       {"mode":"shape",...,"topStartRadius":48,"topStartSmoothing":100,
        "topEndRadius":16,"topEndSmoothing":25, ...}          # 四角独立
       {"mode":"perf","iterations":500,...}                    # 基准
响应 : {"ok":true,"kind":"shape","width":300,"height":300,"segments":17,
        "bytes":12345,"pathData":"M 0 26.5 C ... Z","pathUs":12.3,"rasterUs":45.6,
        "pngBase64":"iVBORw0..."}
       {"ok":false,"error":"..."}

两个设计要点:其一,四角参数沿用上游 SmoothCornerShape 的 start/end 命名——rtl:false 时映射物理角(等价 AbsoluteSmoothCornerShape),rtl:true 时 start/end 物理互换(TL↔TR、BL↔BR),映射逐行取自上游 createOutline 的 RTL 分支,Demo 的 RTL 开关演示的正是 SmoothCornerShape 语义;其二,桥吃物理 px,Dp→px 由 ArkTS 侧 vp2px 负责,与 Compose 侧 density 解析口径对齐。

3.3 错误通道

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

@CName("KornerRender")
fun kornerRender(request: String): String {
    return try {
        /* mode 分发 */
    } catch (t: Throwable) {
        """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
    }
}

非法 JSON、参数越界(width 1…4096、smoothing 0…100 由桥 coerceIn 夹取)、半径超限(上游几何内部 min(cornerRadius, halfOfShortestSide) 夹取,与 Compose 行为一致),全部走同一个 ok:false 通道或安全夹取,ArkTS 侧统一渲染。

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

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

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

4.2 Kotlin/Native 导出函数与双输出
@CName("KornerRender")
fun kornerRender(request: String): String   // shape / perf 一口进
C ABI 符号ArkTS 方法覆盖内核
KornerRenderkornerRender(req)Korner.cornerPath 构造 + 光栅化 + SVG 序列化 + 基准计时

shape 模式一次调用产出两路输出:Rasterizer 把轮廓段展平成多边形、even-odd 扫描线填充、ss 倍(1…4)超采样下采样抗锯齿,PngEncoder(zlib stored 未压缩块 + adler32 + crc32,零依赖不链系统 zlib)编成 PNG;SvgPath 把同样的段序列写成 SVG path 字符串——圆弧转三次贝塞尔,每 ≤90° 一段、控制点系数 k = 4/3·tan(Δθ/4)、切向量 (-rx·sinθ, ry·cosθ),所有 Canvas 实现都接受 C 而 A 支持参差。300px 级 squircle 的 pathData 约 600–900 字符,数值保留两位小数去尾零。

perf 模式用 TimeSource.Monotonic 计时 iterations 次路径构建,返回 avg / p50 / min / max,再附一次光栅化耗时与验证 PNG。

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

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

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

add_library(korner_napi SHARED napi_init.cpp)
target_link_libraries(korner_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohoskorner)

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

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

第 5 阶段:ArkUI 宿主

5.1 显式请求类(ArkTS 严格模式的正确姿势)

ArkTS 禁止未类型化对象字面量(arkts-no-untyped-obj-literals),Record<string, Object> 字面量与 JSON.stringify 内联写法都会被拒。正确姿势是显式声明的类 + 逐字段赋值,未设置的四角字段保持 undefined,JSON.stringify 自动省略,桥端 MiniJson 语义不变(统一模式只发 cornerRadius/smoothing,四角模式才发 topStart*..bottomStart*):

class ShapeRequest {
  mode: string = 'shape';
  width: number = 300;
  height: number = 300;
  fg: string = '#111827';
  bg: string = '#F9FAFB';
  ss: number = 3;
  rtl: boolean = false;
  cornerRadius: number = 32;
  smoothing: number = 100;
  topStartRadius: number | undefined = undefined;
  // ... topEnd / bottomEnd / bottomStart 各两项,iterations
}

private generate(): void {
  const request: ShapeRequest = new ShapeRequest();
  request.mode = this.tab === 'perf' ? 'perf' : 'shape';
  request.width = this.shapeSize;                  // 注意:状态不能叫 size(见 8.1 踩坑 6)
  request.height = this.shapeSize;
  if (this.individual && this.tab === 'customize') {
    request.topStartRadius = this.tlRadius;        // 四角独立
    ...
  } else {
    request.cornerRadius = this.cornerRadius;
    request.smoothing = this.smoothing;
  }
  const raw: string = kornerRender(JSON.stringify(request));
  ...
}
5.2 PNG 解码与页面交互
private decodePng(b64: string): void {
  const helper = new util.Base64Helper();
  const bytes: Uint8Array = helper.decodeSync(b64);
  const sliced = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
  const src = image.createImageSource(sliced);
  src.createPixelMap().then((pm: image.PixelMap) => {
    this.pixelMap = pm;                            // @State 刷新 Image 组件
    this.generating = false;
  });
}

自定义 Tab 的每根滑条(尺寸 / 半径 / 平滑度 / 四角八根)、每个开关(四角独立 / RTL)、每套色板点击都触发重生成;统计行回显 segments / bytes / pathUs / rasterUs,pathData 文本截断 220 字符展示,说明它是可直接喂给 Path / Path2D / clip 的无损通道。对比 Tab 左右并排两张图(平滑 N% vs 0%),配文字说明曲率连续与切点跳变的差异。性能 Tab 输出四行等宽统计文本加一张验证 PNG。

5.3 对比度和错误边界

Tab 与选中态色板用深底白字(#111827),未选中灰底;ok:false 时错误原文进状态行;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进状态行并打 hilog。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohoskorner)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       ├── Bridge.kt                         @CName × 1 + MiniJson + perf 计时(继承自 cmp_qrose 审核模式)
│       ├── Rasterizer.kt                     展平 + even-odd 扫描线 + 超采样抗锯齿
│       ├── SvgPath.kt                        PathSegment → SVG pathData(圆弧转贝塞尔)
│       └── PngEncoder.kt                     零依赖 PNG(zlib stored)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.korner、versionName 2.0.1-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/libohoskorner_api.h
        ├── src/main/cpp/types/libkorner_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 三 Tab 验收页面
6.2 构建、签名和安装
.\gradlew.bat :shared:jvmTest                                        # 5 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 `
              :example:nativeApp:linkDebugSharedOhosX64              # so 自动拷入 entry/libs/<abi>/

# 用 DevEco Studio 打开 example/harmonyApp(不要打开仓库根目录)构建 HAP

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

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

四、完整代码对照

4.1 整体架构

org.dev.korner/**(上游几何原样编译)
    │ SmoothCorner(角度/距离推导) / CornerSmoothing(百分制)
Korner.cornerPath(新增公开入口)
    │ List<PathSegment>(镜像 genericSmoothOutline 指令序列)
Bridge.kt + MiniJson
    │ KornerRender(shape / perf)
Rasterizer + SvgPath + PngEncoder
    │ PNG Base64 + SVG pathData + 统计
libkorner_napi.so
    │ kornerRender
Index.ets
    │ JSON parse + Base64Helper + createPixelMap + @State 更新
    ▼
ArkUI 真机页面(三 Tab)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_korner、四端 Demo 移出
gradle/libs.versions.toml只剩 Kotlin 2.2.21-1.0.0 + multiplatform 插件别名
build.gradle.kts(根)版本 2.0.1-ohos.1、全局禁用 Ohos*Test* 任务
gradle.properties版本号、Temurin 21 路径、kotlin.native 三开关
shared/build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,commonMain 零依赖
shared/src/commonMain/.../SmoothCorner.kt上游几何(逐字节零改动)
shared/src/commonMain/.../CornerSmoothing.kt抽取 + 剥 @JvmInline(普通类复刻值类语义)
shared/src/commonMain/.../Korner.kt新增公开入口 + PathSegment 模型
shared/src/composeMain/**上游 Shape 类 / Demo UI / Platform / 资源冷藏(9 个文件 git mv)
shared/src/commonTest/.../KornerTest.kt5 项交叉验证(段数 / 捷径 / 有限性 / 重载等价)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt1 个 C ABI、mode 分发、RTL 映射、perf 计时、错误通道
nativeApp/.../Rasterizer.kt / SvgPath.kt / PngEncoder.kt光栅化 / SVG 序列化 / 零依赖 PNG
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 三 Tab 验收页面(ShapeRequest 显式类)
README.OpenHarmony_CN.md鸿蒙适配主文档(22 节)

4.3 关键 API 对照

层次API作用
引擎SmoothCorner(radius, smoothing, max)每角几何参数推导(锚点 / 控制点 / 圆弧段)
引擎CornerSmoothing百分制平滑度(Subtle / Balanced / Smooth / Continuous)
入口Korner.cornerPath(w, h, 8 参数)四角独立轮廓段序列(镜像 genericSmoothOutline)
入口Korner.cornerPath(w, h, r, s)统一四角重载
NativeKornerRenderC ABI JSON 导出(shape / perf)
光栅化Rasterizer.rasterize展平 + even-odd 填充 + 超采样 AA → PNG
序列化SvgPath.fromSegments轮廓段 → SVG pathData(圆弧转贝塞尔)
N-APIkornerRender(req)向 ArkTS 暴露能力
ArkTSgenerate() / decodePng()组装请求、解码 PNG、刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const request: ShapeRequest = new ShapeRequest();
request.width = 300; request.height = 300;
request.cornerRadius = 32; request.smoothing = 75;
const res = JSON.parse(kornerRender(JSON.stringify(request)));
// res.ok === true;res.segments === 17;res.pngBase64 直接进 Image;
// res.pathData === 'M 0 26.5 C ... Z' 可直接喂 Path / Path2D / clip
val segments = Korner.cornerPath(300f, 300f, 32f, CornerSmoothing(75))
// [Move, Cubic, Arc, Cubic, Line, Cubic, Arc, Cubic, Line, Cubic, Arc, Cubic,
//  Line, Cubic, Arc, Cubic, Close] —— 与 Compose 侧 genericSmoothOutline 逐行对应

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

五、关键决策说明

决策 1:几何复用 + Shape 冷藏,而不是 ArkTS 重写几何

Shape 类编不了 ohos 是制品线事实,但几何算法 100% 纯 Kotlin。把 SmoothCorner 原样编译、用 Korner.cornerPath 镜像指令序列,比用 ArkTS 重写一套角度推导强在三点:算法与上游逐位一致是验收底线(重写等于放弃复用引入漂移风险);JVM 交叉验证直接可用(同一份代码);等社区 CMP 对齐后 composeMain 接回,桥降级为 fallback,几何入口不动。

决策 2:工具线 2.2.21-1.0.0 + Gradle 8.14.1 降级

本库没有 kotlinx 依赖反推版本的压力(commonMain 零依赖),无需像 KStore 那样升 2.4.20 线;直接落回社区已批准的 2.2.21 钉法,代价是 wrapper 从 9.1.0 降到 8.14.1、gradle-daemon-jvm.properties 与 foojay resolver 一并移除。两条工具链线的经验不能互相套用:有 kotlinx 依赖跟着制品线走,没有依赖跟着社区钉法走。

决策 3:根构建替换(cmp_qrose 模式),四端 Demo 留盘

上游根构建(AGP / Compose 编译器 / vanniktech 发布)对鸿蒙没有保留价值,整个替换为极简 ohos 构建。androidApp / desktopApp / iosApp / webApp 四个 Demo 壳与 shared 内的 androidMain 等平台目录留盘不进 settings.gradle.kts——上游源码一棵树完整保留,对照与回接都有据可查。

决策 4:双输出——PNG 光栅化 + SVG pathData

形状库与条码 / 存储库最大的不同:它的产物"轮廓"天然可以无损序列化。PNG 光栅化让 Demo 开箱即见(Image + PixelMap 零额外代码);pathData 让真实业务拿到与 Compose 侧完全等价的几何(Path 组件 / Canvas Path2D / clip 直接可用,无解码开销、可无限缩放)。两条通道出自同一段 PathSegment,不存在两套几何。

决策 5:jvmTest 交叉验证钉住段数与捷径

几何逻辑在 commonMain,JVM 与 ohos 跑同一份代码;5 项 jvmTest 钉住三档捷径段数(5 / 9 / 17)、指令构成(1 Move + 3 Line + 8 Cubic + 4 Arc + Close)、坐标有限性与边界、统一重载与四角重载等价——这是"镜像没抄错"的闸门。开发中段数笔误(先写成 29)正是被这条测试当场纠正的。

六、测试与验证

6.1 测试环境

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

6.2 JVM 交叉验证

.\gradlew.bat :shared:jvmTest

5 项用例全部通过:

测试类用例数钉住什么
KornerTest5零半径矩形捷径(5 段);零平滑圆角捷径(9 段 / 4 弧);完整平滑轮廓(17 段 / 1+3+8+4+Close 构成);坐标有限且不出画布边界;统一与四角重载等价

6.3 HAP 验证

:shared:compileKotlinJvm / compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
copyKnSoArm64 / copyKnSoX64: 三件套自动回填 entry/libs/<abi>/
llvm-nm -D: KornerRender / libohoskorner_symbols 均为全局 T 符号(双 ABI)
BuildNativeWithCmake: finished(双 ABI 编出 libkorner_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

KN so(debugShared 未 strip)arm64 约 2.7MB、x86_64 约 2.3MB,各配 libc++_shared.so(约 1.8MB / 1.7MB);HAP 内双 ABI 六件套齐全。

6.4 功能用例

  1. 启动即见深色 squircle(默认 300×300、radius 32、smoothing 100%),状态行回显生成成功;
  2. 拖平滑度 0→100,圆角逐渐"胀"成超椭圆,放大四角可见直线→圆弧连续过渡;
  3. 开四角独立,四组滑条各异(如左上 48/100、右上 16/25),一张轮廓里四种曲率各自连续;
  4. 开 RTL(四角独立 + 各角不同值最直观),start/end 角物理互换,镜像即时生效;
  5. 切对比 Tab,同半径下左(平滑 N%)右(圆角 0%)并排,右侧经典圆角切点处可见折痕;
  6. 切性能 Tab,500 次迭代基准输出微秒级 avg / p50 / min / max,segments=17 恒定,iterations 加大线性增长;
  7. 自定义 Tab 底部 pathData 文本随参数实时变化,可直接复制喂给 Path / Path2D;
  8. 切换五套色板,前景 / 背景即时重生成,PNG 透明混合正确。

6.5 验证结论

几何算法、轮廓段模型、Native 动态库、C ABI、N-API、软件光栅化、SVG 序列化、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的 squircle 会话:统一 / 四角独立 / RTL 镜像 / 对比 / 性能基准闭环(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/Korner.git
cd Korner
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
.\gradlew.bat :shared:compileKotlinJvm :shared:jvmTest                         # klib + 5 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 `
              :example:nativeApp:linkDebugSharedOhosX64                         # 双 ABI so 三件套回填
# DevEco Studio 打开 example/harmonyApp 构建 HAP 并签名
hdc install -r entry\build\default\outputs\default\entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.korner -m entry
hdc shell hilog | grep KornerNapi

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :shared 真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. Gradle 9.1.0 与 HarmonyOS Kotlin 2.2.21 插件不兼容,wrapper 必须降 8.14.1,同时删掉 gradle-daemon-jvm.properties(Gradle 9 的 daemon toolchain provisioning,依赖被移除的 foojay resolver);
  4. @JvmInline value class 在 ohos native 目标报 Unresolved reference 'JvmInline',剥注解保 value class 又过不了 JVM——普通类精确复刻值类语义(equals / hashCode / toString 按底层值)是两全解;
  5. commonMain 的 expect getPlatform() 挪走后,jvmMain 的 actual 必须一起挪(jvm 目标保留编译),否则 actual without expect 直接挂——平台目录不是"留着就没事",参与编译的目标对应目录都要清;
  6. ArkTS 的 @State size 与 ArkUI 内置 size 属性冲突(Type 'number' is not assignable to type '(value: SizeOptions) => CommonAttribute'),状态命名要避开组件通用属性,改名 shapeSize 解决;
  7. ArkTS 禁止未类型化对象字面量(arkts-no-untyped-obj-literals):Record<string, Object> = {...} 与内联进 JSON.stringify 的字面量都会被拒——显式 ShapeRequest 类 + 逐字段赋值,未设置字段 undefined 由 JSON.stringify 自动省略;
  8. CMake 找不到 KN so 时 FATAL_ERROR 是设计行为(提示先跑 linkDebugSharedOhos*),不要试图把 so 提交进仓库绕过——copyKnSo* 由链接任务 finalizedBy 自动回填;
  9. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI(模拟器是 x86_64),每个 ABI 三件套缺一不可;
  10. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  11. Windows 盘符会混进 RUNPATH 导致 dlopen 失败,CMake 用 IMPORTED_NO_SONAME + $ORIGIN rpath 规避;
  12. DevEco 打开工程会自动生成 .clang-tidy / .clangd(标准 NAPI lint 配置、无本机路径),可以入库;oh_modules / .hvigor / entry/build / entry/libs / .cxx 与 *.so 必须进 gitignore。

8.2 已知问题

  • 鸿蒙侧无 Compose Shape / Brush:composeMain 里的两个 Shape 类(含 Modifier.clip 生态)不能在 ohos 使用,替代路径是桥返回的 pathData 配 ArkTS Path / Path2D / Canvas clip;
  • 光栅化是纯 CPU 软件实现,无 GPU 加速、无渐变 / 图案填充(上游形状模型也是纯色);
  • PNG 走 zlib stored 未压缩块,512px 级数十 KB;>1024px 建议直接用 pathData;
  • 上游 Demo 的 PerfBenchmarkScreen 测 Compose Shape.createOutline,本 fork 性能 Tab 测等价的 Korner.cornerPath(同一几何推导),数值不可与 Compose 侧直接对表,仅用于 ohos 侧相对比较;
  • RTL 仅镜像角参数(与上游 SmoothCornerShape 语义一致),不做文字方向;
  • 桥目前只出填充 PNG,描边 / 边框走 pathData 通道(Canvas stroke),几何无损;
  • android / js / wasmJs / apple 目标剥离(源文件留在树里),发布配置一并剥离,本 fork 不发 Maven Central;上游 library-ci.yml(Maven Central 发布 workflow)名存实亡保留;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.korner 匹配的签名。

8.3 未来优化方向

  • 观望社区 CMP 1.11 制品线,对齐后把 composeMain 接回 ohos 编译,光栅化降级为 fallback;
  • 补 uitest + capture.py 自动截图脚本(对齐 qrose 的验收形态);
  • 桥补描边 / 边框宽度参数(光栅化 stroke 通道);
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵。

九、总结

9.1 核心难点回顾

上游 shared(几何 + Compose Shape + Demo UI 混在 commonMain)
    → 验证 compose 制品线缺失(决定"几何复用、Shape 冷藏"路线)
    → 工具链对齐 Kotlin 2.2.21-1.0.0(Gradle 8.14.1 降级)
    → 根构建替换(cmp_qrose 模式)+ 源码集拆分
    → ohosArm64 / ohosX64 KLIB + Korner.cornerPath 公开入口
    → C ABI(@CName × 1)+ PathSegment 载荷 + 双输出(PNG / SVG)
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析(ShapeRequest 显式类)
    → ArkUI 真机三 Tab 验收(自定义 / 对比 / 性能)

9.2 封装层次

  • 引擎层:上游原版 SmoothCorner 角度 / 距离推导、半径夹取、插值退化;
  • 入口层:Korner.cornerPath + PathSegment 模型(镜像 genericSmoothOutline 指令序列);
  • Native 层:生成双 ABI 动态库并通过 1 个 C ABI 输出 JSON;
  • 输出层:Rasterizer(扫描线 + 超采样 AA)、SvgPath(圆弧转贝塞尔)、PngEncoder(零依赖 PNG);
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、请求组装、PNG 解码、统计与 pathData 展示;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 适配路线由"缺什么"决定:KStore 缺平台实现(补 8 行 actual 真编整库),Korner 多 UI 包袱(拆源码集冷藏 Shape)——几何纯 Kotlin 就复用,Compose 类型在制品线上不存在就隔离,边界画在依赖的现实上,而不是画在历史结论上;
  2. 镜像上游要留"可验证的接缝":PathSegment 与 Compose Path 指令一一对应、段数 5/9/17 三档捷径钉进 jvmTest,笔误(29 段)当场被测试纠正——镜像代码没有对照测试等于裸奔;
  3. 形状库的桥要多给一条无损通道:条码 / 存储库的产物只能光栅化或序列化数据,而形状的轮廓天然是矢量——PNG 负责"开箱即见",pathData 负责"真实集成",同一段几何两种交付,Demo 与业务两头都讨好。

9.4 适配成果

当前适配已完成:

  • Korner 2.0.1(README 写 2.0.0,构建脚本为 2.0.1,以构建脚本为准)真实几何的双 ABI Kotlin/Native 编译(上游 SmoothCorner.kt 零改动,CornerSmoothing 抽取剥注解,新增 Korner.cornerPath 入口);
  • 1 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证双 ABI 全局 T 符号:KornerRender / libohoskorner_symbols);
  • 软件光栅化(even-odd 扫描线 + 超采样抗锯齿)与 SVG pathData 双输出通道;
  • DevEco Stage ArkUI 三 Tab 验收页面(统一 / 四角独立 / RTL 镜像 / 对比 / 性能基准 / pathData 展示);
  • 5 项 JVM 交叉验证全绿 + HAP 构建(双 ABI 六件套 so,KN so arm64 约 2.7MB / x86_64 约 2.3MB)、签名、设备安装和真机验证;
  • AtomGit 项目文档、OpenHarmony README(中英双份,中文主文档 22 节)和本地效果图。

适配过程 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐