开源鸿蒙平台 CMP 三方库「compose-macos-26-ui」适配全流程
本文记录
compose-macos-26-ui接入 OpenHarmony 的完整过程,覆盖设计令牌提取、鸿蒙工具链钉版、纯 Kotlin 模型编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、软件光栅化渲染、ArkUI 真机页面、HAP 构建和真机验收。
compose-macos-26-ui是一个 Compose Multiplatform 的 macOS 26 风格 UI 库:30+ 个组件、明暗双主题、纯 Compose 无 Material 依赖,在 Android / iOS / Desktop / Web 上复刻 macOS 26(Liquid Glass 时代)的控件观感。它的核心资产不在控件代码本身,而在一套完整的设计令牌体系——明暗两套各 65 个具名颜色、13 个系统强调色(六变体)、约 53 个度量令牌、11 档排版标尺、5 档动画时长与 3 档弹簧参数,以及一套 SF Symbol / Lucide 双轨图标注册表。本次适配与社区早期路线(qrose / haze 的"零依赖内核提取"、KStore 的"整库真编译")一脉相承但切入点不同:上游停留在 Compose 1.12 / Kotlin 2.4.10,社区鸿蒙 CMP 只有 1.9.2,30+ 个 Compose 组件无法编进 ohos——因此本仓把设计系统里可移植、可验收的全部数据资产提取为纯 Kotlin 模型原样编译成 OpenHarmony KLIB,图标向量由 Kotlin/Native 侧 4 倍超采样软件光栅化出 PNG,ArkUI 页面作为验收宿主。中文主文档见仓库 README.OpenHarmony_CN.md,本文是其博客化实录版本。
项目地址: AtomGit/oh-tpc/compose-macos-26-ui.git
上游仓库: kdroidFilter/compose-macos-26-ui
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
compose-macos-26-ui 在 Compose 生态里提供"把应用画成 macOS 26 原生观感"这一件事的完整答案:MacosTheme 根主题一挂,色板 / 度量 / 排版 / 动效四套令牌全局生效;SegmentedControl、Sidebar、Scaffold、Popover 等 30+ 组件按 macOS HIG 对齐;SystemIcon 在 macOS 上走 SF Symbols、其余平台回落 Lucide 向量;桌面端 MacosDecoratedWindow 复刻 SwiftUI full-size content window。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套设计语言,但上游构建(Kotlin 2.4.10 / Gradle 9.3.1 / AGP 9.1.1 / Compose 1.12)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以显示色块,却无法证明这套设计令牌在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证令牌模型、图标光栅化、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 版本断层 | 上游 Compose 1.12 / Kotlin 2.4.10;社区鸿蒙 CMP 1.9.2 / HarmonyOS Kotlin 2.2.21 线。组件源码混编两端等于把上游降级重写,不是适配。 |
| 源码全是 Compose | 与 qrose(编码器纯 Kotlin)、KStore(引擎纯 Kotlin)不同,本库 commonMain 里 76 个文件全部 import androidx.compose.*,没有可直接编的"内核"。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 图标是 ImageVector | 37 个核心向量是 Compose ImageVector 构建器,鸿蒙侧需要等价的路径模型与渲染器。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:设计令牌模型与图标注册表的 ohos 编译、根构建替换(cmp_qrose 模式)、KN 软件光栅化和 C ABI/N-API 桥接层。令牌数值完全由上游定义负责(逐字提取、程序化比对),ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
Demo 提供一个真实的设计令牌浏览会话,对应桥接的全部入口:
- 色板(palette):明 / 暗切换、13 个系统强调色切换、Vibrant 鲜艳变体开关;响应明 / 暗各 65 个具名 token(名称 +
#AARRGGBB+ 上游注释)与 Zinc 灰阶 11 档,半透明色按页面底色叠加真实观感; - 图标(icons):37 个核心 Lucide 风格向量按 32 / 64 / 128 三档尺寸、跟随当前强调色着色渲染为 PNG;Tag / Ellipsis 的实心子路径、PanelLeft 的 19×19 Radix 实心、Trash 存档向量全部如实渲染;
- 目录(catalog):核心 37 条 + 扩展 218 条 SF Symbol → Lucide 名称映射,可搜索,标注"可渲染 / 仅名称";
- 度量与动效(metrics):约 53 个 pt 度量令牌(带可视化比例条)、11 档排版标尺(按真实 sp 预览)、5 档时长与 3 档弹簧参数(可试动);
- 版本握手:
macosUiVersion()返回 fork 版本号,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
op | 回显本次操作(palette / icons / metrics / catalog)。 |
colors / zinc | 转义后的 JSON 数组字符串,元素 {name, hex, note}。 |
icons / missing | 图标数组 {name, lucide, png(base64)} 与未命中名单。 |
metrics / typography / durations / springs | 四类令牌表。 |
core / extended / coreCount / extendedCount | 图标目录与计数。 |
令牌侧的完整定义(65+65 色板、13×6 强调色、vibrant 覆盖逻辑、度量 / 排版 / 动效全表、537 条归一化路径命令)全部在上游 commonMain 里逐字提取进入 .so,JVM 侧保留编译交叉验证 + 程序化数值比对,证明 ohos .so 里执行的令牌数据没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 令牌数值逐字取自上游(lightColorScheme / darkColorScheme / AccentColor / GlobalMetrics / Typography / Animations / 全部 ImageVector 文件),算法零重写;Compose 源码整体存档 src/composeMain 不编译;全仓新增源码 = 8 个纯 Kotlin 模型文件 + nativeApp 3 个桥文件。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);macos-markdown、sample、gallery-* 等纯 Compose 模块留盘不进构建。 |
| 桥接稳定 | 2 个 C ABI 和 UTF-8 JSON(扁平字符串协议),不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 渲染自包含 | KN 侧 4 倍超采样软件光栅化(贝塞尔 / 弧自适应细分、圆头描边盖印、偶奇填充)+ zlib stored 块 PNG 编码,零外部依赖。 |
| UI 完整 | 四 Tab 覆盖全部桥接入口:明暗 + 强调色 + Vibrant、三档尺寸着色图标、可搜索目录、度量 / 排版 / 动效浏览与版本握手。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit,不出现任何第三方镜像站品牌名。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。
compose-macos-26-ui的 30+ 组件不在鸿蒙编目标内;ArkTS Demo 是把桥暴露的设计令牌与图标资产画出来。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认"纯 Compose 无内核"的提取面、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、wrapper 降 8.14.1
第 3 阶段:令牌提取与建模 ── 色板/强调色/度量/排版/动效逐字提取、ImageVector 归一化为绝对坐标路径命令
第 4 阶段:桥协议与光栅化 ── 扁平 JSON 契约、@CName C ABI、4 倍超采样光栅器、stored-PNG 编码
第 5 阶段:ArkUI 宿主 ── CMake N-API、四 Tab 页面、Base64→PixelMap、版本握手
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
macosui/ 上游核心库(进 ohos 构建;commonMain=8 个纯 Kotlin 模型文件,Compose 源码存档于 composeMain/)
macosui-icons/ 上游核心图标(同上;含 37 个向量归一化注册表)
macosui-icons-extended/ 上游扩展图标(commonMain=218 条 sf→lucide 目录)
example/nativeApp/ Kotlin/Native 动态库(Bridge + Rasterizer + PngEncoder)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 四 Tab 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
macos-markdown / sample / gallery-* 纯 Compose 模块,留盘不进 settings
doc/ 本博客文章与真机效果图
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :macosui、:macosui-icons、:macosui-icons-extended 与 :example:nativeApp,rootProject.name = "ohos_compose-macos-26-ui"。上游的 AGP / Compose / detekt / vanniktech / dokka / hot-reload 插件与 buildSrc(桌面 JNI dylib 构建逻辑)全部随根构建替换剥离——本 fork 不发 Maven Central,buildSrc 若留在磁盘会被 Gradle 自动编译,直接删除。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| JDK | 21(Temurin 21.0.12,org.gradle.java.home 写入 gradle.properties) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 8.14.1(wrapper 从上游 9.3.1 降级;2.2.21 旧线只认 8.x,与 KStore 的 2.4.20 新线留 9.x 是两条不互通的经验) |
| 依赖 | 库模块与 nativeApp 零第三方依赖(纯 Kotlin stdlib),没有制品线可达性问题 |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.macosui |
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
注意 gradle.properties 里的 org.gradle.java.home 只对已启动的 Gradle 守护进程生效,gradlew.bat 启动 wrapper 本身仍要靠环境变量 JAVA_HOME——这是 Windows 上的第一个坑(见 8.1 第 1 条)。
1.3 源码依赖度盘点
| 模块 | 源码形态 | 依赖形态 | 处置 |
|---|---|---|---|
macosui | 76 个文件(56 组件 + 18 主题 + 4 字体) | 100% import androidx.compose.* | Compose 源码整体 git mv 进 src/composeMain(非源集、不编译、保留历史);commonMain 重建为纯 Kotlin 模型 |
macosui-icons | 39 个文件(Icons 注册表 + expect/actual + 37 向量) | ImageVector / UIKit / JNI | 向量数据归一化提取进 commonMain;SF Symbols 加载链(仅 macOS/iOS 生效)随平台源集存档 |
macosui-icons-extended | 219 个文件 | icons-lucide-cmp(Compose 图标库) | 提取 218 条 SystemIcon("sf", Lucide.X) 名称映射;lucide-cmp 向量不在本仓,目录标注"仅名称" |
macos-markdown / sample / gallery-* | 纯 Compose / KSP | mikepenz 渲染器、AGP、GraalVM | 留盘不进 settings |
提取出的 8 个模型文件(全仓唯一新增库源码):
macosui/src/commonMain/.../model/
MacosAccentColor.kt 13 个强调色 × (light/dark/containerLight/containerDark/vibrantLight/vibrantDark)
MacosColorModel.kt light/darkColorTokens() 各 65 token、zincTokens()、applyVibrant()
MacosMetricsModel.kt GlobalMetrics 全字段 + 控件尺寸分组 + Shapes 圆角,约 53 个 pt 令牌
MacosTypographyModel.kt 11 档排版标尺(34sp largeTitle → caption)
MacosAnimationModel.kt 5 档时长 + 3 档弹簧(Snappy/Smooth/Gentle)
macosui-icons/src/commonMain/.../icons/
VectorIconData.kt PathCmd(MoveTo/LineTo/QuadTo/CubicTo/ArcTo/Close,绝对坐标)+ VectorIcon
CoreIconRegistry.kt 37 个 NamedVectorIcon,537 条命令,含 filled 子路径标记
macosui-icons-extended/src/commonMain/.../icons/extended/
ExtendedIconCatalog.kt 218 条 sf→lucide 映射
两个提取时的真实现象值得记录:其一,上游 Icons.kt 实际挂载 36 个图标,Trash.kt 定义的向量(20×21 视口、实心填充、Color(0xFF1A1A1A))是未被引用的孤儿——本仓照样收录进注册表,sf 名如实留空,不在数据层"补"一个名字;其二,并非全部向量都是描边风格:Tag / Ellipsis 含实心子路径、PanelLeft 是 19×19 的 Radix 实心图标(带 group(translationX=2f, translationY=2f) 平移,需折算进绝对坐标)、Trash 无描边——IconSubPath 因此加了 filled: Boolean 字段,光栅化按子路径而非按图标决策。
1.4 页面边界
标题区 macosui · OpenHarmony / 版本握手行(Native 版本握手: 1.1.0-ohos.1)
Tab 切换 色板 / 图标 / 目录 / 度量与动效
色板 Tab 明暗 Toggle、13 强调色横滑圆点、Vibrant 开关、65 项色板 Grid + Zinc 灰阶节
图标 Tab 32/64/128 三档尺寸、37 个图标 Grid(跟随强调色着色)、missing 提示
目录 Tab 搜索框 + List 懒渲染(核心/扩展分节、可渲染/仅名称标注)
度量 Tab 度量比例条、排版真实字号预览、时长点击试动、弹簧参数 + springMotion 试一试
关于区 版本握手、库定位、架构分层、仓库与社区链接(仅 AtomGit)
页面启动时先调 macosUiVersion() 做握手,再按 Tab 懒加载数据。
第 2 阶段:目标与依赖打通
2.1 依赖形态判断(本适配的路线分歧点)
与 KStore 不同,本库没有任何 kotlinx 依赖——上游 commonMain 的 import 全部指向 androidx.compose.*。这意味着:
- 没有"换线不换版"的整库编译选项:社区鸿蒙 CMP 1.9.2 对应的 Compose 编译器线最多到 1.9.x,上游代码用满 Compose 1.12 API,把上游降级到 1.9.2 等于重写一遍组件库,“适配"变成"另起炉灶”,恰恰是社区审核不鼓励的路线;
- 也没有内核可直接抽:qrose 有纯 Kotlin 编码器、KStore 有纯 Kotlin 存储引擎,本库连
Color都是 Compose 类型; - 但有等价物:设计系统的价值在令牌数据而非控件实现。色板 / 强调色 / 度量 / 排版 / 动效参数是纯数值;图标是几何路径数据。把数据层从 Compose 类型里"脱壳"成纯 Kotlin,就是本库的"内核提取"。
结论:走 cmp_qrose 已审核的令牌提取 + 光栅化渲染桥路线,Kotlin 钉 2.2.21-1.0.0(无第三方依赖拖版本,旧线即可),Gradle 降 8.14.1。
2.2 工具链钉版与根构建替换
settings.gradle.kts 把 eazytec 仓库写在最前(配置期找不到 ohosArm64 扩展的,九成是这步没做):
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()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
google()
mavenCentral()
}
}
rootProject.name = "ohos_compose-macos-26-ui"
include(":macosui")
include(":macosui-icons")
include(":macosui-icons-extended")
include(":example:nativeApp")
gradle/libs.versions.toml 精简到只剩一个插件;gradle.properties 写入 JDK 21 路径与 kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true;wrapper 的 distributionUrl 改为 gradle-8.14.1-bin.zip。根构建收敛为版本号(1.1.0-ohos.1,取上游最新 tag v1.1.0)+ 全局禁用 Ohos*Test* 任务。
2.3 编译纯 Kotlin 模型
三个库模块收敛为同一份纯 KMP 构建脚本:
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
applyDefaultHierarchyTemplate()
jvmToolchain(21)
jvm()
ohosArm64()
ohosX64()
sourceSets {
commonMain.dependencies {}
}
}
实测(Windows / Git Bash):
./gradlew.bat :macosui:compileKotlinJvm :macosui-icons:compileKotlinJvm :macosui-icons-extended:compileKotlinJvm # BUILD SUCCESSFUL
./gradlew.bat :macosui:compileKotlinOhosArm64 :macosui:compileKotlinOhosX64 # BUILD SUCCESSFUL(×3 模块)
第 3 阶段:令牌提取与桥协议建模
3.1 提取纪律
- 逐字保真:65+65 色板、13×6 强调色、11 档 Zinc、全部度量 / 排版 / 动效数值从上游文件程序化比对(值 + 顺序 + note),
0xFF0088FF一个十六进制位都不许漂; - 路径归一化:
moveTo/horizontalLineToRelative/arcToRelative/...全部转绝对坐标;arcTo的终点本就是绝对、arcToRelative是偏移;close()后当前点重置为子路径起点;每条path { }块 = 一个IconSubPath; - 如实存疑:Trash 孤儿向量
sf=""、扩展目录 7 个 SF 名一对多(上游本就如此)、lucideName 保留上游标识符原样(Volume1不强行转volume-1)——不在数据层做上游没做的决定。
3.2 JSON 契约
dev.nucleusframework.macoscompose.{model,icons}/**(纯 Kotlin 令牌模型,原样编译)
↓ 色板/度量/排版/动效/图标注册表
Bridge.kt(@CName × 2,扁平 JSON 进出)
↓ UTF-8 JSON
N-API macosUiVersion() / macosUiDispatch()
↓
ArkUI Index.ets(四 Tab 懒加载)
请求与响应(全扁平字符串,嵌套载荷以转义 JSON 数组字符串内嵌,ArkTS 二次 JSON.parse):
请求 : {"op":"palette","scheme":"dark","accent":"blue","vibrant":"1"}
{"op":"icons","size":"64","color":"#FF0A99FF","names":""} # names 空 = 全部 37 个
{"op":"metrics"}
{"op":"catalog"}
响应 : {"ok":true,"op":"palette",...,"colors":"[{\"name\":\"primary\",\"hex\":\"#FF1C1C1E\",\"note\":\"\"},...]","zinc":"[...]"}
{"ok":true,"op":"icons","icons":"[{\"name\":\"house\",\"lucide\":\"house\",\"png\":\"<base64>\"},...]","missing":""}
{"ok":false,"error":"unknown op: ..."}
icons 的 color 支持 #RRGGBB / #AARRGGBB;size 钳位 4…512;未命中图标进 missing 不算失败——和 qrose 的错误通道设计一致:非法 JSON、非法 scheme / accent / color、未知 op、光栅化异常,全部走同一个 ok:false 通道,ArkTS 侧统一渲染错误横幅。
3.3 光栅化为什么自己写
HarmonyOS ArkTS 有 Canvas / Path2D,理论上可以在 ArkTS 侧画向量。选择 KN 侧自绘有三个理由:其一,qrose / Vico / Reorderable 已审核的路径就是"KN 光栅化 + PNG + NAPI",本仓保持同构;其二,向量数据留在 Kotlin 侧渲染,ArkTS 只消费位图,令牌与渲染责任不跨语言边界;其三,渲染质量可控——4 倍超采样盒式滤波的输出在任何 DPI 下都是确定性的。
第 4 阶段:原生桥接与光栅化(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libmacosui_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosmacosui.so)
│ MacosUiDispatch / MacosUiVersion
▼
UTF-8 JSON + DisposeString
4.2 Kotlin/Native 导出函数
@CName("MacosUiVersion")
fun macosUiVersion(): String = "1.1.0-ohos.1"
@CName("MacosUiDispatch")
fun macosUiDispatch(request: String): String = try {
when (MiniJson.parse(request).str("op")) {
"palette" -> palette(json) // 65 token + zinc + vibrant 覆盖
"icons" -> icons(json) // 37 向量 → PNG base64
"metrics" -> metrics() // 度量/排版/时长/弹簧
"catalog" -> catalog() // 37 + 218 目录
else -> fail("unknown op")
}
} catch (t: Throwable) {
"""{"ok":false,"error":${jsonStr(t.message ?: t.toString())}}"""
}
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
MacosUiDispatch | macosUiDispatch(req) | 四 op 一口进:令牌模型 + 光栅化 + PNG |
MacosUiVersion | macosUiVersion() | fork 版本握手(真机版本行的数据来源) |
4.3 光栅化实现要点
- 超采样:4× 渲染后 4×4 盒式滤波降采样,512px 上限(2048² IntArray ≈ 16 MB);
- 曲线细分:二次 / 三次贝塞尔按设备空间控制多边形长度自适应(步长约 1 个超采样像素,钳位 16…128 步);
- 弧:W3C SVG 端点参数化 → 中心参数化(含 F.6.6 半径放大、零半径退化),≤90° 分段,手柄系数
k = 4/3·tan(Δ/4)(90° 时恰为 0.552284749831),再走同一自适应细分; - 描边:圆头 / 圆角通过"沿折线以 ≤ strokeWidth/3 步长盖印实心圆"天然获得;
- 填充:实心子路径(Tag / Ellipsis / PanelLeft / Trash)走偶奇规则扫描线多边形填充——按子路径决策而非按图标,因为同一图标里两种风格共存;
- PNG:RGBA + filter 0 + zlib stored 块(未压缩),CRC32 查表 + adler32 手写,KN 侧无
java.util.zip的自包含等价物。
4.4 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"macosUiDispatch", nullptr, NapiMacosUiDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"macosUiVersion", nullptr, NapiMacosUiVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosmacosui_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosmacosui_api.h 手写只声明 ExportedSymbols 结构前缀(DisposeStablePointer / DisposeString 两个函数指针,与 KN 生成的完整头前缀布局一致)与两个业务符号。
CMake 里 KN so 以 IMPORTED 引入(路径 entry/libs/${OHOS_ARCH}/libohosmacosui.so,缺失 FATAL_ERROR 提示先跑 Gradle link 任务),$ORIGIN rpath 三重设置 + POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录:
export const macosUiDispatch: (request: string) => string;
export const macosUiVersion: () => string;
第 5 阶段:ArkUI 宿主
aboutToAppear(): void {
this.version = macosUiVersion(); // 版本握手:来自真实 Native 调用
this.loadPalette();
}
private loadPalette(): void {
const body: string = JSON.stringify({
op: 'palette', scheme: this.scheme, accent: this.accent, vibrant: this.vibrant ? '1' : '0'
});
const res = JSON.parse(macosUiDispatch(body));
this.colors = JSON.parse(res.colors); // 二次 parse:嵌套载荷是转义数组串
}
图标 Tab 的解码链沿用 qrose 验证过的路径:util.Base64Helper().decodeSync(png) → bytes.buffer.slice(...) → image.createImageSource(...) → createPixelMap() → Image(this.iconPixels.get(name));37 个图标并行解码,Map 整体重赋值驱动 @State 刷新。ok:false 时错误原文进统一横幅;Native 调用本身抛错同样被 try/catch 收进横幅。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohosmacosui)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ ├── Bridge.kt @CName × 2 + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
│ ├── Rasterizer.kt 4× 超采样 + 贝塞尔/弧细分 + 描边盖印 + 偶奇填充
│ └── PngEncoder.kt stored-PNG(CRC32 + adler32)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.macosui、versionName 1.1.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/libohosmacosui_api.h
├── src/main/cpp/types/libmacosui_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
export JAVA_HOME="/path/to/jdk-21" # Windows:$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
./gradlew.bat :macosui:compileKotlinJvm :macosui-icons:compileKotlinJvm :macosui-icons-extended:compileKotlinJvm
./gradlew.bat :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 中勾选自动生成,证书写入本机 ~/.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.macosui -m entry
四、完整代码对照
4.1 整体架构
上游 theme / icons 定义(Compose 类型)
→ 逐字提取为 8 个纯 Kotlin 模型文件(值零漂移)
→ ohosArm64 / ohosX64 KLIB + jvm 交叉验证
example/nativeApp(Bridge + Rasterizer + PngEncoder)
│ MacosUiDispatch / MacosUiVersion
libmacosui_napi.so
│ macosUiDispatch / macosUiVersion
Index.ets
│ 双重 JSON.parse + Base64 → PixelMap
▼
ArkUI 真机四 Tab(色板 / 图标 / 目录 / 度量与动效)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_compose-macos-26-ui、只 include 4 模块 |
build.gradle.kts(根) | 版本 1.1.0-ohos.1、全局禁用 Ohos*Test* 任务 |
gradle/libs.versions.toml + gradle.properties + wrapper | Kotlin 2.2.21-1.0.0、JDK 21、Gradle 8.14.1、KN 分发开关 |
三个库模块 build.gradle.kts | 纯 KMP:jvm + ohosArm64 + ohosX64,零依赖 |
macosui/src/commonMain/.../model/*.kt(5 个) | 色板 / 强调色 / 度量 / 排版 / 动效令牌 |
macosui-icons/src/commonMain/.../icons/{VectorIconData,CoreIconRegistry}.kt | 路径命令模型 + 37 向量注册表 |
macosui-icons-extended/.../ExtendedIconCatalog.kt | 218 条 sf→lucide 目录 |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../{Bridge,Rasterizer,PngEncoder}.kt | 2 个 C ABI、扁平 JSON、4× 超采样光栅、stored-PNG |
harmonyApp/.../cpp/{CMakeLists.txt,napi_init.cpp,include/libohosmacosui_api.h} | IMPORTED KN so、N-API 导出、DisposeString |
harmonyApp/.../ets/pages/Index.ets | ArkUI 四 Tab 验收页面 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(22 节 / 6300+ 汉字) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 模型 | lightColorTokens(accent) / darkColorTokens(accent) | 明 / 暗各 65 具名 token(对应上游 light/darkColorScheme 工厂全参数) |
| 模型 | applyVibrant(tokens, accent, dark) | 对齐上游 ColorScheme.vibrant() 的 10 字段替换 |
| 模型 | metricTokens() / typographyTokens() / durationTokens() / springTokens() | 度量 / 排版 / 时长 / 弹簧全表 |
| 模型 | CoreIconRegistry.all / bySfName() | 37 个归一化向量(含 filled 子路径) |
| 模型 | ExtendedIconCatalog.entries | 218 条 sf→lucide 目录 |
| 渲染 | rasterize(icon, sizePx, argb) | 4× 超采样 → 盖印描边 / 偶奇填充 → 盒式降采样 |
| 渲染 | PngEncoder.encode(image) | RGBA → stored-PNG |
| Native | MacosUiDispatch / MacosUiVersion | C ABI JSON 导出 |
| N-API | macosUiDispatch(req) / macosUiVersion() | 向 ArkTS 暴露能力 |
| ArkTS | loadPalette() / loadIcons() / loadCatalog() / loadMetrics() | 触发四 op 并刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(macosUiDispatch(JSON.stringify({
op: 'palette', scheme: 'dark', accent: 'blue', vibrant: '1'
})));
const colors = JSON.parse(res.colors);
// colors[0] === { name: 'primary', hex: '#FFF5F5F5', note: '' }
// Vibrant 开启时 tertiary === '#FF0A99FF'(Blue.vibrantDark)——真机截图可证
val tokens = darkColorTokens(MacosAccentColor.Blue)
.let { if (vibrant) applyVibrant(it, MacosAccentColor.Blue, dark = true) else it }
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:提取设计令牌,而不是把组件降级到鸿蒙 CMP 1.9.2
上游在 Compose 1.12 / Kotlin 2.4.10,社区鸿蒙 CMP 停在 1.9.2。把 30+ 组件降到 1.9.2 API 等于重写组件库,此后永远追不上上游;而设计系统的长期价值(色板、度量、排版、动效、图标资产)与组件实现解耦。提取让"macOS 26 设计语言在鸿蒙可用"这件事先成立,等社区 CMP 与上游对齐后,composeMain 可以整体接回(见 README.OpenHarmony_CN.md 第 22 节)。
决策 2:工具链钉 2.2.21-1.0.0 + Gradle 8.14.1(与 KStore 相反的选择)
本仓零第三方依赖,没有"工具链跟着依赖走"的约束,直接采用社区最早批准的 qrose 钉法;Gradle 按 2.2.21 旧线惯例从 9.3.1 降 8.14.1。KStore 因为 kotlinx 制品线(stdlib 2.4.20 元数据)必须用新线,两条工具链线的经验不能互相套用——同一个社区里两条钉法并存,判断依据始终是依赖矩阵而非惯性。
决策 3:根构建替换(cmp_qrose 模式),纯 Compose 模块留盘
上游根构建(AGP / detekt / dokka / vanniktech / hot-reload / buildSrc JNI)对鸿蒙没有保留价值,整个替换为极简 ohos 构建;macos-markdown、sample、gallery-* 是纯 Compose / KSP 模块,鸿蒙没有对应编译面,留盘不进 settings.gradle.kts——它们的存在方式只有这一种。
决策 4:单 JSON 出入口 + 双重 parse
四 op 一口进出、嵌套载荷以转义数组字符串内嵌,是 qrose 审核模式的直接继承:桥不引入 schema 序列化框架(KN 侧手写 MiniJson,约 80 行),ArkTS 侧 JSON.parse 两次到位。代价是嵌套层多一层转义,换来的是桥上任何一个字段都能用字符串日志完整还原。
决策 5:JVM 交叉验证 + 程序化数值比对
令牌逻辑在 commonMain,JVM 与 ohos 编的是同一份代码;提取阶段用脚本把 65+65 色板、13×6 强调色、11 Zinc、37 向量命令按"值 + 顺序 + 计数"与上游源文件比对(ArcTo 134 / Close 33 / CubicTo 15 / LineTo 201 / MoveTo 104 / QuadTo 50 = 537),这是"数据没漂"的闸门——设计令牌库的交叉验证重点天然落在数值保真而不是行为测试上。
六、测试与验证
6.1 测试环境
本次验证使用 Windows 11 x64(Git Bash)、JDK 21(Temurin 21.0.12)、HarmonyOS Kotlin 2.2.21-1.0.0、Gradle 8.14.1、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。Kotlin/Native 交叉编译在 Windows 上完成(~/.konan 的 ohos sysroot 与 cpf-llvm 工具链),无需 macOS。
6.2 JVM 交叉验证
./gradlew.bat :macosui:compileKotlinJvm :macosui-icons:compileKotlinJvm :macosui-icons-extended:compileKotlinJvm
三个模块编译通过;提取数据与上游源文件的程序化比对全绿:
| 比对项 | 结果 |
|---|---|
| 明 / 暗色板 65+65 token(值 + 顺序 + note) | 一致 |
| 13 个强调色 × 6 变体 | 一致 |
| Zinc 灰阶 11 档 | 一致 |
| 度量 / 排版 / 动效全表 | 一致 |
| 37 向量 537 条命令(按类型计数 + 10 图标逐命令抽查) | 一致 |
| 扩展目录 218 条 | 一致 |
6.3 HAP 验证
:macosui / :macosui-icons / :macosui-icons-extended compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
llvm-nm -D: MacosUiDispatch / MacosUiVersion / libohosmacosui_symbols 均为全局 T 符号(双 ABI)
BuildNativeWithCmake: finished(双 ABI 编出 libmacosui_napi.so)
CompileArkTS: finished(修复 tabIndex / Scroll 两处 API 兼容后)
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
libohosmacosui.so(debugShared,含 Kotlin/Native 运行时)arm64 约 3.2 MB、x86_64 约 2.6 MB,随配套 triple 的 libc++_shared.so(约 1.8 / 1.7 MB)一起进 HAP;entry/libs/ 由 Gradle copyKnSo* 任务自动回填,不入库。
6.4 功能用例
- 启动后页头出现
Native 版本握手: 1.1.0-ohos.1(来自真实 Native 调用,非静态文字); - 色板 Tab 默认深色 + blue:
primary #FFF5F5F5、background #FF1C1C1E等 65 项与上游 darkColorScheme 逐一对应; - 打开 Vibrant:
tertiary变为#FF0A99FF(Blue.vibrantDark)、error变为鲜艳红——vibrant 覆盖链路在真机生效(截图可证); - 切换 13 个强调色任一(如 pink),accent 系 token(accent / info / ring / inputFocusBorder / surfaceTint / tertiary / 两个 container)即时重算;
- 图标 Tab 37 个图标按当前强调色着色渲染,32 / 64 / 128 三档切换重新光栅化;Tag 的实心子路径、PanelLeft 实心面板渲染正确;
- 目录 Tab 搜索
arrow:核心 + 扩展中所有 sf 名含 arrow 的条目过滤展示,local标注与实际可渲染一致; - 度量 Tab:
defaultComponentHeight 22pt等比例条、largeTitle 34sp真实字号预览、点击时长档触发试动动画; - 传入非法 accent(如
{"op":"palette","accent":"silver"})返回ok:false,错误横幅显示unknown accent: silver,不崩溃。
6.5 验证结论
令牌模型、图标注册表、KN 光栅化、PNG 编码、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的设计令牌会话:版本握手、明暗 + 13 强调色 + Vibrant 的色板矩阵、着色光栅化图标、255 条目录与度量 / 动效浏览(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/compose-macos-26-ui.git
cd compose-macos-26-ui
export JAVA_HOME="/path/to/jdk-21" # Windows PowerShell:$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
./gradlew.bat :macosui:compileKotlinOhosArm64 :macosui-icons:compileKotlinOhosArm64 :macosui-icons-extended:compileKotlinOhosArm64 # klib
./gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64 # so 三件套回填
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.macosui -m entry
hdc shell hilog | grep MacosUiNapi
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让库模块真正编译成
ohosArm64/ohosX64——本仓连"库"带 Demo 一起换了内核形态:令牌提取 + 光栅化,组件源码存档; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;gradle.properties的org.gradle.java.home管不到 wrapper 启动:gradlew.bat先读环境变量JAVA_HOME,Windows 上忘了 export 就是JAVA_HOME is not set——构建脚本与文档里两处都要写;buildSrc不能"留着不用":只要目录存在 Gradle 就会自动编译它,根构建替换后它与 ohos 工具链无关,直接删除才是干净解;- 代码生成器产出的
IconSubPath(listOf(..., filled = true))把命名参数写进了 vararglistOf,K2 只报None of the following candidates is applicable不报根因——生成路径数据后必须编译验证,人工目检 3000 字符长行看不出这种错; - 光栅化器最初按"整图标 strokeWidth 决定描边 / 填充",但真实数据是按子路径混合(Tag 同图标内两种风格并存、PanelLeft/Trash 整体实心且 strokeWidth=0)——决策粒度要对齐数据粒度,否则实心部分会被画成 2px 空心轮廓;
strokeWidth = 0f是实心图标的语义值,描边步长strokeWidth/3若不设下限就是除零 / 死循环,盖印步长必须coerceAtLeast;- ArkTS 的
@State tabIndex撞了CustomComponent基类的通用属性名(基类里它是函数类型),编译期报 not assignable——自定义状态名要避开 ArkUI 通用属性; Scroll({ horizontal: true })在新 SDK 已废弃(签名只剩Scroller),横向滚动改Scroll().scrollable(ScrollDirection.Horizontal)——旧示例代码照抄不过编译;- 弧命令语义:Compose
arcTo的终点坐标是绝对、arcToRelative是偏移,混看会把弧画到视口外;W3C F.6.6 的半径放大步骤漏掉时,扁弧(rx/ry 被压缩过的)会渲染成直线; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free();手写*_api.h只保留 ExportedSymbols 前缀两个函数指针是安全的(结构体前缀布局兼容); - HAP 缺当前 ABI 的 so 报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;entry/libs/是构建产物,gitignore 挡住,仓库里只有生成它的 Gradle 任务。
8.2 已知问题
- 30+ Compose 组件不在鸿蒙编目标内(
composeMain存档);若社区 CMP 与 Compose 1.12 对齐可整体接回; - Manrope 字体未进 HAP,排版 Tab 用系统字体按 sp 预览;
- 扩展目录 218 条仅 sf→lucide 名称映射(lucide-cmp 向量不在本仓,Demo 标"仅名称");
- Trash 为上游未挂载的存档向量(
sf留空如实呈现);扩展目录 7 个 SF 名一对多为上游原状; - vibrant 只覆盖上游
ColorScheme.vibrant()实际替换的 10 个字段; - 单次 icons 请求 size 上限 512px(超采样内存钳位);
- android / js / wasmJs / apple 目标剥离(源文件留在树里),发布、dokka、detekt 一并剥离,本 fork 不发 Maven Central;
- CI 未加 ohos 构建(与 qrose 一致的已知空白,列入下一步)。
8.3 未来优化方向
- 补充 x86_64 模拟器四 Tab 全量走查与自动化截图(uitest + capture 脚本);
- 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 光栅化器输出缓存(name@size@color 三元组),减少重复请求的 PNG 编码开销;
- 若社区出现 ohos 的 Compose 1.12 线,把
composeMain接回commonMain,光栅化降级为图标导出工具。
九、总结
9.1 核心难点回顾
上游 30+ 组件全部 import androidx.compose.*
→ 判定"组件降级重写"不可行,锚定设计令牌为提取面
→ 工具链钉 Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(qrose 旧线)
→ 根构建替换(cmp_qrose 模式,composeMain 存档隔离)
→ 8 个纯 Kotlin 模型文件(值零漂移 + 程序化比对)
→ ohosArm64 / ohosX64 KLIB + 双 ABI KN 动态库
→ C ABI(@CName × 2)+ 4× 超采样光栅化 + stored-PNG
→ C++ N-API(DisposeString 纪律)
→ ArkTS 四 Tab 真机验收(色板 / 图标 / 目录 / 度量动效)
9.2 封装层次
- 模型层:色板 / 强调色 / 度量 / 排版 / 动效 / 图标注册表,纯 Kotlin,逐字保真;
- 渲染层:路径归一化 + 贝塞尔 / 弧细分 + 描边盖印 / 偶奇填充 + PNG 编码,全部在 KN 侧自包含;
- Native 层:双 ABI 动态库,2 个 C ABI 输出 JSON;
- N-API 层:参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:四 Tab 状态管理、双重 JSON.parse、Base64→PixelMap、错误横幅;
- DevEco 层:CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 纯 Compose 库的适配面在数据不在代码:当组件实现无法跨 Compose 版断层时,先把设计系统的令牌与几何资产"脱壳"成纯 Kotlin——这套判断同样适用于其他主题 / 图标 / 设计规范类 CMP 库;
- 提取类适配的验收闸门是数值保真:整库编译靠上游测试证明没漂(KStore),令牌提取靠程序化比对证明没漂(本仓)——65+65 色板、13×6 强调色、537 条路径命令逐一比对通过,比任何肉眼走查都可信;
- 两条工具链线的选择依据是依赖矩阵:零依赖选旧线(2.2.21 + Gradle 8.14.1),kotlinx 新制品线选新线(2.4.20 + Gradle 9.x)——把"社区钉法"当成单一答案是这类适配里最容易被带偏的地方。
9.4 适配成果
当前适配已完成并推送(提交 dc47a32 鸿蒙适配,作者 Everlasting / oh-tpc,416 文件 +4727 / −986):
compose-macos-26-ui1.1.0-ohos.1设计令牌体系的双 ABI Kotlin/Native 编译(明 / 暗各 65 token、13×6 强调色、vibrant、约 53 度量、11 排版、5 时长 3 弹簧、37 向量 537 命令、218 条目录);- 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证双 ABI 全局 T 符号); - KN 侧 4 倍超采样软件光栅化器 + stored-PNG 编码器(描边 / 填充混合风格全支持);
- DevEco Stage ArkUI 四 Tab 验收页面(版本握手、明暗 + 13 强调色 + Vibrant、着色图标、可搜索目录、度量动效);
- JVM 交叉验证 + 程序化数值比对全绿;HAP 构建(双 ABI 三件套)、签名、真机安装和真机验证(见 7.1 效果图);
- AtomGit 项目文档、OpenHarmony README 四件套(CN 主文档 6300+ 汉字 / 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。
参考文档
更多推荐



所有评论(0)