开源鸿蒙平台 CMP 三方库「bubbles-compose」适配全流程
本文记录
bubbles-compose接入 OpenHarmony 的完整过程,覆盖纯 Compose UI 控件库的路线选型、社区 Compose 鸿蒙制品线的依赖分线、整个上游commonMain的真实编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、expect/actual 接缝补位、无头门面(headless facade)、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
bubbles-compose是 iOS「Bubbles UI」风格的 Compose Multiplatform 主题与控件库:明暗ColorScheme(19 个语义色)、iOS 字阶Typography(11 档)、Shapes(5 档圆角)、20+ 个 iOS 系统色(含高对比度无障碍变体),以及 Button / CheckBox(三态)/ RadioButton / Switch / Slider / SegmentedControl / TabRow / Scaffold / Nav bars / TextField / Separator 等控件和bubblesTween/BubblesEasing动画包。本次适配与社区早期路线有本质区别——qrose 把纯 Kotlin 编码器内核与 Compose Painter 隔离、KStore 直接换 kotlinx ohos 制品线,而bubbles-compose的公开 API 100% 是 Compose Composable,没有可提取的"零依赖内核",主题模型本身就是控件库。因此本仓走的是 compose-shimmer / eungabi 开创的路线:上游commonMain整库一行不改地编译进鸿蒙动态库,无头门面把真实主题 token、控件几何 token 与真实动画引擎(真实Animatable跑真实bubblesTween(BubblesEasing))提升为 JSON C ABI,ArkUI 拿真实数据渲染控件并逐帧采样——页面上每个色值、每个尺寸、每一帧动画都来自 Kotlin 侧的编译产物,停止动画引擎画面立即冻结。
项目地址: AtomGit/oh-tpc/bubbles-compose.git
上游仓库: kalist28/bubbles-compose
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
bubbles-compose 为 Compose 应用提供"iOS 设计语言"这一件事的完整答案:BubblesTheme 一行建主题(自动跟随系统深浅色),ColorScheme 给出 19 个语义色 token,Typography 摆好 11 档 iOS 字阶,Shapes 定下 5 档圆角,BubblesColors 内置全套 iOS 系统色(红橙黄绿蓝紫 + 灰阶 1~8,每个都带深色与高对比度变体),控件层则把 Button / Switch / Slider / 选择器 / 分段控件 / 标签行 / 脚手架 / 导航栏 / 输入框 / 分隔线全部收进同一套 token 体系,动效统一走 bubblesTween(400ms,cubic-bezier 0.2833/0.99/0.31833/0.99 的 iOS 视图过渡曲线)。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、浏览器共享。OpenHarmony 应用同样需要这套成体系的设计语言,但上游构建(Kotlin 2.2.0 / Gradle 8.13 / AGP 8.11.0)不会生成 OpenHarmony 变体。
「给鸿蒙写一套 ArkTS 版 Bubbles」不是适配——那是重写,主题值、字阶、圆角、动效曲线会立刻与上游漂移。如果只把示例页面改成 ArkTS,页面可以显示色块,却无法证明上游控件库本体在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证主题模型、控件几何、动画机制、Compose 制品线、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 没有纯内核 | 库的全部 API 都是 @Composable,qrose 式"抽取编码器"路线不适用。 |
| 工具链不一致 | 上游 Kotlin 2.2.0 / Gradle 8.13 / AGP 8.11.0(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建。 |
| 依赖矩阵 | commonMain 声明了 lifecycle-viewmodel-compose / lifecycle-runtime-compose(2.9.1)——ohos 制品线不发布;且这是构建声明与源码实际引用不一致的上游遗留问题。 |
| API 代差 | 上游按 Compose 1.8.2 编写;社区 ohos 线是 1.12.0-0.1.0-07,个别 API 标注收紧(BlurEffect.convertRadiusToSigma 变为 @InternalComposeUiApi)。 |
| 无组合宿主 | 社区 Compose ohos 线发布 ui/foundation/animation 的 ohos klib,但 OHOS 上还没有 composition host,Composable 无法直接上屏。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
1.2 库提供的能力
Demo 提供一个真实的"Bubbles 主题会话",对应桥接的全部入口:
- 主题(theme):真实
lightColorScheme()/darkColorScheme()的 19 个语义色 token(accent / label×4 / systemFill×4 / placeholderText / separator×2 / link / background×6),切换深浅色即时重新取值,页面自身的底色、卡片色、文字色也用 token 渲染——页面即主题; - 调色板(palette):真实
BubblesColors.system*(dark, highContrast)的 21 个 iOS 动态系统色 + 14 个固定色,highContrast直通上游自己的无障碍分支; - 排版与形状(typography / shapes):11 档字阶逐档列出(字号/行高/字重)并按真实字号渲染,5 档圆角(4/8/12/16/24dp)来自真实
Shapes; - 控件(tokens + theme):Button(filled/tinted/clear)、Switch、CheckBox(双态/三态)、Radio、Slider、SegmentedControl、TabRow、TextField、Divider 的交互画廊——尺寸取自真实 token(Switch 51×31dp、图标按钮 42dp、按钮按压/描边透明度),颜色取自当前主题;
- 无障碍(contrast):真实
Color.accessible(isDark)对比度调整(向白/黑 lerp 20%)前后对照; - 动效(anim.*):真实
Animatable跑真实bubblesTween(duration, easing = BubblesEasing),小球位置由 33ms 轮询采样驱动,采样序列绘出缓动曲线;涟漪默认时序(450ms 展开 / 180ms 淡出)来自库内真实对象; - 版本握手:
bubblesVersion()返回 fork 版本号,来自真实 Native 调用而非静态文字。
响应统一走 {"ok":true,"op":...} / {"ok":false,"op":...,"error":...} 通道:
| 字段 | 作用 |
|---|---|
ok / op | 操作是否成功、回显本次操作。 |
colors[].name/argb/hex | 主题与调色板条目;hex 为 ArkUI 直用的 #RRGGBB / #AARRGGBB。 |
styles[].fontSizeSp/lineHeightSp/fontWeight | 字阶档位指标。 |
switchWidthDp 等 | 控件几何 token。 |
value / elapsedMillis / durationMillis | 动画引擎实时采样。 |
引擎侧完整能力(全部控件 Composable、涟漪 Indication、触感/无障碍 expect-actual 体系)全部在上游 commonMain 里原样随编译进入 .so,ArkTS 侧只消费门面暴露的数据面。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | bubbles 全部 commonMain 文件(30 个 Kotlin 源文件)原样编译,不提取、不改写;新增源码仅 ohosMain 两个文件(无头门面 + Haze scrim actual)。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);composeApp / iosApp / kotlin-js-store / build-configuration 留盘不进构建。 |
| 制品纪律 | 换线不换版:Compose 用社区 ohos 线 1.12.0-0.1.0-07,JVM 交叉验证用 Maven Central 1.11.0(同代);coroutines ohos 线 1.11.0-0.1.0-07 / JVM 1.10.2。 |
| 桥接稳定 | 2 个 C ABI(BubblesDispatch / BubblesVersion)+ UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| UI 完整 | 单页五 Tab 覆盖主题 / 调色板 / 控件 / 动效 / 关于全部桥接入口。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现——OHOS 尚无社区 Compose 线的组合宿主。页面上每个色值、每个尺寸、每一帧动画都来自
libohosbubbles.so里编译进的上游库对象,这是"真编译"与"照着写"的分界线。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、验证社区 Compose ohos 制品线、确定「整库编译」路线
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、lifecycle 剥离
第 3 阶段:源集与接缝 ── nonIosMain / skikoMain 重接线、Accessibility/触感复用、HazeNode scrim 降级、整库 ohos 编译
第 4 阶段:无头门面 ── BubblesHeadless:主题/调色板/字阶/形状/token/无障碍/真实动画引擎
第 5 阶段:原生桥接 ── @CName C ABI、MiniJson、C++ N-API、CMake、参数校验和内存释放
第 6 阶段:ArkUI 宿主 ── 五 Tab 验收页面(主题即页面、token 驱动画廊、动画逐帧采样)
第 7 阶段:示例与验证 ── 双 ABI 动态库、符号表验证、HAP、签名、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
bubbles/ 上游控件库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
bubbles/src/commonMain/ 30 个 Kotlin 源文件:主题/排版/形状/全部控件/动画包,原样编译,零改动
bubbles/src/nonIosMain/ 上游 non-iOS actual(Accessibility 返回 false、LocalHapticFeedback),ohos 经 dependsOn 复用
bubbles/src/ohosMain/ 新增:BubblesHeadless 无头门面 + HazeNode scrim actual(全库仅有的新增源文件)
bubbles/src/skikoMain/ 上游 Skia RuntimeShader 模糊 actual,仅服务 jvm 目标,留盘不进 ohos
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson,baseName=ohosbubbles)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面(bundleName org.terminator.ohos.bubbles)
composeApp/ iosApp/ kotlin-js-store/ build-configuration/ 上游演示与发布工程,留盘不进构建
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存;上游构建文件以 *.upstream 后缀留盘
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :bubbles 与 :example:nativeApp,rootProject.name = "ohos_bubbles"。上游的 AGP classpath、compose-hot-reload、vanniktech 发布、nyx 发布流程全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| Compose(ohos 线) | 社区 org.jetbrains.compose.* 1.12.0-0.1.0-07(runtime / runtime-saveable / foundation / animation / ui,ohos klib) |
| Compose(JVM 交叉验证线) | Maven Central 1.11.0(同代,desktop 变体齐全) |
| coroutines | ohos 线 1.11.0-0.1.0-07 / JVM 线 1.10.2(上游版本主体) |
| JDK | 21 |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作交叉验证) |
| Gradle | 9.6.1(2.4.20 线支持 Gradle 9.x,沿用 compose-shimmer 先例) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 26.0.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.bubbles |
| 版本 | 0.0.1-ohos.1(上游未发布 Maven Central,本地版本 0.0.1 + ohos 后缀) |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
上游 bubbles/build.gradle.kts 在 commonMain 声明了 lifecycle-viewmodel-compose / lifecycle-runtime-compose(2.9.1),但全库源码没有任何文件 import androidx.lifecycle(逐文件核查)——典型的"幽灵依赖"。ohos 制品线不发布这两个坐标,处置:ohos 构建里直接剥离,源码零引用、剥离无副作用。
| 模块 | commonMain 文件 | 依赖形态 | 处置 |
|---|---|---|---|
bubbles | 30 | Compose runtime/foundation/animation/ui + coroutines(源码 import 面逐项核对);lifecycle 声明未被源码使用 | 整体编译(jvm + 双 ohos),lifecycle 剥离 |
按源码实际 import 面收敛 Compose 四件套(rememberSaveable 补 runtime-saveable);compose.components.resources / uiToolingPreview 同样只在上游构建声明里、源码未用,一并剥离。
expect 声明全库 4 处:Accessibility.isHighContrastEnabled / isReduceTransparencyEnabled、rememberBubblesHapticFeedback()、HazeNode。处置见第 3 阶段。
1.4 页面边界
标题区 BUBBLES · OHOS / 版本握手行(CMP iOS 风格主题与控件库 · Kotlin/Native ohosArm64/ohosX64)
Tab 切换 主题 / 调色板 / 控件 / 动效 / 关于
主题区 浅色/深色切换 + 19 token 色块网格 + Shapes 圆角卡 + Typography 字阶列表
调色板区 深浅色 + 高对比度双开关,35 色网格;Color.accessible 对照卡
控件区 Button/Switch/CheckBox/Radio/Slider/SegmentedControl/TabRow/TextField/Divider 画廊
动效区 时长与缓动选择、播放/停止、进度条 + 小球 + 实时读数、缓动曲线采样图、涟漪时序卡
关于区 版本握手、库简介、架构分层、仓库与社区链接
页面启动时先调 bubblesVersion() 做握手,再依次拉取 theme / typography / shapes / tokens / ripple 建立本地状态。
第 2 阶段:目标与依赖打通
2.1 制品线验证(整库编译路线的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上的 Compose ohos 线,用 .module 元数据逐个验证:
org.jetbrains.compose.ui:ui:1.12.0-0.1.0-07
variants: ohosArm64ApiElements-published / ohosX64ApiElements-published
+ android/desktop/ios/macos 变体(根 artifact 重定向到子模块)
org.jetbrains.compose.animation:animation:1.12.0-0.1.0-07
variants: 同上(ohosArm64 / ohosX64 齐全)
org.jetbrains.compose.runtime / runtime-saveable / foundation:… 同构
两个关键结论:其一,bubbles 源码 import 面(runtime/foundation/animation/ui)在 ohos 线全部有 ohosArm64/ohosX64 klib,「整个 commonMain 编译进 ohos」在依赖层面成立;其二,ohos 线不发布 *-desktop 子 artifact,JVM 交叉验证目标不能从这条线解析——所以 Compose 依赖按"目标线"声明(ohosMain 用社区线、jvmMain 用 Maven Central 1.11.0 线),与 compose-shimmer / eungabi 的做法一致:换线不换版。
2.2 工具链钉版(为什么是 2.4.20-0.1.0-07)
coroutines ohos 线 1.11.0-0.1.0-07 依赖 stdlib 2.4.20-0.1.0-07(元数据 2.4.0),Kotlin 2.2.x 只能读到 2.3.0,编译期直接报元数据不兼容。工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07(KStore 适配已验证过这条推导链)。Gradle 用 9.6.1(2.4.20 线支持 Gradle 9.x,compose-shimmer 同款 wrapper 直接沿用,上游 8.13 wrapper 一并升级)。
settings.gradle.kts 把 eazytec 双仓写在最前:
pluginManagement {
repositories {
// HarmonyOS Kotlin toolchain must resolve first, otherwise ohosArm64()
// fails with "Unresolved reference" at configuration time.
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_bubbles"
include(":bubbles")
include(":example:nativeApp")
gradle.properties 重写(上游留 gradle.properties.upstream):org.gradle.java.home 指向本机 JDK 21、version=0.0.1-ohos.1、kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true。版本目录重写(上游留 .upstream),只保留 kotlin-multiplatform 与 kotlin-compose-compiler 两个插件别名(compose 编译器插件随 Kotlin 版本走,org.jetbrains.kotlin.plugin.compose:2.4.20-0.1.0-07)。根 build.gradle.kts 全局禁用 Ohos*Test* 任务——ohos 测试需要设备/模拟器,库当前没有 commonTest 源集,未来测试统一 JVM 跑。
2.3 编译真实控件库
bubbles/build.gradle.kts 重写为纯 KMP 模块(上游留 build.gradle.kts.upstream),双线依赖:
kotlin {
applyDefaultHierarchyTemplate()
compilerOptions {
// compose 1.11/1.12 把 BlurEffect.convertRadiusToSigma(上游 skikoMain
// 模糊 actual 使用)标为 @InternalComposeUiApi;上游按 1.8.2 编写时还是
// public。源码不动——opt-in 随构建文件走。
optIn.add("androidx.compose.ui.InternalComposeUiApi")
}
jvm { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) } } // 交叉验证
ohosArm64() // 适配本体
ohosX64()
sourceSets {
val nonIosMain by creating { dependsOn(commonMain.get()) }
val skikoMain by creating { dependsOn(commonMain.get()) }
jvmMain.get().dependsOn(nonIosMain)
jvmMain.get().dependsOn(skikoMain)
ohosMain.get().dependsOn(nonIosMain) // ohos 复用 non-iOS actual
ohosMain.dependencies { // 社区 ohos 线
api(libs.compose.runtime)
api(libs.compose.runtime.saveable)
api(libs.compose.foundation)
api(libs.compose.animation)
api(libs.compose.ui)
implementation(libs.kotlinx.coroutines.core)
}
jvmMain.dependencies { // Maven Central 上游线
api(libs.compose.runtime.jvm)
api(libs.compose.runtime.saveable.jvm)
api(libs.compose.foundation.jvm)
api(libs.compose.animation.jvm)
api(libs.compose.ui.jvm)
implementation(libs.kotlinx.coroutines.core.jvm)
}
}
}
实测:
./gradlew :bubbles:compileKotlinJvm # BUILD SUCCESSFUL
./gradlew :bubbles:compileKotlinOhosArm64 :bubbles:compileKotlinOhosX64 # BUILD SUCCESSFUL
上游 commonMain 30 个文件一个没动,只有 ohosMain 两个新文件。
第 3 阶段:源集与接缝(技术难点)
3.1 自定义中间源集重接线
上游为「iOS 专属触感/无障碍」和「Skia 模糊」手工建了两个中间源集,按 8 个目标接线;ohos 构建只保留 jvm + 双 ohos,接线收敛:
val nonIosMain by creating { dependsOn(commonMain.get()) }
val skikoMain by creating { dependsOn(commonMain.get()) }
jvmMain.get().dependsOn(nonIosMain)
jvmMain.get().dependsOn(skikoMain)
ohosMain.get().dependsOn(nonIosMain) // ohos 复用 non-iOS actual
上游 androidMain / iosMain / jsMain / wasmJsMain 目录与 appleMain / macosMain 接线全部留盘不进构建。
3.2 expect/actual 接缝的 ohos 补位
| expect(commonMain) | 上游 actual | ohos 处置 |
|---|---|---|
Accessibility.isHighContrastEnabled / isReduceTransparencyEnabled | iosMain(UIKit)/ nonIosMain(false) | 复用 nonIosMain(返回 false),经 dependsOn 图自动生效 |
rememberBubblesHapticFeedback() | iosMain(UIKit 生成器)/ nonIosMain(LocalHapticFeedback) | 复用 nonIosMain |
HazeNode(glassmorphism 模糊节点) | androidMain(半透明 scrim 降级)/ skikoMain(Skia RuntimeShader 全功能) | 新增 ohosMain scrim actual——与上游 androidMain 同策略 |
Haze.ohos.kt 的取舍:社区 Compose ohos 线没有暴露 skikoMain 模糊着色器所需的 org.jetbrains.skia.RuntimeEffect 面,且 OHOS 尚无组合宿主;上游在 Android 上对不支持 RenderEffect 的设备本来就是画 scrim 降级——ohos 沿用同一策略,skikoMain 的全功能模糊继续作为 jvm/desktop actual。这是全库唯一新增的 actual 类(约 50 行,与上游 androidMain 逐行同构)。
3.3 API 代差:opt-in 进构建文件
JVM 交叉验证在 Compose 1.11.0 线上撞到唯一的代差:上游 Haze.skiko.kt 调用的 BlurEffect.convertRadiusToSigma 在 1.11/1.12 被标注 @InternalComposeUiApi(上游按 1.8.2 编写时还是普通 public)。处置:源码不动,kotlin.compilerOptions.optIn 加 androidx.compose.ui.InternalComposeUiApi——上游语义原样,opt-in 随构建文件走。
第 4 阶段:无头门面(BubblesHeadless)
4.1 分层
io.github.kalist28.bubbles/**(上游 commonMain 原样编译)
│ ColorScheme / Typography / Shapes / BubblesColors / bubblesTween
BubblesHeadless.ohos.kt(门面,UI-free)
│ 快照 + 动画引擎
Bridge.kt(@CName × 2,扁平 JSON 进出)
↓ UTF-8 JSON
N-API bubblesDispatch() / bubblesVersion()
↓
ArkUI Index.ets(五 Tab 滚动)
OHOS 无组合宿主,Composable 上不了屏;能跨过 C ABI 的是数据与计算。BubblesHeadless(eungabi EunGabiHeadless / compose-shimmer ShimmerHeadless 的同款门面模式)把库内 UI-free 的部分提升为公开面:
| 门面 API | 提升的上游能力 | 说明 |
|---|---|---|
schemeSnapshot(dark) | lightColorScheme() / darkColorScheme() | 19 个语义色 → ARGB;页面主题即真实 ColorScheme |
paletteSnapshot(dark, highContrast) | BubblesColors.system*(dark, highContrast) | 21 动态色 + 14 固定色;highContrast 直通上游无障碍分支(与 Accessibility.isHighContrastEnabled 同一开关) |
typographySnapshot() | Typography() | 11 档字阶:字号/行高(sp)+ 字重名 |
shapesSnapshot() | Shapes() | 5 档圆角 dp;RoundedCornerShape(4.dp) 构造时以 dp 数值存 px,密度 1 下读回即声明值 |
rippleSnapshot() | RippleIndication 默认值 | 展开 450ms / 淡出 180ms / systemGray4.copy(.5f) 颜色(调参数化函数,绕开 @Composable getter) |
tokenSnapshot() | BubblesSwitchDefaults / BubblesButtonTokens | Switch 51×31dp、图标按钮 42dp、按钮按压/描边透明度 |
accessibleColor(argb, dark) | Color.accessible(isDark) | 真实无障碍对比度调整 |
start/stop/progress | Animatable + bubblesTween(duration, easing = BubblesEasing) | 真实动画引擎,跑的正是每个控件动画用的那条 tween 工厂 |
4.2 动画引擎与帧时钟
Animatable.animateTo 需要 MonotonicFrameClock(Android 上由 Recomposer 提供,测试里由 runTest 提供),无头侧用墙钟驱动(8ms/帧、TimeSource.Monotonic 时间戳);协程上下文带 CoroutineExceptionHandler,引擎异常经 progress().error 通道浮出、绝不杀进程。鸿蒙线 Dispatchers.Main 跨线程派发有已知问题(resaca 仓适配时记录过),门面跑在专用单线程 dispatcher(newSingleThreadContext("bubbles-engine"),失败回落 Dispatchers.Default),全程不碰 Main:
init {
// The exact spec widgets get from bubblesTween(duration, easing =
// BubblesEasing): instantiated through the real factory, not mirrored.
val spec: AnimationSpec<Float> = bubblesTween(
durationMillis = durationMillis,
easing = if (easingName == "linear") LinearEasing else BubblesEasing,
)
scope.launch {
animatedState.animateTo(targetValue = 1f, animationSpec = spec)
}
}
Demo 提供 bubbles / linear 两档缓动对照——停止引擎小球冻结、重启从 0 开始,每一帧都可归因到 Kotlin 侧的 Animatable。
第 5 阶段:原生桥接
5.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libbubbles_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosbubbles.so)
│ BubblesDispatch / BubblesVersion
▼
UTF-8 JSON + DisposeString
5.2 Kotlin/Native 导出函数
@CName("BubblesVersion")
fun bubblesVersion(): String = "0.0.1-ohos.1" // 版本握手
@CName("BubblesDispatch")
fun bubblesDispatch(request: String): String // 10 个 op 一口进
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
BubblesDispatch | bubblesDispatch(req) | theme / palette / typography / shapes / ripple / tokens / contrast / anim.start / anim.progress / anim.stop |
BubblesVersion | bubblesVersion() | fork 版本握手(真机版本行的数据来源) |
JSON 读写用手写 MiniJson(cmp_qrose 传承,协议只传扁平字段);浮点序列化用定点 %.4f(KN 无 String.format,Float.toString() 可能进科学计数法——wavy-slider 同款处理)。每个入口整体 try/catch,Kotlin 异常永远不跨 FFI。
5.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"bubblesDispatch", nullptr, NapiBubblesDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"bubblesVersion", nullptr, NapiBubblesVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosbubbles_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosbubbles_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。
5.4 CMake 和类型声明
add_library(ohosbubbles SHARED IMPORTED)
set_target_properties(ohosbubbles PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(bubbles_napi SHARED napi_init.cpp)
target_link_libraries(bubbles_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohosbubbles)
OHOS_ARCH 为空或 libohosbubbles.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const bubblesDispatch: (request: string) => string;
export const bubblesVersion: () => string;
第 6 阶段:ArkUI 宿主
6.1 握手与数据装载
aboutToAppear(): void {
this.version = bubblesVersion(); // 版本握手:来自真实 Native 调用
this.loadTheme(false); // theme → 19 token
this.loadTypography(); // 11 档字阶
this.loadShapes(); // 5 档圆角
this.loadTokens(); // 控件几何 token
this.loadRipple(); // 涟漪默认时序
this.loadPalette(false, false); // 35 色调色板
}
6.2 页面交互
五个 Tab 自上而下:主题 Tab 是 19 token 色块网格 + Shapes 圆角卡 + Typography 列表,页面底色/卡片色/文字色即当前 scheme(页面即主题);调色板 Tab 深浅色与高对比度双开关,21 个动态色实时切变体;控件 Tab 画廊里 Switch 恰为 token 给出的 51×31vp、滑块 27vp、指示条 400ms bubbles 曲线滑动;动效 Tab 33ms 轮询 anim.progress 驱动进度条与小球,运行期采样绘出缓动曲线(BubblesEasing 实机采样 vs LinearEasing 对照);关于 Tab 展示版本握手行、ArkTS → NAPI → C ABI → Kotlin 引擎 → Compose 制品线 分层说明与仓库/社区链接。
6.3 错误边界
所有 JSON 解析防御式处理:ok:false 时错误原文进状态行与 hilog;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进状态行。定时器在 aboutToDisappear 清理,页面无任何 ohpm 三方依赖。
第 7 阶段:示例与验证
7.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohosbubbles)+ copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ ├── Bridge.kt @CName × 2 + 10 个 op 分发 + 错误通道
│ └── MiniJson.kt 扁平 JSON 读取 + 定点浮点序列化
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.bubbles、versionName 0.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/libohosbubbles_api.h
├── src/main/cpp/types/libbubbles_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 验收页面(五 Tab)
7.2 构建、签名和安装
./gradlew :bubbles:compileKotlinJvm # 交叉验证
./gradlew :bubbles:compileKotlinOhosArm64 :bubbles:compileKotlinOhosX64 # ohos KLIB
./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 中勾选自动生成,证书写入本机 ~/.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.bubbles -m entry
四、完整代码对照
4.1 整体架构
io.github.kalist28.bubbles/**(上游 commonMain 原样编译)
│ ColorScheme / Typography / Shapes / BubblesColors / bubblesTween / BubblesEasing
bubbles/src/ohosMain(新增)
│ BubblesHeadless 门面(真实 token + 真实动画引擎) + HazeNode scrim actual
Bridge.kt + MiniJson
│ BubblesDispatch / BubblesVersion(扁平 JSON 进出)
libbubbles_napi.so
│ bubblesDispatch / bubblesVersion
Index.ets
│ JSON parse + @State 更新 + 逐帧采样
▼
ArkUI 真机页面(五 Tab 验收)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_bubbles、demo 模块移出 |
gradle/libs.versions.toml | Kotlin 2.4.20-0.1.0-07 + 双 Compose 线(ohos 1.12.0-0.1.0-07 / jvm 1.11.0)钉版 |
build.gradle.kts(根) | 版本 0.0.1-ohos.1、全局禁用 Ohos*Test* 任务 |
bubbles/build.gradle.kts | jvm + ohosArm64 + ohosX64;nonIosMain/skikoMain 重接线;双线依赖;lifecycle 剥离;InternalComposeUiApi opt-in |
bubbles/src/ohosMain/.../BubblesHeadless.ohos.kt | 无头门面:主题/调色板/字阶/形状/涟漪/token/无障碍/动画引擎 |
bubbles/src/ohosMain/.../effect/Haze.ohos.kt | HazeNode scrim 降级 actual(上游 androidMain 同策略) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib(baseName=ohosbubbles)+ copyKnSo* 回填 harmonyApp/libs |
example/nativeApp/.../Bridge.kt | 2 个 C ABI、10 个 op 分发、错误通道 |
example/nativeApp/.../MiniJson.kt | 手写扁平 JSON 读取 + 定点浮点序列化 |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 五 Tab 验收页面 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | lightColorScheme() / darkColorScheme() | 19 token 语义色方案(页面主题数据源) |
| 引擎 | BubblesColors.system*(dark, highContrast) | iOS 系统色 + 无障碍高对比度变体 |
| 引擎 | Typography() / Shapes() | 11 档字阶 / 5 档圆角 |
| 引擎 | bubblesTween(duration, easing = BubblesEasing) | 控件过渡规范(400ms cubic-bezier 0.2833/0.99/0.31833/0.99) |
| 引擎 | Color.accessible(isDark) | 无障碍对比度调整 |
| 门面 | BubblesHeadless.schemeSnapshot 等 | 上表能力的 UI-free 快照 |
| Native | BubblesDispatch / BubblesVersion | C ABI JSON 导出 |
| N-API | bubblesDispatch(req) / bubblesVersion() | 向 ArkTS 暴露能力 |
| ArkTS | loadTheme() / pollProgress() | 数据装载与逐帧采样 |
4.4 ArkTS 与 Kotlin 的边界
const req: ThemeRequest = { op: 'theme', dark: this.isDark };
const res = JSON.parse(bubblesDispatch(JSON.stringify(req))) as ThemeResponse;
// res.colors: 19 个真实 token(accent=#FF007AFF、label、systemFill…)
// 页面底色/卡片/文字立即按 token 重绘
fun schemeSnapshot(dark: Boolean): SchemeSnapshot {
val scheme = if (dark) darkColorScheme() else lightColorScheme()
val colors = listOf(
"accent" to scheme.accent,
"label" to scheme.label,
// …全部 19 个 token
).map { (name, color) -> NamedColor(name, color.toArgb()) }
return SchemeSnapshot(dark = scheme.isDark, colors = colors)
}
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:整库编译而不是提取「主题模型子集」
库的公开 API 全部是 Composable,但数据模型本身(ColorScheme/Typography/Shapes/BubblesColors)是纯 Kotlin 值对象,随 commonMain 一起编译毫无障碍。只提取模型会丢掉两样东西:控件几何 token(散在各 *Defaults 里)和动画包——而它们才是「控件库」的灵魂。整库编译 + 无头门面让 demo 的每个数值都能指着库里的真实对象。
决策 2:双制品线(ohos 社区线 + JVM 上游线)
ohos 线没有 *-desktop 子 artifact,JVM 交叉验证必须从 Maven Central 解析;两条线的版本主体同代(1.12.0-ohos / 1.11.0,上游 1.8.2 的下一代),API 面一致,源码不用分叉。这是 compose-shimmer / eungabi 已验证的「换线不换版」。
决策 3:HazeNode 沿用上游 Android 的 scrim 降级
上游自己就对不支持 RenderEffect 的 Android 画 scrim;ohos 无组合宿主 + 社区线无 skiko RuntimeEffect 面,scrim 是语义最诚实的降级。全功能模糊保留在 skikoMain(jvm/desktop)。
决策 4:lifecycle 剥离(幽灵依赖处置)
构建声明了、源码零引用、ohos 线无制品——三条件同时满足,剥离是唯一自洽处置。JVM 交叉验证同样不需要,行为面零影响。
决策 5:真实动画引擎进 demo 而不是 ArkTS 自演
动效 Tab 的小球、进度条、缓动曲线全部由 anim.progress 采样驱动:停止引擎小球冻结、重启从 0 开始——每一帧都可归因到 Kotlin 侧的 Animatable。这是「真编译」与「照着写」的分界线。
六、测试与验证
6.1 测试环境
本次验证使用 Windows x64、JDK 21、DevEco Studio 26.0(SDK 5.1.0(18)+)、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。
6.2 编译与链接验证
./gradlew :bubbles:compileKotlinJvm # 上游源码在 Compose 1.11.0 线交叉验证
./gradlew :bubbles:compileKotlinOhosArm64 :bubbles:compileKotlinOhosX64 # ohos KLIB
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
| 任务 | 结果 |
|---|---|
compileKotlinJvm | BUILD SUCCESSFUL(上游 30 文件零改动) |
compileKotlinOhosArm64 / OhosX64 | BUILD SUCCESSFUL |
linkDebugSharedOhosArm64 | arm64-v8a/libohosbubbles.so(debug 未 strip 约 29.1MB)+ libc++_shared.so |
linkDebugSharedOhosX64 | x86_64/libohosbubbles.so(debug 未 strip 约 27.8MB)+ libc++_shared.so |
copyKnSoArm64 / copyKnSoX64 | 三件套回填 example/harmonyApp/entry/libs/ |
体积说明:debugShared 未 strip、内含 Kotlin/Native 运行时 + Compose runtime/foundation/animation/ui 全线(对比 qrose 无 Compose 的 ~4.7MB);HAP 打包 strip 后显著缩小。
6.3 符号与 HAP 验证
llvm-nm -D(双 ABI): BubblesDispatch / BubblesVersion 均为全局 T 符号
BuildNativeWithCmake: finished(双 ABI 编出 libbubbles_napi.so)
CompileArkTS: finished(五 Tab 页面 + 类型声明)
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
6.4 功能用例
- 启动后「关于」Tab 显示
bubblesVersion() → 0.0.1-ohos.1(来自真实 Native 调用,非静态文字); - 「主题」Tab 切换深浅色,19 个 token 色块与页面底色/卡片色/文字色同步变化,数值来自真实
darkColorScheme()/lightColorScheme(); - 「调色板」Tab 开启深色与高对比度,21 个系统色逐个切换为对应变体(systemRed 深色 #FF6961 → 高对比度 #D70015),与上游源码逐值一致;
Color.accessible(isDark=true)对照卡显示向白色 lerp 20% 后的调整色;- 「控件」Tab Switch 尺寸恰为 51×31vp(token 原值),按钮/分段控件/标签行交互正常,TabRow 指示条以 400ms bubbles 曲线滑动;
- 「动效」Tab 播放 400ms bubbles 缓动:小球平滑起停,采样曲线呈典型 ease-out 形状;点停止小球立即冻结;linear 对照明显不同;
- 涟漪卡片显示真实默认时序 450ms/180ms,按压闪现动画使用相同时序;
- 全程无崩溃、无 ANR,
hilog无 Native 异常。
6.5 验证结论
主题模型、iOS 系统调色板(含无障碍变体)、字阶、形状、控件几何 token、Color.accessible 对比度调整、真实 bubblesTween 动画引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的"Bubbles 主题会话":版本握手、深浅色切换、高对比度调色板、token 驱动控件画廊与逐帧采样的缓动演示(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/bubbles-compose.git
cd bubbles-compose
export JAVA_HOME="/path/to/jdk-21"
./gradlew :bubbles:compileKotlinJvm # 交叉验证
./gradlew :bubbles:compileKotlinOhosArm64 :bubbles:compileKotlinOhosX64 # ohos KLIB
./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.bubbles -m entry
hdc shell hilog | grep Bubbles
八、遗留问题与改进方向
8.1 踩坑复盘
settings.gradle.kts里 eazytec 双仓必须排在最前,否则配置期直接Unresolved reference: ohosArm64;- ohos Compose 线不发布
*-desktop子 artifact——Compose 依赖必须按目标线(ohosMain/jvmMain)分开声明; - 上游 commonMain 的 lifecycle 声明是幽灵依赖(源码零 import):先 grep 源码 import 面再定依赖处置,不要照抄构建声明;
- Compose 1.8→1.11/1.12 的 API 收紧(
BlurEffect.convertRadiusToSigma→@InternalComposeUiApi):源码不动,opt-in 进构建文件; BubblesColors.systemXxx同名双面:@Composable属性 getter 不能在非组合上下文调用,门面一律走参数化函数(systemGray4(false));systemGray7/8无highContrast变体,签名逐个核对;- 该 Compose 线没有
Color(Int)工厂重载:ARGB int 用分量构造; CornerBasedShape.topStart静态类型是CornerSize,取 dp 需 cast 到CornerRadius再按密度 1 换算(RoundedCornerShape(4.dp)构造时 dp 数值即存为 px,读回无损);Animatable.animateTo无头驱动必须自备MonotonicFrameClock(8ms 墙钟帧),并用CoroutineExceptionHandler兜底——引擎异常走progress().error通道,不杀进程;- 鸿蒙线
Dispatchers.Main跨线程派发有已知问题(resaca 记录):门面用专用单线程 dispatcher,全程不碰 Main; - ArkTS 严格模式:
Color枚举与 hex 字符串不互通(Color.Transparent传给string参数直接编译失败),页面侧颜色统一为字符串(#RRGGBB/#AARRGGBB);curves.cubicBezier已弃用,用cubicBezierCurve同签名替换; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); build-profile.json5的signingConfigs留空数组、不引用签名,勾选 DevEco 自动签名后由 IDE 回填,提交前用git update-index --skip-worktree保护;HAP 缺当前 ABI 的 so 报安装失败9568347,双 ABI 每档三件套(NAPI so + KN so + libc++)缺一不可。
8.2 已知问题
- OHOS 无组合宿主:Composable 本体编译进 so 但不上屏,由 ArkTS 按真实 token/动画数据渲染(社区 Compose ohos 宿主可用后可切换为直接托管);
HazeNodeohos 走 scrim 降级(全功能 RuntimeShader 模糊仅 jvm/desktop);- 触感/无障碍 actual 复用 non-iOS 桩(
isHighContrastEnabled=false、LocalHapticFeedback),鸿蒙系统级无障碍开关未接(后续可在 ohosMain 读系统设置补 actual); - android / ios / js / wasmJs 目标与发布流程剥离(源文件/构建文件留盘),本 fork 不发 Maven Central;
- 上游无 commonTest 源集,交叉验证暂为「编译通过」级;补测试后统一 JVM 跑。
8.3 未来优化方向
- 跟进社区 Compose ohos 宿主,把 demo 从「token 驱动 ArkTS 渲染」升级为直接托管 Composable;
- ohosMain 补系统级高对比度/深浅色/触感 actual;
- 桥接补状态推送(N-API 线程安全回表),动画改订阅而非轮询;
- 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建冒烟;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵。
九、总结
9.1 核心难点回顾
上游 bubbles(commonMain 全是 Composable)
→ 验证社区 Compose ohos 制品线(runtime/foundation/animation/ui 全有 ohosArm64/ohosX64)
→ 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1)
→ 根构建替换(cmp_qrose 模式,lifecycle 幽灵依赖剥离)
→ nonIosMain/skikoMain 重接线 + HazeNode scrim actual + InternalComposeUiApi opt-in
→ 整库 ohos KLIB(上游源码零改动)
→ BubblesHeadless 无头门面(真实 token + 真实 Animatable/bubblesTween)
→ C ABI(@CName × 2)+ 扁平 JSON
→ C++ N-API(DisposeString 纪律)
→ ArkTS 五 Tab 真机验收(主题即页面 / token 驱动画廊 / 动画逐帧采样)
9.2 封装层次
- 库层:上游原版主题模型、全部控件、动画包(
commonMain零改动); - 门面层:
BubblesHeadless把 token/无障碍/动画提升为 UI-free 面(ohosMain 新增); - Native 层:双 ABI 动态库,2 个 C ABI 输出 JSON;
- N-API 层:参数检查、字符串转换、Native 内存释放;
- ArkTS 层:页面状态、JSON 校验、分发、日志与错误渲染;
- DevEco 层:CMake、HAP、签名、安装与真机运行。
9.3 三条经验
- UI 库的适配单位是「数据面」而不是「控件面」:Composable 上不了屏时,主题 token、几何常量、动画规范就是库的 API——把它们原样送过 FFI,页面自然「像」Bubbles,而且每个像素可归因;
- 制品线决定路线:先验证社区 ohos 线覆盖了源码的全部 import 面(bubbles 四件套齐全),「整库编译」才成立;同时摸清它的缺口(无
*-desktop、无 lifecycle),缺口即处置清单; - 真实动画是最硬的验收证据:色值可以照抄、尺寸可以照量,但「停止引擎画面冻结、重启从 0 开始」只有真引擎在跑才做得到——demo 里必须留一个由 Kotlin 逐帧驱动的活物。
9.4 适配成果
当前适配已完成:
- 上游
commonMain全部 30 个源文件零改动的ohosArm64/ohosX64Kotlin/Native 编译与双 ABI 动态库链接(含完整 Compose runtime/foundation/animation/ui 社区线1.12.0-0.1.0-07); - 2 个 C ABI 符号(
llvm-nm验证全局 T)+ C++ N-API 包装层(libbubbles_napi.so); BubblesHeadless无头门面:19 token 主题 / 35 色(含无障碍变体)/ 11 档字阶 / 5 档圆角 / 控件几何 token /Color.accessible/ 真实bubblesTween动画引擎;- DevEco Stage ArkUI 五 Tab 验收页面(bundleName
org.terminator.ohos.bubbles)+ HAP 构建、签名、设备安装和真机验证(深色模式调色板实测截图); - JVM 交叉验证编译通过(Compose 1.11.0 线);
- AtomGit 项目文档、OpenHarmony README(中英双份)、上游原版留档(
README.upstream.md+*.upstream构建文件)与本地效果图。
适配过程 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)