开源鸿蒙平台 CMP 三方库「compose-cupertino」适配全流程
本文记录
compose-cupertino接入 OpenHarmony 的完整过程,覆盖 CMP 组件库设计令牌盘点、ohosArm64/ohosX64双目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。
compose-cupertino是 iOS 风格的 Compose Multiplatform 组件库(Cupertino 主题、控件与 SF Symbols 风格图标)。上游公开 API 全部是@Composable函数,无法在鸿蒙定制工具链下直接编译。本次适配把它的设计语言——ColorScheme 19 槽位、系统色 18 个、legacy 常量色 15 个、字阶 11 档、圆角 5 档、组件度量 21 项和 67 个图标矢量路径——1:1 提取进 OpenHarmony ARM64 运行时,ArkUI 页面作为真机验收宿主。
项目地址: AtomGit/oh-tpc/compose-cupertino
上游仓库: alexzhirkevich/compose-cupertino
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
compose-cupertino 为 Compose Multiplatform 提供 CupertinoTheme、CupertinoButton、CupertinoSwitch、CupertinoSegmentedControl、CupertinoNavigationBar、Material3 自适应层和 800+ SF Symbols 风格图标。CMP 项目把这些 UI 能力放在 commonMain,由 Android、iOS、桌面、Web 或其他宿主共享。OpenHarmony 应用同样需要这套 Cupertino 设计语言,但上游 9 个 target 里没有 ohos,默认构建不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以显示色块,却无法证明这份设计语言的数据真的来自上游库源码、并在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证令牌提取、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 上游无 ohos target,必须增加 ohosArm64()/ohosX64() 才能生成 OpenHarmony 动态库。 |
| 工具链不一致 | 上游 Kotlin 2.0.20 / CMP 1.7.0 与鸿蒙定制 Kotlin 2.2.21-1.0.0 分属两条插件线,不能共存于同一个 Gradle 构建。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| UI 依赖 Compose 运行时 | @Composable 组件无法脱离 compose.runtime 编译;可移植的是设计令牌与矢量数据。 |
| 令牌一致性 | 颜色、字阶、度量必须与上游源码 1:1,ArkTS 侧不允许手写副本。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在独立 example 构建、令牌 1:1 提取、C ABI/N-API 桥接和 ArkUI 验收层。设计语义仍由上游源码提取的令牌负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
示例提供四个可观察场景:
- 主题:ColorScheme 19 槽位、系统色 18 个、legacy 常量色 15 个、圆角 5 档,支持明暗与高对比度切换;
- 字阶:largeTitle 34sp 到 caption2 11sp 共 11 档,按各自字号、行高、字重实时预览;
- 图标:CupertinoIcons 核心集 67 个(filled 24 + outlined 43),矢量路径在 Canvas 上回放;
- 组件:Switch、分段控件、滑块、四档按钮和导航栏,全部由组件度量令牌驱动。
ThemeResult 包含:
| 字段 | 作用 |
|---|---|
mode | light/dark 两套配色。 |
highContrast | 高对比度档位(上游 Accessibility 分支)。 |
colorScheme | 19 个槽位的 #AARRGGBB 值。 |
systemColors | 18 个 iOS 系统色(systemRed…systemGray6)。 |
legacyColors | 15 个上游常量色(Black、Gray、BrightSeparator 等)。 |
textStyles | 11 档 fontSize/lineHeight/fontWeight。 |
shapes | 5 档圆角(4/8/12/16/24dp)。 |
components | 21 项组件度量(dp)。 |
buttonSizes | 4 档按钮内边距与圆角映射。 |
defaultAlphaPercent | 上游 DefaultAlpha(内容色 10% alpha)。 |
桥还提供 CupertinoIcon({"list":true}) 图标清单和 CupertinoVersion() 版本检视入口,供验收页核对数据规模与版本。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 令牌与图标路径全部提取自上游 cupertino-core/cupertino 源码文件,上游仓库零改动。 |
| 平台目标 | 独立 example 构建加入 ohosArm64 + ohosX64,生成双 ABI 动态库。 |
| 桥接稳定 | 使用少量 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址。 |
| CMP 兼容 | 上游 CMP 工程保持原样可用;ArkUI 只负责真机验收;社区 CMP 工具链对齐后可接回 @Composable 本体。 |
| UI 完整 | 支持明暗切换、高对比度、字阶预览、图标变体切换和组件交互。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。上游 CMP 工程零改动;鸿蒙 Demo 验证的是同一份设计令牌的 OpenHarmony Native 链路。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点源集、令牌出处和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── 独立 example 构建、ohosArm64/ohosX64 和双 ABI 产物
第 3 阶段:设计令牌建模 ── ColorScheme/系统色/字阶/圆角/度量/图标路径和 JSON 契约
第 4 阶段:原生桥接 ── C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 主题/字阶/图标/组件四 Tab、Canvas 回放和错误边界
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
(仓库根) 上游库模块:Kotlin 2.0.20 / CMP 1.7.0,零改动
cupertino-core/ ColorScheme / CupertinoColors / Typography / Shapes(令牌出处)
cupertino/ 上游组件 + 核心图标 67 个(度量与图标出处)
cupertino-adaptive/ Material3 ↔ Cupertino 自适应层(未入桥)
cupertino-icons-extended/ 扩展图标 766 个(未入桥)
example/ 独立 OHOS Gradle 构建(Gradle 8.14.1 + 鸿蒙 Kotlin)
example/nativeApp/ 设计令牌 + @CName 桥(Kotlin/Native 动态库)
example/harmonyApp/ DevEco Stage、N-API 和 ArkTS 页面
doc/ 适配文章与效果图
example 是独立 Gradle 工程(自带 wrapper 8.14.1),不把 DevEco 工程作为根项目的 Kotlin 子模块,Gradle 和 Hvigor 可以分开执行。根构建保持上游原样——上游 Kotlin 2.0.20 与鸿蒙定制 Kotlin 2.2.21-1.0.0 不能强行融合,这是本次适配最重要的边界决策。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | OpenHarmony 定制版 2.2.21-1.0.0(eazytec Maven) |
| Gradle(example 构建) | 8.14.1 |
| JDK | 21 |
| OpenHarmony 目标 | ohosArm64 + ohosX64 |
| DevEco product | 5.1.0(18) |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.cupertino |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 页面边界
标题区 COMPOSE-CUPERTINO / Cupertino 设计令牌工作台
主题区 ColorScheme 19 槽位 / 系统色 18 / legacy 15 / 圆角 5
字阶区 largeTitle 34sp → caption2 11sp 按档实时预览
图标区 CupertinoIcons 核心集 67 个,Canvas 路径回放
组件区 Switch / 分段控件 / 滑块 / 按钮 / 导航栏
导航区 主题 | 字阶 | 图标 | 组件 四 Tab
页面启动时通过 cupertinoTheme() 读取令牌并渲染,不在 ArkTS 侧手写任何 Cupertino 色值。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 / ohosX64
example/nativeApp/build.gradle.kts 只声明鸿蒙目标,业务源码放在默认层级模板提供的 ohosMain:
kotlin {
applyDefaultHierarchyTemplate()
compilerOptions {
optIn.add("kotlin.experimental.ExperimentalNativeApi")
}
ohosArm64 {
binaries.sharedLib {
baseName = "ohoscupertino"
}
}
ohosX64 {
binaries.sharedLib {
baseName = "ohoscupertino"
}
}
}
2.2 构建 Native 产物
cd example
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
链接任务通过 finalizedBy 的 Copy 任务自动准备(同时从 ~/.konan/dependencies 取同 triple 的 libc++_shared.so):
example/harmonyApp/entry/libs/arm64-v8a/libohoscupertino.so ≈7.6 MB
example/harmonyApp/entry/libs/arm64-v8a/libc++_shared.so
example/harmonyApp/entry/libs/x86_64/libohoscupertino.so ≈6.7 MB
example/harmonyApp/entry/libs/x86_64/libc++_shared.so
nm 验证两个 ABI 均导出 CupertinoTheme、CupertinoIcon、CupertinoVersion 和 libohoscupertino_symbols。
2.3 独立消费工程
example/settings.gradle.kts 把鸿蒙 Maven 私服放在 gradlePluginPortal 之前:
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_cupertino"
include(":nativeApp")
example/harmonyApp(DevEco 工程)消费的是 .so 文件而不是 Maven 产物:CMake 把 libohoscupertino.so 声明为 IMPORTED 库再包一层 NAPI。Gradle 与 Hvigor 各自独立执行,上游工程零改动即可在 DevEco 打包。
第 3 阶段:设计令牌建模
3.1 JSON 契约
cupertino-core/.../theme/ColorScheme.kt(上游 ColorSchemeTokens)
↓ 1:1 提取
CupertinoTokens.kt(19 槽位 × 明暗 × 高对比 + 系统色 18 + legacy 15 + 字阶 11 + 圆角 5 + 度量 21)
cupertino/.../icons/{filled,outlined}(上游 ImageVector)
↓ 机械提取
Icons.kt(67 个图标路径命令流)
↓
Bridge.kt(CupertinoTheme / CupertinoIcon / CupertinoVersion)
↓ UTF-8 JSON
N-API cupertinoTheme() / cupertinoIcon() / cupertinoVersion()
↓
ArkUI Index.ets 四 Tab
页面不复制任何令牌数值,每个颜色、字号和图标轮廓都由 Kotlin 侧生成。
3.2 结果数据类
令牌按「明暗 × 高对比度」四象限存储,请求时才解析为最终值:
SchemeSlot(
name = "secondaryLabel",
light = 0x993C3C43u, lightAccessible = 0xCC3C3C43u,
dark = 0x99EBEBF5u, darkAccessible = 0xB2EBEBF5u,
)
val value = if (dark) {
if (highContrast) slot.darkAccessible else slot.dark
} else {
if (highContrast) slot.lightAccessible else slot.light
}
图标令牌保留上游 viewport 与默认尺寸,路径是压缩命令流:
IconToken("Heart", "filled", 23.2734f, 22.2305f, 23.2734f, 22.2305f,
"M 11.6367 22.2305 C ... Z")
3.3 运行逻辑
ArkTS 侧 replayIconPath() 把命令流回放到 CanvasRenderingContext2D:M→moveTo、L→lineTo、C→bezierCurveTo、H/V→结合当前点 lineTo、Z→closePath,按 viewport 等比缩放居中后 fill()。数值文本在提取时原样保留,与上游源文件可逐字符比对。
组件度量同样走令牌:Switch 用 switchWidth=51/switchHeight=31/switchThumbPadding=2,分段控件用 segmentedMinHeight=34,导航栏用 navigationBarHeight=49,全部来自上游各组件文件的 tokens 常量。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libcupertino_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohoscupertino.so)
│ CupertinoTheme / CupertinoIcon / CupertinoVersion
▼
UTF-8 JSON + explicit free(DisposeString)
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;HarmonyOS Kotlin 的 CAdapter 也不会把 @CName 业务符号挂到 ArkTS,所以 ArkTS 只 import CMake 包装出的 libcupertino_napi.so,不直接 import libohoscupertino.so。
4.2 Kotlin/Native 导出函数
@CName("CupertinoTheme")
fun cupertinoTheme(request: String): String = try {
val json = MiniJson.parse(request)
buildTheme(json.str("mode").equals("dark", true), json.bool("highContrast"))
} catch (t: Throwable) {
failure(t.message ?: t.toString()) // {"ok":false,"error":"..."}
}
@CName("CupertinoIcon")
fun cupertinoIcon(request: String): String = try {
val json = MiniJson.parse(request)
if (json.bool("list")) buildIconList() else buildIcon(json.str("name"), json.str("variant"))
} catch (t: Throwable) {
failure(t.message ?: t.toString())
}
@CName("CupertinoVersion")
fun cupertinoVersion(): String
三个入口都不抛异常;JSON 解析用桥内手写的 MiniJson,不引任何第三方库。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"cupertinoTheme", nullptr, NapiCupertinoTheme, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"cupertinoIcon", nullptr, NapiCupertinoIcon, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"cupertinoVersion", nullptr, NapiCupertinoVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr}};
C++ 只做字符串转换(先量长再拷贝)、调用 C ABI 和释放:Kotlin/Native 返回的字符串创建 ArkTS 值后立即经 libohoscupertino_symbols()->DisposeString 释放。N-API 不实现任何令牌语义。
4.4 CMake 和类型声明
add_library(ohoscupertino SHARED IMPORTED)
set_target_properties(ohoscupertino PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(cupertino_napi SHARED napi_init.cpp)
target_link_libraries(cupertino_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoscupertino)
IMPORTED_NO_SONAME + $ORIGIN rpath 防止绝对路径进入 RUNPATH,POST_BUILD 把 KN so 与 libc++_shared.so 拷到 NAPI so 旁边。类型声明:
export const cupertinoTheme: (request: string) => string;
export const cupertinoIcon: (request: string) => string;
export const cupertinoVersion: () => string;
第 5 阶段:ArkUI 宿主
5.1 加载目录和运行结果
import { cupertinoTheme, cupertinoIcon, cupertinoVersion } from 'libcupertino_napi.so';
const res: ThemeResult = JSON.parse(cupertinoTheme(
JSON.stringify({ mode: this.dark ? 'dark' : 'light', highContrast: this.highContrast })
)) as ThemeResult;
this.colorScheme = res.colorScheme ?? {};
this.schemeNames = Object.keys(this.colorScheme);
图标网格先经 cupertinoIcon(JSON.stringify({ list: true })) 拿清单,每个 IconCell 子组件再按名取路径、在自己的 Canvas 上回放;主题变色通过 @Prop @Watch 触发重绘。
5.2 页面交互
页面支持主题、字阶、图标、组件四 Tab:明暗与高对比度切换实时重载令牌;字阶 Tab 按每档 fontSize/lineHeight/字重预览;图标 Tab 切换 filled/outlined 变体;组件 Tab 的 Switch、滑块、分段控件、按钮和导航栏全部由令牌驱动且可交互。所有数值来自共享 Kotlin 结果,ArkTS 不重新实现 Cupertino 语义。
5.3 对比度和错误边界
标题与状态行使用 label 色,次级文字使用 tertiaryLabel,选中 Tab 与强调色使用 accent(iOS 蓝 #FF007AFF)。所有 Native 调用都在 try/catch 内,错误文本显示在页面状态行,页面永不崩溃。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── settings.gradle.kts
├── nativeApp/src/ohosMain/kotlin/example/nativeapp/
│ ├── CupertinoTokens.kt 设计令牌(1:1 提取,标注上游出处)
│ ├── Icons.kt 67 个图标矢量路径
│ ├── MiniJson.kt 扁平 JSON 解析与 argbHex 输出
│ └── Bridge.kt @CName 三导出与错误边界
└── harmonyApp/
├── entry/src/main/cpp/CMakeLists.txt
├── entry/src/main/cpp/napi_init.cpp
└── entry/src/main/ets/pages/Index.ets
6.2 构建、签名和安装
export JAVA_HOME="/path/to/jdk-21"
cd example
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
# DevEco Studio 打开 example/harmonyApp(不是仓库根目录!)
# File → Project Structure → Signing Configs 手动自动签名(证书不入库)
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.cupertino -m entry
四、完整代码对照
4.1 整体架构
CupertinoTokens.kt / Icons.kt
│ 19 槽位配色 / 系统色 / 字阶 / 圆角 / 度量 / 67 图标
▼
Bridge.kt
│ CupertinoTheme / CupertinoIcon / CupertinoVersion
▼
libohoscupertino.so(ohosArm64 + ohosX64)
│ C ABI + JSON
▼
libcupertino_napi.so(CMake NAPI 包装)
│ cupertinoTheme / cupertinoIcon / cupertinoVersion
▼
Index.ets(四 Tab + Canvas 路径回放)
▼
ArkUI 真机页面
4.2 文件清单
| 文件 | 职责 |
|---|---|
example/settings.gradle.kts | eazytec Maven 置前,ohos_cupertino 独立构建根 |
example/nativeApp/build.gradle.kts | ohosArm64/ohosX64 sharedLib 与 so 拷贝任务 |
example/nativeApp/.../CupertinoTokens.kt | 令牌 1:1 提取(文件头标注上游出处) |
example/nativeApp/.../Icons.kt | 67 个图标矢量路径 |
example/nativeApp/.../MiniJson.kt | JSON 解析与 #AARRGGBB 输出 |
example/nativeApp/.../Bridge.kt | C ABI、JSON 返回和错误边界 |
example/harmonyApp/.../napi_init.cpp | N-API 导出、字符串转换和释放 |
example/harmonyApp/.../Index.ets | ArkUI 四 Tab 页面与 Canvas 回放 |
doc/img/cupertino-ohos-device-icons.jpg | 真机效果图 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 上游 | lightColorScheme() / darkColorScheme() | ColorScheme 令牌出处(零改动) |
| 上游 | CupertinoColors.systemBlue(dark, hc) 等 | 系统色出处(零改动) |
| 上游 | CupertinoIcons.Filled/Outlined.* | 图标出处(零改动) |
| Native | CupertinoTheme | 返回主题令牌 JSON |
| Native | CupertinoIcon | 返回图标路径/清单 JSON |
| Native | CupertinoVersion | 返回版本 JSON |
| N-API | cupertinoTheme / cupertinoIcon / cupertinoVersion | 向 ArkTS 暴露数据 |
| ArkTS | loadTheme / loadIcons / replayIconPath | 渲染令牌、回放路径 |
4.4 ArkTS 与 Kotlin 的边界
const res: ThemeResult = JSON.parse(cupertinoTheme(
JSON.stringify({ mode: 'dark', highContrast: false }))) as ThemeResult;
@CName("CupertinoTheme")
fun cupertinoTheme(request: String): String
两者之间只传输 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:独立 example 构建,而不是整仓切换鸿蒙 Kotlin
上游 CMP 1.7.0 / Kotlin 2.0.20 与鸿蒙定制 Kotlin 2.2.21-1.0.0 分属两条插件线。独立构建让上游源码零改动、上游 CI 原样保留,审核面只有新增文件。
决策 2:令牌 1:1 提取,而不是运行时计算或手写
上游 ColorSchemeTokens 全是编译期常量,值即语义。提取后每个数值都能在上游具体文件 diff 对照,避免「仿制漂移」。
决策 3:图标走路径命令 JSON + Canvas 回放
ImageVector 无法跨语言,但 moveTo/curveTo 序列就是路径本身。ArkTS Canvas 回放零失真,filled/outlined 两个变体各 67 选 1 实时取用。
决策 4:双 ABI,ohosX64 保模拟器
DevEco 模拟器是 x86_64;HAP 缺当前 ABI 的三件套(libcupertino_napi.so + libohoscupertino.so + libc++_shared.so)安装直接报错 9568347。
决策 5:JSON 作为跨语言数据契约,NAPI 再包一层
ArkTS 不能直接 import KN so 的 @CName 符号,CMake 包装层是必经之路;JSON 让 ArkTS、C++ 与 Kotlin/Native 的边界清楚可调试,后续增加字段不破坏 ABI。
六、测试与验证
6.1 测试环境
本次真机验证使用 macOS(Apple Silicon)、JDK 21(Homebrew openjdk@21)、DevEco Studio、HarmonyOS SDK 和 HarmonyOS 真机(arm64,compatibleSdkVersion 5.1.0(18))。
6.2 Native 链接与符号验证
cd example
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
nm -gU example/harmonyApp/entry/libs/arm64-v8a/libohoscupertino.so | grep Cupertino
验证两个 ABI 均导出 CupertinoTheme、CupertinoIcon、CupertinoVersion 和 libohoscupertino_symbols 四个 C 符号,且 entry/libs/<abi>/ 三件套齐全。
6.3 HAP 验证
CompileArkTS: finished
PackageHap: finished
SignHap: finished(签名在 DevEco 手动完成,证书不入库)
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
6.4 功能用例
- 启动后加载主题,状态行显示「已加载 19 槽位配色 · 18 系统色 · 11 档字阶 · 5 档圆角」;
- 切换深浅色,19 槽位 + 18 系统色 + 15 legacy 色实时刷新(dark accent 为
#FF0A84FF); - 开启高对比度,secondaryLabel 等槽位切换 accessible 档;
- 字阶 Tab 按 11 档各自 fontSize/lineHeight/字重预览,headline 加粗;
- 图标 Tab 切换 filled/outlined,67 个图标 Canvas 回放(见 7.1);
- 组件 Tab 的 Switch(51×31)、分段控件(高 34)、滑块、四档按钮、导航栏(高 49)全部由令牌驱动且可交互。
6.5 验证结论
Native 动态库、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在 HarmonyOS 真机上成功安装并进入前台,页面展示了与上游源码 1:1 的 Cupertino 设计令牌与图标轮廓。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/compose-multiplatform-AnimatedImage.git
cd compose-multiplatform-AnimatedImage
export JAVA_HOME="/path/to/jdk-21"
(cd example && ./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64)
# DevEco Studio 打开 example/harmonyApp → Signing Configs 自动签名 → Build Hap(s)
hdc -t <设备序列号> install -r \
example/harmonyApp/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b org.terminator.ohos.cupertino -m entry
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须真正编译
ohosArm64动态库并打进 HAP; - 工具链不能强行融合:上游 Kotlin 2.0.20 与鸿蒙定制 Kotlin 分属两条插件线,独立 example 构建是唯一让上游零改动的路径;
- ArkTS 不能直接 import
libohoscupertino.so的@CName符号,必须 CMake 再包一层 NAPI,且每个 ABI 目录需要 KN so、libc++ 与 NAPI so 三件套,缺件安装直接报错9568347; - Kotlin 块注释可嵌套:文档注释里写
icons/filled/*.kt通配符会触发Unclosed comment;无符号字面量0xFF000000u在无期望类型处推断为 UInt,令牌统一uL后缀; - 签名工程必须与源码分离,证书不能进入 AtomGit。
8.2 已知问题
- 鸿蒙侧无 Compose 运行时,
@Composable组件本体(手势、动画、Layout)不在 ohos 编译范围; cupertino-adaptive的 Material3↔Cupertino 动态映射是 Composable 逻辑,未入桥;- 高对比度档位为静态快照,不读系统无障碍设置;
cupertino-icons-extended的 766 个扩展图标未全量入桥(体积考虑);- HAP 安装必须使用与 bundle name 匹配的签名。
8.3 未来优化方向
- 按同一提取脚本把 766 个扩展图标全量入桥;
- 桥增加版本字段与 ABI 兼容检查;
- 在 CI 中加入 Native 链接、符号检查和 HAP 构建;
- 补充不同设备 ABI 与 API 版本的验收矩阵;
- 跟踪社区 CMP for HarmonyOS 工具链进展,评估
@Composable本体接回时机。
九、总结
9.1 核心难点回顾
上游 Cupertino 源码(ColorScheme / CupertinoColors / Typography / Shapes / 组件度量 / icons)
→ 1:1 提取 CupertinoTokens.kt / Icons.kt
→ 独立 example 构建 + ohosArm64/ohosX64
→ C ABI(CupertinoTheme / CupertinoIcon / CupertinoVersion)
→ C++ N-API(libcupertino_napi.so)
→ ArkTS JSON 解析 + Canvas 路径回放
→ ArkUI 真机页面 + 四 Tab 验收
9.2 封装层次
- 上游层:提供 ColorScheme、系统色、字阶、圆角、组件度量与图标路径(零改动);
- 令牌层:把上游静态值镜像为纯 Kotlin 数据并逐一标注出处;
- Native 层:生成双 ABI 动态库并通过有限 C ABI 输出 JSON;
- N-API 层:完成字符串转换、调用分发和 Native 内存释放;
- ArkTS 层:管理主题状态、图标网格、Canvas 回放和页面交互;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- UI 型 CMP 库先把「设计语言」编译进 ARM64 运行时,再接 ArkUI 验收层,两者边界天然清晰;
- 工具链冲突用独立构建隔离,而不是改动上游依赖——零改动源码让适配永远可以跟随上游演进;
- 令牌必须 1:1 镜像并标注出处,让每个数值可 diff、可审核,避免「仿制漂移」。
9.4 适配成果
当前适配已完成:
- compose-cupertino 设计令牌(19 槽位配色 + 18 系统色 + 15 legacy 色 + 11 档字阶 + 5 档圆角 + 21 项组件度量)的
ohosArm64+ohosX64双 ABI 编译(上游零改动); - 67 个核心图标矢量路径的 1:1 提取与 Canvas 回放;
- 三个 C ABI 符号和 C++ N-API;
- DevEco Stage ArkUI 四 Tab 验收页面;
- HAP 构建、手动签名、设备安装和真机验证;
- 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。
参考文档
更多推荐


所有评论(0)