本文记录 AestheticDialogs 接入 OpenHarmony 的完整过程,覆盖对话框设计系统盘点、模型/Token 核心移植、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机/真机验收。

AestheticDialogs 是一个 Jetpack Compose 对话框设计系统(上游 2.0.0,Apache-2.0):三层 UI 架构(Component → Variant → Primitive → Tokens),七个组件家族(Alert / Confirmation / Selection / Input / Content / Feedback / Notification)共用一套框架、一套主题与一套无障碍保证;1.x 时代的八种装饰对话框在 2.0 里收敛为 Feedback(Flat / Flash)与 Notification(Toaster / Rainbow / Connectify / Emoji / Emotion)两个家族。本次适配与存储/编码类库(KStore / qrose)有本质区别:对话框是交互式 UI 而非静态数据,渲染必须活在帧率循环里,所以适配切面选在"模型 → 渲染规格"——上游的 UiModel 层(永不持 lambda)、配色引擎、tone 渐变、间距/圆角/字号/动效刻度被移植为纯 Kotlin 编译成 OpenHarmony 动态库,每次打开对话框时 ArkTS 调桥拿回完整规格 JSON,再用 ArkUI 组件绘制;哪个颜色、多大圆角、什么渐变、按钮怎么随 tone 变,全部由 Kotlin 核心计算,ArkTS 只做布局与交互,不定义任何设计值。

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

上游仓库: gabriel-TheCode/AestheticDialogs

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 CMP 适配

AestheticDialogs 2.0 为 Android 应用提供"对话框"这一件事的设计系统答案:AestheticAlertDialog(uiModel, onSignal) 一行弹窗,视觉决策(状态色、渐变、圆角、间距、字号、动效时长)全部由 Token 层输出;DialogTone 五档语义(Success / Error / Warning / Info / Neutral)驱动所有家族的状态表达;withBrand() 四个参数一行换品牌且状态色不可品牌化;暗色主题是同一套角色的纯重映射。上游把这些能力实现在 Jetpack Compose 上(AGP 9.2.1 / Kotlin 2.4.10 / Gradle 9.7.0),OpenHarmony 应用同样需要这套对话框语言,但上游构建不会生成 OpenHarmony 变体。

只把示例页面改成 ArkTS 是不够的:页面能画出来,却无法证明设计系统的决策引擎(tone → 按钮色映射、渐变 ramp、暗色重映射、token 刻度)真的在 OpenHarmony 设备上运行。适配需要同时验证模型层、配色引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
上游不是 KMP纯 Android 库(com.android.library + Compose 插件),没有 commonMain,不能像 KStore 那样"整体编译"。
模型层被 androidx 注解污染@Immutable、ImageVector、KeyboardType、Compose Color / Dp / TextStyle / Easing 散在模型与 token 文件里。
渲染层过不了 CMP 版本鸿沟社区鸿蒙 CMP 在 1.9.2 一线,上游依赖的 Compose 运行时远新于此。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交互语义跨不了桥signal 回调、dismissOnBackPress / dismissOnClickOutside 双开关、软键盘避让是 UI 侧概念,桥只能同步一来一回。
工具链不一致上游 Gradle 9.7.0 / AGP 9.2.1 与鸿蒙工具链无法共存于一个 Gradle 构建。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:模型层与 Token 层的 ohos 编译(包名与文件名保持上游原样,色值/间距/混合系数逐字保留)、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。渲染由 ArkTS 按核心输出的规格完成;上游 Android 模块(aestheticdialogs/、app/)留在仓库里零改动,随时可与上游 diff 对账。

1.2 库提供的能力

Demo 覆盖上游 Catalog 的全部入口,13 个对话框场景 + 5 种横幅 + 5 种横幅动画:

  • Confirmation(Default / Destructive):一句问话两个答案;destructive 的确认键保证用 error 色填充,isConfirming 时按钮转圈并锁全对话框(Demo 模拟 1.5s 请求),完成后自动弹 Toaster 横幅;
  • Alert(Default):icon / title / message + 主次动作,tone 决定状态徽章;覆盖错误/离线/权限三种模式、glyph 覆盖(把 info 换成 search)、长文本滚动与返回键拦截;
  • Selection(Single / Multiple):radio 行 / checkbox 行、搜索框、禁用行、空态、多选提交;
  • Input(Text / Password):errorText 错误态、确认禁用、密码显隐切换;
  • Content(Default):中部内容 slot + 框架(自适应宽度、scrim、dismiss 契约、动作行);
  • Feedback(Flat / Flash):Flat 安静卡片 + tone 徽章;Flash tone 渐变满填充 + 反色文案(“loud one”);
  • Notification(Toaster / Rainbow / Connectify / Emoji / Emotion):五种横幅轮廓 + Slide / Fade / Scale / SlideHorizontal / None 五种出入场动画,顶/底锚点、4s 自动消失;
  • 全局:暗色 Switch(切换即重发桥请求 mode:"dark")、TONE 五档循环药丸、桥状态卡(显示最近一次调用的 ok / kind / tone / scrim / surface / radius / elevation 实值——验收"核心真的在跑"的直接证据)。

桥响应(规格 JSON)的主要字段:

字段作用
colors / toneColors完整语义配色(surface / content / border / action 四组角色)与当前 tone 的四角色。
toneGradient / toneRim三档渐变 ramp(stop 0 / 0.5 / 1),由 lerp(accent,inverse,0.22) → accent → lerp(accent,primary,0.14) 生成,混合系数与上游 ToneStyling.kt 逐字一致;垂直版供 Flash 填充,水平版供 Connectify 顶条。
typography / spacing / shapes / dimens / frame / banner六档字号、4dp 间距网格、圆角刻度、控件尺寸、宽度桶(480/560)、横幅规格——全部来自上游 tokens。
motion时长三档(120/220/320ms)+ 出入场 cubic-bezier 字符串。
actions / confirmStyle四种 emphasis 的按钮样式,以及按当前对话框解析出的确认键样式(tone → 按钮色映射这层设计决策留在 Kotlin)。
model规范化回显(默认值已应用),ArkTS 直接渲染,不必自己补默认。

1.3 实现适配

维度要求
代码复用模型与 Token 层"逻辑搬移"进 example/nativeApp/src/commonMain(包名、文件名与上游一致),四类机械替换(Color→AestheticColor、Dp/sp→Double、TextStyle/Easing→TypeSpec/bezier 字符串、ImageVector/KeyboardType→glyph 名/自建枚举);色值、间距、混合系数逐字保留。
平台目标example/nativeApp 声明 jvm()(交叉验证)+ ohosArm64 + ohosX64,双 ABI sharedLib libohosaestheticdialogs.so。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 aestheticdialogs/、app/ 留盘不进 settings。
桥接稳定1 个 C ABI(AestheticDialogSpec)+ UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
UI 完整单页覆盖 7 家族全部变体;所有视觉值来自规格 JSON,ArkTS 零设计 token 硬编码(错误指示色除外——它不能依赖已宕机的 token 引擎)。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现,也不是"照着截图重写一套":每个对话框打开前都真实调用 Native 桥获取规格,Bridge 状态卡展示的 scrim / radius / elevation 等值即来自 Kotlin 核心。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点上游架构(三层 UI + UiModel 无 lambda)、确定"模型/Token 留 Kotlin、渲染归 ArkTS"切面
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、Gradle 降 8.14.1、删 daemon-jvm criteria
第 3 阶段:核心移植       ── model/foundation/tokens/variants 逻辑搬移 + 四类机械替换,包名不变
第 4 阶段:桥协议建模     ── MiniJson 树形解析器、SpecBuilder 规格输出、confirmStyle 规则以上游源码校准
第 5 阶段:原生桥接       ── @CName C ABI、C++ N-API、CMake IMPORTED + $ORIGIN rpath
第 6 阶段:ArkUI 宿主与验证 ── 七家族 Demo、HAP、真机/真机安装、点击驱动验收和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
aestheticdialogs/                     上游 Android 库模块(留盘不进 settings,零改动)
app/                                  上游 Catalog 演示模块(留盘不进 settings,零改动)
example/nativeApp/                    Kotlin/Native 动态库(核心移植 + Bridge + MiniJson + SpecBuilder)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.upstream.md                    上游原文保存
docs/ scripts/ CLAUDE.md              上游文档与脚本(零改动)

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :example:nativeApp,rootProject.name = "ohos_AestheticDialogs"。上游的 AGP、maven-publish、roborazzi、dokka、spotless、BCV 插件全部随根构建替换剥离——本 fork 不发 Maven Central,Android 侧构建回官方上游仓。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(Temurin 21.0.12.101,org.gradle.java.home 钉死)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(2.2.21 旧线要求;上游 9.7.0 wrapper 不可用)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.aestheticdialogs
版本2.0.0-ohos.1(Gradle 与 AppScope/app.json5 双处一致)
export JAVA_HOME="/path/to/jdk-21"
java -version

上游根目录原有 gradle/gradle-daemon-jvm.properties(JBR 21 工具链 criteria),会与钉死的 Temurin 路径打架导致 daemon 起不来——适配时删除,JDK 由 org.gradle.java.home 唯一决定。

1.3 源码依赖度盘点
上游目录文件依赖形态处置
model/ + 7 个 components/*/models/8@Immutable 注解 + ImageVector + KeyboardType 三处 androidx逻辑搬移(结构、字段名、默认值逐字保留)
foundation/3Compose Color逻辑搬移(AestheticColor(Long ARGB) 替换)
tokens/7Dp / sp / TextStyle / Easing / Shape逻辑搬移(数值不变,类型机械替换)
variants/ToneStyling.kt1@Composable + Brush改纯函数(ramp 三色 + 混合系数逐字保留)
primitives/ + components/*/variants/12Compose 渲染(Canvas、Dialog 窗口、TextField)不移植——渲染归 ArkTS;其中 AestheticGlyphs.kt 的几何数据(单位方框线段坐标)逐字翻译进 ArkTS Glyph.ets
preview/ + test/—Robolectric / Roborazzi留在上游模块(本仓不可构建)

四类机械替换的对照:

上游类型移植为说明
Color(0xFF48D865)AestheticColor(0xFF48D865L)value class 包 ARGB Long,hex() 输出 #AARRGGBB
48.dp / 20.sp48.0 / 20.0Double,数值不变
TextStyle(weight, size, lineHeight, letterSpacing)TypeSpec(size, weight, lineHeight, letterSpacing)weight 用 Int(400/500/600)
LinearOutSlowInEasing / CubicBezierEasing(0.4f, 0f, 1f, 1f)"cubic-bezier(0, 0, 0.2, 1)" / "cubic-bezier(0.4, 0, 1, 1)"字符串,ArkTS 侧解析四控制点
icon: ImageVector?icon: String?glyph 名(check/cross/exclamation/info/search/close/reveal/conceal)
keyboardType: KeyboardTypeDialogKeyboardType 枚举Text/Email/Phone/Number/Uri
1.4 页面边界
标题区          AestheticDialogs 2.0 / 副标题 / Dark theme 开关
TONE 区         SUCCESS/ERROR/WARNING/INFO/NEUTRAL 五药丸 + CYCLE 循环按钮
桥状态卡        调用数、ok/kind/variant/tone/dark、scrim/surface/radius/elevation 实值、最近 signal
DIALOGS 组      13 个场景行(Confirmation×2 / Alert×4 / Selection×2 / Input×2 / Content / Feedback×2)
NOTIFICATIONS 组 横幅动画切换器(Slide/Fade/Scale/SlideHorizontal/None)+ 5 个横幅行
模态层          Stack overlay:scrim + 对话框卡片(宽度按宽度桶自适应、返回键/点外分别建模)
横幅层          顶部/底部悬浮 + 出入场动画 + 4s 自动消失

页面打开每个对话框前都先调 aestheticDialogSpec(json);signal(Confirmed / Cancelled / ItemClicked / SearchQueryChanged…)在 ArkTS 本地处理后写入桥状态卡的"最近 signal"行。

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

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

本仓核心是"逻辑搬移"的纯 Kotlin,不依赖任何 kotlinx 制品线,因此沿用社区早期批准的钉法:HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 同款)。这条线的要求是 Gradle 8.14.1——上游 wrapper 是 9.7.0,必须替换;gradle.properties 钉 kotlin.native.ignoreDisabledTargets=true 与 kotlin.native.distribution.downloadFromMaven=true(KN 分发从 Maven 取,本机 ~/.konan 已有 2.2.21-1.0.0 预编译与 ohos sysroot 缓存,离线可链)。

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

include(":example:nativeApp")

gradle/libs.versions.toml 只剩一个插件条目:

[versions]
kotlin = "2.2.21-1.0.0"

[plugins]
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
2.2 编译核心

example/nativeApp/build.gradle.kts:

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

kotlin {
    applyDefaultHierarchyTemplate()
    jvm()          // jvmTest 交叉验证
    jvmToolchain(21)

    ohosArm64 {
        binaries.sharedLib { baseName = "ohosaestheticdialogs" }
    }
    ohosX64 {
        binaries.sharedLib { baseName = "ohosaestheticdialogs" }
    }

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

fun cxxShared(triple: String): File { /* 从 ~/.konan/dependencies 定位 libc++_shared.so */ }

tasks.register<Copy>("copyKnSoArm64") { /* debugShared 产物 + libc++ → harmonyApp/entry/libs/arm64-v8a */ }
tasks.register<Copy>("copyKnSoX64")   { /* → entry/libs/x86_64 */ }
tasks.matching { it.name == "linkDebugSharedOhosArm64" }.configureEach { finalizedBy("copyKnSoArm64") }
tasks.matching { it.name == "linkDebugSharedOhosX64" }.configureEach { finalizedBy("copyKnSoX64") }

两处纪律:kotlin.experimental.ExperimentalNativeApi 的 optIn 只对 native 目标合法,本模块还有 jvm(),所以用 @file:OptIn 挂在 ohosMain 的 Bridge.kt 上而不是 compilerOptions 块;根构建全局禁用 Ohos*Test* 任务(ohos 测试目标不可用,测试统一 JVM 跑)。

实测:

./gradlew :example:nativeApp:jvmTest                                        # BUILD SUCCESSFUL, 17 项
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
# libohosaestheticdialogs.so: arm64 5,489,968 B ≈ 5.2MB;x86_64 4,966,808 B ≈ 4.7MB
2.3 上游同步机制

核心文件与上游模块保持文件级对应(包名、文件名一致)。上游发新版时:diff 上游 foundation/tokens/model/components/*/models → 同步 nativeApp commonMain 对应文件 → 跑 jvmTest → 重链 so。"上游零改动"是验收底线:git diff --stat HEAD -- aestheticdialogs app docs scripts 为空。

第 3 阶段:桥协议建模

3.1 JSON 契约
com.thecode.aestheticdialogs/**(移植核心:UiModel + 配色引擎 + tokens + tone ramp)
        ↓ 规格计算
SpecBuilder.kt(请求解析 → UiModel 构建 → 规格输出)
Bridge.kt(@CName × 1,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API aestheticDialogSpec(request)
        ↓
ArkUI Index.ets(目录页 + 模态 overlay + 横幅宿主)

请求与响应:

请求 : {"kind":"confirmation","mode":"dark","reducedMotion":false,
        "brand":{"primary":"#...","onPrimary":"#","surface":"#","onSurface":"#"},
        "model":{"variant":"destructive","title":"...","confirmLabel":"...","cancelLabel":"..."}}
响应 : {"ok":true,"kind":"...","variant":"...","tone":"...","isDark":false,
        "colors":{...},"toneColors":{...},"toneGradient":[...],"toneRim":[...],"glyph":"...",
        "frame":{...},"banner":{...},"dimens":{...},"spacing":{...},"shapes":{...},
        "typography":{...},"motion":{...},"actions":{...},"confirmStyle":{...},
        "model":{规范化回显}}
       {"ok":false,"error":"..."}

kind 覆盖七家族;model 字段名 = 上游 UiModel 字段的 lowerCamelCase(selectedIds 数组、items[{id,label,supportingText,enabled}]、primaryAction{label,emphasis,enabled,loading} 等);mode 选 light/dark 配色;brand 同上游 withBrand()(status tone 不可品牌化的约束一并保留);reducedMotion 映射 motion.enabled。

3.2 confirmStyle:设计决策留在 Kotlin

确认按钮"什么颜色"是设计系统的核心观点,这层逻辑放 Kotlin 侧、ArkTS 不重复实现。规则以上游 variants 源码逐条校准:

组件 / 变体confirmStyle
alert按 primaryAction.emphasis 取条目;Primary 且 tone 非 Neutral 时 = tone accent / onAccent
confirmation.DefaultPrimary emphasis + tone:Neutral 时是品牌蓝 action.primary(不是 neutral 灰 accent——初版按直觉写成恒 tone accent,读上游 ConfirmationDialogVariants 后纠正)
confirmation.Destructiveerror accent / onAccent(上游强制 tone=Error)
selection / inputactions.primary(动作行不传 tone,即品牌蓝)
feedback.Flattone 解析的 Primary(Neutral→品牌蓝)
feedback.Flash透明容器 + onAccent 文字与 1dp 描边(上游 DialogButtonPrimitive 是 containerColor=Transparent, BorderStroke(1dp, onGradient)——渐变面板本身即背景,按钮只描边)
content有 primaryAction 按其 emphasis,否则 actions.primary

这条经验值得单独立规:每一条视觉规则都要能指回上游某个文件,猜的必须被源码推翻。

3.3 错误通道与 MiniJson

桥内整体 try/catch,Kotlin 异常永远不跨 FFI:

@CName("AestheticDialogSpec")
fun aestheticDialogSpec(request: String): String = try {
    SpecBuilder.build(request)
} catch (t: Throwable) {
    """{"ok":false,"error":${MiniJson.jsonString(t.message ?: t.toString())}}"""
}

非法 JSON、未知 kind / tone / variant、缺必填字段(如 alert 缺 primaryAction——上游 UiModel 里它非可选),全部走同一个 ok:false 通道。请求是嵌套 JSON(model 对象、items 数组、action 对象),所以 MiniJson 是树形递归下降解析器(JsonObject / JsonArray / JsonString / JsonNumber / JsonBool / JsonNull 六节点,全转义含 \uXXXX),而不是 cmp_qrose 时代的扁平 key-value 版本——但决策同源:C ABI 边界永远 const char* → const char*,不上 kotlinx.serialization,桥的 schema 一共 30 来个字段,150 行手写解析器比序列化插件的元数据负担划算。

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

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

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只传布尔又会让页面复制设计决策,所以选择 C ABI + JSON——切面在"模型 → 规格",不在"规格 → 像素"。

4.2 Kotlin/Native 导出函数
@CName("AestheticDialogSpec")
fun aestheticDialogSpec(request: String): String   // 七家族一口进
C ABI 符号ArkTS 方法覆盖内核
AestheticDialogSpecaestheticDialogSpec(req)配色引擎 + tone ramp + token 刻度 + confirmStyle 解析 + 模型规范化

llvm-nm -D 实测双 ABI so 均含 T AestheticDialogSpec 与 T libohosaestheticdialogs_symbols;手写 libohosaestheticdialogs_api.h(只声明 ExportedSymbols 结构与业务符号)与 KN 自动生成的头文件逐字一致。

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

回调统一三步:napi_typeof 校验必须是 string(两段式 JsToUtf8 取串)、OH_LOG_INFO 打请求字节数(hilog tag AestheticDialogsNapi——真机上验证桥调用的直接证据)、调 KN 函数并包装返回。KN 返回字符串必须经 libohosaestheticdialogs_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。nm_modname(aestheticdialogs_napi)、oh-package 依赖名(libaestheticdialogs_napi.so)、index.d.ts 导出名与 ArkTS import 'libaestheticdialogs_napi.so' 四处名字严格一致。

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

add_library(aestheticdialogs_napi SHARED napi_init.cpp)
target_link_libraries(aestheticdialogs_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosaestheticdialogs)

OHOS_ARCH 为空或 KN so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath + IMPORTED_NO_SONAME 防止 Windows 盘符路径写进 NEEDED;POST_BUILD 把 KN so 与 libc++_shared.so 拷到 NAPI so 旁,三件套同目录 dlopen 互见。

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

第 5 阶段:ArkUI 宿主

5.1 打开对话框
private openDemo(id: string): void {
  // 1. 组 model(字段 = 上游 UiModel lowerCamelCase)
  // 2. 调桥取规格
  const spec: SpecResponse = fetchSpec(kind, this.mode(), DEFAULT_BRAND, model);
  if (!spec.ok) { this.lastCall = `ok=false error=${spec.error ?? 'unknown'}`; return; }
  // 3. 激活模态 overlay,signal 本地处理
  this.dlgSpec = spec;
  this.activeDemo = id;
}

渲染层零设计 token 硬编码:卡片底 colors.surface.container、遮罩 colors.surface.scrim、圆角 shapes.dialog、标题 typography.title 逐档映射、渐变 toneGradient 直喂 linearGradient、动效 motion.easingEnter 解析四控制点后 curves.cubicBezierCurve(...) 现场构造、glyph 用 Glyph.ets 逐字翻译的单位方框线段几何(如 check = (0.24,0.53)→(0.43,0.71)→(0.77,0.31),圆头线帽,线宽 = 0.10×边长)。暗色切换 = 换 mode:"dark" 重发请求,全部规格随之翻转——上游"暗色是纯重映射"的设计在鸿蒙侧零额外代码兑现。

5.2 模态实现:Stack overlay 而非 @CustomDialog

@CustomDialog 把"返回键"与 autoCancel 耦合,无法独立表达上游 DialogDismissBehavior 的两个开关。页面 Stack overlay + onBackPress 分发才能分别建模 dismissOnBackPress / dismissOnClickOutside(alertLong demo 专门演示返回键拦截);对话框宽度按 frame 宽度桶自适应:min(屏宽 − 2×compactWindowMargin, widthMedium)。

5.3 错误边界与兜底规格

fetchSpec 整体 try/catch,桥或 JSON.parse 失败返回 ok=false 的 SpecResponse;emptySpec() 兜底值不是零而是上游 light 主题真值(字号 20/15/16/13/15/12、可见配色、圆角 24)——桥宕机时页面依然可读;pageSpec 为 null 时 BridgeCard 顶部渲染红色错误卡(错误指示色硬编码是刻意的:它不能依赖已宕机的 token 引擎)。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosaestheticdialogs)+ cxxShared + copyKnSo*
│   └── src/
│       ├── commonMain/kotlin/com/thecode/aestheticdialogs/   核心移植(model/foundation/tokens/variants/glyphs)
│       ├── commonMain/kotlin/example/nativeapp/              MiniJson + SpecBuilder
│       ├── ohosMain/kotlin/example/nativeapp/Bridge.kt       @CName("AestheticDialogSpec")
│       └── jvmTest/kotlin/                                   17 项交叉验证
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.aestheticdialogs、versionName 2.0.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/libohosaestheticdialogs_api.h
        ├── src/main/cpp/types/libaestheticdialogs_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/
            ├── model/Spec.ets                协议 interface + 请求构造 + bezier/宽度解析
            ├── components/                   Glyph / AestheticButton / AestheticDialogCard / AestheticBanner
            └── pages/Index.ets               ArkUI 验收页面(目录 + 模态 + 横幅)
6.2 构建、签名和安装
./gradlew :example:nativeApp:jvmTest                                    # 17 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

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

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书不进入仓库(仓库内 signingConfigs 保持空数组)。DevEco 真机(x86_64)接受未签名调试包,可先行验收再上真机:

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.aestheticdialogs -m entry
hdc shell hilog | grep AestheticDialogs          # 桥调用打点:request bytes=...

四、完整代码对照

4.1 整体架构

com.thecode.aestheticdialogs/**(移植核心:UiModel + AestheticColors + tokens + tone ramp)
    │ 五档 tone / light-dark 重映射 / withBrand / 渐变系数 0.22·0.14
SpecBuilder + MiniJson
    │ 请求 JSON → UiModel → 规格 JSON(含 confirmStyle 设计决策)
libaestheticdialogs_napi.so
    │ aestheticDialogSpec
Index.ets + components/*
    │ 规格驱动渲染 + signal 本地处理
    ▼
ArkUI 页面(13 场景 + 5 横幅 + 5 动画)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_AestheticDialogs、Android 模块移出
gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 单插件钉版
build.gradle.kts(根)版本 2.0.0-ohos.1、全局禁用 Ohos*Test* 任务
gradle.propertiesJDK 21 钉路径、KN 开关、wrapper 8.14.1
nativeApp/.../foundation/AestheticColor.ktvalue class(Long ARGB)+ lerp + #AARRGGBB 输出
nativeApp/.../AestheticColorSchemes.ktlight / dark / withBrand(色值逐字)
nativeApp/.../tokens/*Dimens / Spacing / Elevation / Shapes / Typography / Motion / Palette
nativeApp/.../components/*/models/*.kt7 家族 UiModel + Signal(结构与上游逐字一致)
nativeApp/.../example/nativeapp/MiniJson.kt树形递归下降 JSON 解析器 + writer
nativeApp/.../example/nativeapp/SpecBuilder.kt七 kind 协议、confirmStyle、错误契约
nativeApp/.../ohosMain/.../Bridge.kt@CName("AestheticDialogSpec"),异常不穿 ABI
nativeApp/.../jvmTest/*.kt17 项交叉验证(含页面级请求回归)
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/model/Spec.ets协议 interface + 请求构造 + 兜底规格
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(24 节)

4.3 关键 API 对照

层次API作用
引擎aestheticLightColors() / aestheticDarkColors()两套语义配色(角色级)
引擎withBrand(primary, onPrimary, surface, onSurface)一行换品牌,status tone 不可品牌化
引擎toneRamp(tone, colors)三色渐变 ramp(0.22 / 0.14 混合系数)
引擎AestheticDimens / Spacing / Shapes / Typography / Motion全部布局刻度 token
模型*UiModel(7 家族 sealed interface)无 lambda 的视觉状态;Signal 全部无参 data object
NativeAestheticDialogSpecC ABI JSON 导出(唯一入口)
N-APIaestheticDialogSpec(req)向 ArkTS 暴露能力
ArkTSfetchSpec(kind, mode, brand, model)调桥 + 解析 + 错误兜底

4.4 ArkTS 与 Kotlin 的边界

const spec = JSON.parse(aestheticDialogSpec(JSON.stringify({
  kind: 'confirmation', mode: this.isDark ? 'dark' : 'light',
  brand: DEFAULT_BRAND,
  model: { variant: 'destructive', title: 'Delete this album?',
           message: 'This cannot be undone.',
           confirmLabel: 'Delete album', cancelLabel: 'Keep it' }
}))) as SpecResponse;
// spec.confirmStyle.container === '#FFC22B22'(error accent,Kotlin 侧解析)
// spec.colors.surface.scrim / spec.shapes.dialog 直接驱动遮罩与圆角

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

五、关键决策说明

决策 1:渲染切面选在"模型 → 规格",不在"规格 → 像素"

qrose 能走"KN 光栅化 PNG 回传"是因为二维码的产物就是静态位图;对话框是交互式 UI,要响应点击、输入、动效,必须活在 ArkUI 的帧率循环里。而"设计值"(颜色/圆角/渐变/字号/动效时长)是纯数据,跨语言零损耗。所以 Kotlin 侧止步于规格 JSON,像素交给 ArkUI——这同时保住了"设计决策留在 Kotlin"与"交互不跨 FFI"两条底线。

决策 2:核心放 example/nativeApp 逻辑搬移,不 KMP 化库模块

上游 :aestheticdialogs 深度绑定 AGP / mavenPublish / roborazzi / BCV,改 KMP 会破坏其发布链且工程量大;qrose 能直接复用库模块是因为它本来就是 KMP。逻辑搬移保持包名与文件名不变,上游同步靠文件级 diff,配 17 项 jvmTest 钉住数值没有漂。

决策 3:工具线用 2.2.21 + Gradle 8.14.1

本仓核心零 kotlinx 依赖,不需要 KStore 那条 2.4.20 制品线;沿用社区早期批准的 2.2.21-1.0.0 钉法,代价是 Gradle 从 9.7.0 降到 8.14.1(上游 Android 构建本来就不在本仓进行,无附加成本)。两条工具链线的经验不能互相套用:钉哪条线由核心的依赖形态决定。

决策 4:MiniJson 手写、树形版

请求含嵌套对象与数组,qrose 的扁平解析器不够用;升级为六节点树形递归下降解析器(全转义、\uXXXX、科学计数法),但"不上 kotlinx.serialization"的决策同源——C ABI 边界永远字符串进字符串出。

决策 5:模态用 Stack overlay,dismiss 双开关独立建模

@CustomDialog 的返回键与 autoCancel 耦合;页面 overlay + onBackPress 分发才能分别表达 dismissOnBackPress=false(Blocking)与 dismissOnClickOutside=false,scrim 直接用 colors.surface.scrim。

决策 6:兜底规格用真值而不是零

emptySpec() 曾用全零占位,导致一次"静默白屏"事故(桥返回 ok:false 时字号全 0,文字全部不可见,页面只剩一个 Toggle)。修复后兜底值 = 上游 light 主题真值 + BridgeCard 红色错误卡——失败必须可见,这是 UI 桥接层的通用铁律。

六、测试与验证

6.1 测试环境

本次验证使用 Windows 11、JDK 21(Temurin)、DevEco Studio 5.1.0(18) 命令行工具(hvigor / hdc)与 DevEco 真机(x86_64,arm64 真机流程相同)。

6.2 JVM 交叉验证

./gradlew :example:nativeApp:jvmTest

17 项用例全部通过:

测试类用例数钉住什么
SpecBuilderTest15七 kind 完整请求、转义/中文标题、light/dark、withBrand、9 类错误请求 ok=false、默认值回显、渐变 stop 0/0.5/1、scrim #52000000/#99000000
PageRequestReproTest2页面级最小请求必须带 primaryAction(缺 → 明确错误);带最小 action → ok=true 全字段返回

断言直接对着上游源码数值写死(4dp 间距网格、24/16 圆角、六档字号、tone 渐变系数、scrim 透明度 0.32/0.6),任何移植漂移立刻红。

6.3 HAP 验证

:example:nativeApp:jvmTest: BUILD SUCCESSFUL (17 tests)
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: AestheticDialogSpec / libohosaestheticdialogs_symbols 均为全局 T 符号(双 ABI)
copyKnSoArm64 / copyKnSoX64: 三件套就位 entry/libs/{arm64-v8a,x86_64}
BuildNativeWithCmake: finished(双 ABI 编出 libaestheticdialogs_napi.so)
CompileArkTS: finished
PackageHap: finished(entry-default-unsigned.hap,真机可直接安装)
install bundle successfully
start ability successfully

KN so arm64 约 5.2MB、x86_64 约 4.7MB(debugShared,含 Kotlin/Native 运行时)。

6.4 功能用例

  1. 冷启动后 hilog 出现 AestheticDialogsNapi: aestheticDialogSpec request bytes=232(页面级主题请求,桥真实在跑);
  2. 首页渲染完整:标题、TONE 药丸(SUCCESS 选中态品牌蓝填充)、13 场景列表、Bridge 状态卡显示 ok kind=alert ... scrim=#52000000 surface=#FFFFFFFF radius=24 elev=12;
  3. 点击 Confirmation:模态弹出——半透明 scrim(α=0.32)、白底 24dp 圆角卡片、“Publish this photo?”、品牌蓝胶囊 Publish + 描边 Not yet;
  4. 点击 Destructive:确认键 error 红填充;点击后按钮转圈 1.5s、全对话框禁用,完成后顶部弹 Toaster 横幅;
  5. Feedback Flash:tone 渐变满填充面板 + 透明描边按钮(上游 BorderStroke(1dp, onGradient) 的复刻);
  6. Selection Single:7 语言行含禁用项与搜索框,点选即回填;
  7. Input Password:显隐切换,<8 字符确认禁用;
  8. Alert 长文本:内容滚动,返回键不关闭(dismissOnBackPress=false 独立建模);
  9. 五种横幅 + 五种动画逐一触发,4s 自动消失;
  10. 暗色 Switch:全部规格翻转(等价于 mode:“dark” 重新过桥),CYCLE 药丸用 toneColors 渲染;
  11. 每步操作的 Bridge 状态卡与"最近 signal"行实时留痕。

6.5 验证结论

模型层、配色引擎、token 刻度、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,点击驱动的截图确认目录页、模态对话框、Flash 渐变面板、暗色重映射与 Rainbow 横幅全部按 Kotlin 核心输出的规格渲染(见下节效果图)。

七、运行效果

7.1 运行截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/AestheticDialogs.git
cd AestheticDialogs
export JAVA_HOME="/path/to/jdk-21"
./gradlew :example:nativeApp:jvmTest                                          # 17 项交叉验证
./gradlew :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.aestheticdialogs -m entry
hdc shell hilog | grep AestheticDialogs

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算适配:必须让模型/Token 核心真正编译成 ohosArm64 / ohosX64 动态库,页面从桥取值渲染;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 上游的 gradle/gradle-daemon-jvm.properties(daemon JVM criteria)会与 org.gradle.java.home 钉死的路径打架,daemon 起不来——适配时删除;
  4. 带有 jvm() 目标的模块里,kotlin.experimental.ExperimentalNativeApi 的 optIn 不能放 compilerOptions(native-only),用 @file:OptIn 挂在 ohosMain 文件上;
  5. JVM 后端要求 value class 加 @JvmInline,KN 侧则不需要——两目标共存时按最严的写;
  6. ArkTS 严格模式组件成员名:@Component struct 的成员不能用 size / enabled / onClick / borderColor / borderWidth 这类通用属性方法名(报 “not assignable to the same property in base type ‘CustomComponent’”),全局改名 glyphSize / isEnabled / onClicked / strokeColor / strokeWidth 并同步所有调用点;
  7. import { ICurve } from '@ohos.curves' 在新 SDK 无此导出:模型层只存 bezier 四数值,使用点 curves.cubicBezierCurve(...) 现场构造(@kit.ArkUI);
  8. ArkTS 禁止未类型化对象字面量(arkts-no-untyped-obj-literals):model: {} 改为显式 EmptyModel 类实例;
  9. Path 组件的圆角线帽属性是 strokeLineJoin 不是 strokeJoin;全局 px2vp 已弃用,改 screen.width / densityPixels;
  10. 静默白屏事故:页面级请求缺 primaryAction(上游必填)→ 桥 ok:false → 兜底规格全零 → fontSize(0) 文字不可见、背景透明,页面只剩一个 Toggle。三层修复:请求补最小 action、兜底规格改上游真值、BridgeCard 红色错误卡——UI 桥的失败必须可见;
  11. DevEco 打开工程后生成的 example/harmonyApp/.idea/(含 clangd 缓存)要单独追加 gitignore,根目录 .idea 规则盖不住嵌套目录;
  12. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  13. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free()。

8.2 已知问题

  • Compose 渲染层(variants / primitives 的 @Composable 部分)未移植;ArkTS 视图按同一 Token 规格等价复刻,个别度量(文字基线节奏)允许有 ArkUI 排版差异;
  • 上游 defaultMotion() 读系统动画缩放设置;鸿蒙侧 reduced-motion 由请求参数显式传入,未接系统设置监听;
  • 无障碍:实现了基本可达性,上游 LiveRegionMode(Error 用 Assertive)的完整等价物未逐一映射;
  • InputDialog 的 keyboardType 映射 ArkTS 输入风格,个别 Android 键盘变体无等价物未纳入枚举;
  • 本仓不能构建 Android APK;上游 Android 验证(roborazzi 截图、BCV、spotless)回官方上游仓,本仓以"上游文件零改动"保证可回溯;
  • 签名材料由 DevEco 自动管理,证书绝不进入仓库。

8.3 未来优化方向

  • 鸿蒙侧包化:把 components + Spec 整理为 HAR/HSP,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 无障碍映射补全(liveRegion 等价物、屏幕朗读实测);
  • glyph 几何改 SymbolGlyph / 资源化,减少自绘 Path;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充真机(arm64)验收矩阵与自动化截图脚本(uitest dumpLayout + uiInput click 的脚本化,本次真机验收已用此法)。

九、总结

9.1 核心难点回顾

上游 AestheticDialogs 2.0(纯 Android Compose,三层 UI 架构)
    → 判定切面:UiModel 无 lambda + Token 驱动 ⇒ 模型/Token 层可移植
    → 工具链对齐 Kotlin 2.2.21-1.0.0(Gradle 降 8.14.1,删 daemon-jvm criteria)
    → 根构建替换(cmp_qrose 模式,Android 模块留盘零改动)
    → 核心逻辑搬移 + 四类机械替换(色值/系数逐字保留)
    → ohosArm64 / ohosX64 sharedLib + jvm() 交叉验证
    → C ABI(@CName × 1)+ 规格协议 + confirmStyle 以上游源码校准
    → C++ N-API(DisposeString 纪律)
    → ArkTS 规格驱动渲染(零 token 硬编码)
    → ArkUI 真机/真机验收(13 场景 + 5 横幅 + 5 动画 + 暗色重映射)

9.2 封装层次

  • 模型层:上游七家族 UiModel / Signal / DialogAction / DialogDismissBehavior(结构逐字);
  • 引擎层:light / dark 配色、withBrand、tone ramp(0.22/0.14)、全部 token 刻度;
  • Native 层:双 ABI 动态库并通过 1 个 C ABI 输出规格 JSON;
  • N-API 层:参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:协议类型、规格驱动渲染、signal 处理、错误兜底与桥状态卡;
  • DevEco 层:CMake、HAP、签名、安装和设备运行。

9.3 三条经验

  1. 适配切面由库的本质输出决定:静态产物(二维码)可以 KN 光栅化回传位图;交互式 UI 的正确切面是"模型/Token 留 Kotlin、渲染归平台 UI"——AestheticDialogs 2.0 的"UiModel 永不持 lambda"架构让这个切面天然干净,上游每一次迭代都能机械同步,翻成 ArkTS 就是永久分叉;
  2. 每条视觉规则都要能指回上游源码:confirmStyle 的 tone→按钮色映射(Neutral→品牌蓝、Destructive→error、Flash→透明+onAccent 描边)初版凭直觉写错了两处,全部靠读上游 variants 源码纠正——设计系统适配里"猜"是负债;
  3. 失败必须可见:兜底值用真值而不是零、错误卡硬编码颜色、hilog 打请求字节数——"静默白屏"排查花了完整一轮日志+截图+JVM 复现,这三层防线能让同类故障在第一眼就被定位。

9.4 适配成果

当前适配已完成:

  • AestheticDialogs 2.0.0-ohos.1:模型层与 Token 引擎的双 ABI Kotlin/Native 编译(包名不变、色值/系数逐字保留,上游 Android 模块零改动);
  • 1 个 C ABI 符号(AestheticDialogSpec)和 C++ N-API 包装层(llvm-nm 验证全局 T 符号,双 ABI);
  • DevEco Stage ArkUI 验收页面(13 对话框场景 + 5 横幅 + 5 动画 + 暗色重映射 + Bridge 状态卡,全部视觉值来自桥);
  • 17 项 JVM 交叉验证全绿 + HAP 构建、真机安装、点击驱动验收与效果图;真机流程同路径(自动签名 + hdc install signed hap);
  • AtomGit 项目文档(四份 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐