开源鸿蒙平台 CMP 三方库「compose-theme-reveal」适配全流程
本文记录
compose-theme-reveal接入 OpenHarmony 的完整过程,覆盖纯 Compose UI 库的盘点、社区 Compose ohos 制品线验证、无头门面设计、真实引擎的ohosArm64/ohosX64KLIB 编译、Kotlin/Native 动态库、C ABI / N-API 桥接、ArkUI 复刻渲染、HAP 构建和真机验收。
compose-theme-reveal是 Compose Multiplatform 的主题揭示过渡库:新主题不是干切,而是穿过旧主题被揭示出来。ThemeRevealHost维护双重组合——committed 世界照常显示,incoming 世界只在动画遮罩后面生长(扩散的圆斑、分缝的裂带、生长的盒子,或整版逐张翻转的卡片),前沿自带描边、辉光、焦边、摆动与震颤;RevealController.revealTo挂起直到全覆盖才提交新值。本次适配与社区早期路线(qrose 的"零依赖内核提取")有本质区别:这是一个纯 Compose UI 库,没有可提取的纯 Kotlin 核心,因此走 compose-shimmer 验证过的「模式A」——上游 Compose 代码原样编译进 OpenHarmony KLIB(社区 Compose ohos 制品线1.12.0-0.1.0-07,官方坐标换线不换版),库模块ohosMain新增无头门面ThemeRevealHeadless:12 个预设工厂、Envelope.intensity、internalTileBoard+cardTurn、CubicBezierEasing缓动全部是真实上游代码,只有私有绘制函数的前沿几何按公式逐条镜像。ArkTS Demo 每帧调用evaluate取回引擎几何,用 ArkUI 的 Path clip、三层描边与卡片 3D 旋转复刻揭示效果。
项目地址: AtomGit/oh-tpc/compose-theme-reveal
上游仓库: mofeejegi/compose-theme-reveal
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
compose-theme-reveal 给 Compose Multiplatform 应用回答"主题切换如何有仪式感"这件事:RevealController.revealTo(target, origin, style) 挂起提交(互斥保护、最新请求合并、150ms 冲刺加速),ThemeRevealHost 把 incoming 世界组合在遮罩后面,ArrivalStyle 描述前沿个性(12 种预设:iris / cut / bloom / rift / chaos / paperBurn / curtain / slash / aperture / flipboard / cascade / shutters),Envelope 控制戏剧强度包络,TileBoard + cardTurn 排程卡片翻转(洗牌 / 波扫 / 源扩散三种次序,翻走 / 展开两种翻转)。整库 23 个 commonMain 文件(约 1556 行)只依赖 Compose runtime / foundation / ui,零 expect / actual——适配面理论上是最小的。
但障碍在另一头:OHOS 社区 Compose 线(org.jetbrains.compose.*:1.12.0-0.1.0-07)目前只有 klib 制品,没有组合宿主(composition host)。ThemeRevealHost 是 Composable,挂不进 ArkTS 树;而它又是一个纯 Compose UI 库,没有 KStore 那种可以整体无头复用的"纯 Kotlin 引擎 + 文件 Codec"分层。如果只把示例页面改成 ArkTS 动画,页面可以动,却无法证明公共揭示引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 制品线缺失 | Compose runtime / foundation / ui 必须有 ohos klib 变体才能解析——Maven Central 上没有,只在社区 eazytec 制品线上。 |
| 工具链不一致 | 上游 Kotlin 2.4.10 / Gradle 9.6.1 / AGP 9.1.1 与鸿蒙工具链无法共存于一个 Gradle 构建;旧钉法 Kotlin 2.2.21-1.0.0 读不了 2.4 元数据。 |
| 无组合宿主 | ThemeRevealHost / rememberRevealController 是 Composable,ArkTS 树里没有地方挂;withFrameNanos 帧时钟、pointerInput 输入法、GraphicsLayer 录制回放都依赖组合环境。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 动画语义跨不了桥 | revealTo 的挂起提交、双时钟(eased progress + raw time)、150ms 加速是 Kotlin 侧概念,C ABI 只能同步一来一回。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:上游 commonMain 的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI / N-API 桥接层。揭示几何与缓动完全由上游原版实现负责,ArkTS 只负责时间轴、调用 evaluate 和渲染返回的几何。
1.2 库提供的能力
Demo 提供一个真实的主题揭示会话,对应桥接的全部入口:
- 样式表(styles):一次取回 12 个预设的完整参数——前沿种类、时长、wobble 频带(幅度 / 频率 / 相速 / 尖化)、描边 / 辉光 / 焦边宽度、震颤与漂移、包络拐点、tiles 牌局配置。每个数值都来自
ArrivalStyle.iris(...)等真实工厂函数的返回值序列化,不是手抄常量; - 逐帧求值(evaluate):给定样式名、宿主尺寸、origin、原始进度 fraction 与原始秒数,引擎返回该帧完整几何——sway 后的 origin、世界震颤偏移、边缘参数(含三层辉光的遍数与单遍 alpha、纸燃焦边色)、以及前沿本体(radial / box 的 128 点闭合多边形、slit 的两条 65 点折线、tiles 的整副卡片状态表);
- 版本握手(version):返回 fork 版本、Kotlin 与 Compose 制品线版本,来自真实 Native 调用而非静态文字。
响应的关键字段:
| 字段 | 作用 |
|---|---|
progress / intensity | 引擎 ease 后的揭示进度与包络强度(Envelope.intensity 真实调用)。 |
origin / shake | sway 漂移后的前沿圆心;世界震颤的整像素偏移(sin(t·47) / cos(t·61))。 |
edge | 描边 / 辉光 / 焦边的像素宽度、accent 与 char 色、辉光遍数与单遍 alpha。 |
front | 前沿几何:radial / box 闭合多边形、slit 双折线、tiles 卡片状态表(每张卡的 turn / 角度 / 轴 / 方向 / 铰链 / 变暗)。 |
引擎侧完整能力(RevealController 的请求合并与挂起提交、ThemeRevealHost 的双重组合与 GraphicsLayer 录制回放、InputPolicy 输入法)全部在上游 commonMain 里原样随编译进入 .so;JVM 侧保留 14 项 jvmTest 交叉验证用例,证明 ohos .so 里执行的揭示逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | theme-reveal 的全部 commonMain 文件原样编译,不提取、不重写;全仓唯一新增 Kotlin 门面 ThemeRevealHeadless.ohos.kt 放在库模块 ohosMain 源集(同模块才可见 internal 的 TileBoard / cardTurn)。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);上游 sample/*(Android / iOS / Desktop / Web)留盘不进构建。 |
| 桥接稳定 | 使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | Compose 坐标保持官方组名,ohos 线 1.12.0-0.1.0-07、jvm 交叉验证线 1.12.0 双线分声明(换线不换版),上游测试零改动全绿。 |
| UI 完整 | 单页覆盖全部桥接入口:12 种到达 × 6 个上游调色板的全屏揭示、几何 Tab 的原始数据与手动推进、关于 Tab 的版本握手。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。OHOS 社区 Compose 线尚无组合宿主,ArkTS Demo 的职责是把桥暴露的揭示几何逐帧画出来——画面上的每一条遮罩边缘、每一张翻转卡片的位置,都来自
.so里真实上游代码的求值结果。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认 Compose ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、双线依赖声明
第 3 阶段:桥协议建模 ── 无头门面设计(真实调用 vs 镜像公式)、styles/evaluate/version 契约、错误通道
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 16ms 帧循环时间轴、150ms 冲刺、三 Tab 验收页面
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
theme-reveal/ 上游库(进 ohos 构建,jvm + ohosArm64 + ohosX64)
theme-reveal/src/commonMain/ 上游 23 个文件一行不改(Composable + 揭示引擎)
theme-reveal/src/ohosMain/ 新增:ThemeRevealHeadless.ohos.kt 无头门面(全仓唯一新增库源文件)
sample/ 上游示例(Android/iOS/Desktop/Web),留盘不进 settings
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
*.upstream 上游构建文件原样保存(settings/build/properties/toml 五件)
doc/ 适配全流程(本文)与效果图 img/
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :theme-reveal 与 :example:nativeApp,rootProject.name = "ohos_compose_theme_reveal"。上游的 AGP 9 KMP 插件、com.android.kotlin.multiplatform.library、vanniktech 发布、iOS / js / wasmJs 目标全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21(org.gradle.java.home 指向本机 JDK,Gradle 启动还需 JAVA_HOME) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作交叉验证) |
| Compose(ohos 线) | org.jetbrains.compose.*:1.12.0-0.1.0-07(官方坐标,eazytec 制品线) |
| Compose(jvm 线) | org.jetbrains.compose.*:1.12.0(Maven Central,上游所用线) |
| Gradle | 9.6.1(沿用上游 wrapper;2.4.20 线支持 Gradle 9.x) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.themereveal |
export JAVA_HOME="/path/to/jdk-21"
java -version
踩坑提示:
gradle.properties里的org.gradle.java.home要等 JVM 起来后才生效,gradlew启动器本身认的是JAVA_HOME环境变量——不设置时直接报JAVA_HOME is not set。
1.3 源码依赖度盘点
| 模块 | commonMain 文件 | 依赖形态 | 处置 |
|---|---|---|---|
theme-reveal | 23 | Compose runtime + foundation + ui(含动画曲线),零平台源码集 | 整体编译(jvm + 双 ohos) |
expect / actual 声明全库为 0;上游六个目标(Android / iOS×2 / js / wasmJs / jvm)共享同一份纯 common 代码——这既是适配面最小的原因,也意味着没有"纯 Kotlin 内核"可以提取:所有能力都长在 Compose API 上。处置因此不是 qrose 式的"编码器留 commonMain、Painter 挪走",而是 compose-shimmer 验证过的「模式A」:
- 上游
commonMain原样编译进ohosArm64/ohosX64klib(Composable、ThemeRevealHost、绘制通道全部在.so里); - 库模块
ohosMain新增无头门面ThemeRevealHeadless,把绘制通道的输入(样式参数、尺寸、origin、双时钟)与输出(前沿几何)暴露成纯函数; - 视觉复刻交给 ArkTS。
1.4 页面边界
标题区 THEME REVEAL · OPENHARMONY / 版本握手行(Compose 主题揭示库 · Kotlin/Native ohosArm64/ohosX64)
Tab 切换 效果 / 几何 / 关于
效果 Tab 全屏"世界"宿主(当前调色板背景 + 大标题 + 调色板圆点)
底部 4×3 Arrival 按钮网格(12 种到达样式,各以上游 sample 命名)
揭示中:incoming 世界按引擎遮罩裁剪显示,前沿叠辉光/焦边/描边,世界整体震颤
tiles 样式:整版卡片逐张 3D 翻转(引擎排程)
点击宿主空白处:150ms 冲刺加速(对应上游 InputPolicy.Block)
几何 Tab 最近一帧引擎原始 JSON(progress/intensity/origin/shake/edge/points 摘要)
手动滑杆推进 fraction 的 300×400 预览(工程师验收视图)
关于 Tab 版本握手、库简介、架构分层、仓库与社区链接
页面启动时先调 themeRevealVersion() 与 {"op":"version"} 做握手;点击按钮时以按钮中心为 origin(复刻上游 sample"每个按钮从自身中心揭示"的语义)。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 Compose 的 ohos 制品。用 .module 元数据逐个验证:
org.jetbrains.compose.runtime:runtime:1.12.0-0.1.0-07
variants: ohosArm64Api-published / ohosX64Api-published / jvmApi-published …
org.jetbrains.compose.foundation:foundation:1.12.0-0.1.0-07
variants: 同上(ohos 双变体齐全)
org.jetbrains.compose.ui:ui:1.12.0-0.1.0-07
variants: 同上
org.jetbrains.compose.animation:animation:1.12.0-0.1.0-07
variants: 同上(RevealController 的 CubicBezierEasing 来自这里)
三个关键结论:其一,坐标保持官方组名 org.jetbrains.compose.*,版本主体与上游声明的 1.12.0 逐字相同,只是仓库换线——commonMain 里一行依赖坐标都不用改;其二,ohos 制品同时发布 jvm 变体,但不发布 jvm 目标需要的 *-desktop 子制品,所以 jvm 交叉验证必须单独走 Maven Central 的 1.12.0 上游线(双线声明,见 2.3);其三,Maven Central 上的 1.12.0 没有 ohos 变体,版本必须带 -0.1.0-07 后缀才解析得到。
2.2 工具链钉版(为什么是 2.4.20-0.1.0-07)
社区此前批准的钉法是 HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 用的)。但 Compose ohos 线 1.12.0-0.1.0-07 的 klib 由 Kotlin 2.4.20-0.1.0-07 构建,旧线编译期读不了 2.4 元数据。工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07。额外好处:上游源码本来就是 Kotlin 2.4.10 编的,2.4.20 与之同代,源码级兼容成本为零;Gradle 9.6.1 上游 wrapper 原样沿用(compottie / KStore 先例,2.4.20 线支持 Gradle 9.x),不像 2.2.21 旧线必须降 8.14.1。
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_compose_theme_reveal"
include(":theme-reveal")
include(":example:nativeApp")
dependencyResolutionManagement 同样把两个 eazytec 仓库置于 google() / mavenCentral() 之前。
2.3 编译真实引擎
theme-reveal/build.gradle.kts 收敛为纯 KMP 模块(compose 编译器插件用官方 org.jetbrains.kotlin.plugin.compose,版本随 Kotlin 工具链):
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.compose.compiler)
}
kotlin {
applyDefaultHierarchyTemplate()
jvm {
compilerOptions { jvmTarget = JvmTarget.JVM_21 }
}
ohosArm64()
ohosX64()
sourceSets {
ohosMain.dependencies { // 社区 ohos 制品线
api(libs.compose.runtime)
api(libs.compose.foundation)
api(libs.compose.ui)
api(libs.compose.animation)
}
jvmMain.dependencies { // 上游 Maven Central 线(交叉验证)
api(libs.compose.runtime.jvm)
api(libs.compose.foundation.jvm)
api(libs.compose.ui.jvm)
api(libs.compose.animation.jvm)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
实测:
./gradlew :theme-reveal:compileKotlinOhosArm64 :theme-reveal:compileKotlinOhosX64 # BUILD SUCCESSFUL
./gradlew :theme-reveal:jvmTest # 14 项全绿
引擎的全部 commonMain 文件一个没动,ThemeRevealHost 的 Composable 连同完整 Compose 运行时一起编译进了 ohos klib——只差一个能把它们逐帧求值出来的门面。
2.4 测试依赖的处置:测试统一在 JVM 跑
commonTest 对 ohos 测试编译同样可见,一旦某个测试依赖解析不到 ohos 变体就会卡死 compileTestKotlinOhosArm64。处置是两条纪律的组合:测试统一在 JVM 跑(jvmTest),根构建全局禁用 ohos 测试任务:
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
这样上游测试文件零改动保留,ohos 侧只承担编译与链接。
第 3 阶段:桥协议建模
3.1 JSON 契约
com.mofeejegi.themereveal/**(上游引擎 + Composable,原样编译)
↓ ArrivalStyle 预设 / Envelope.intensity / TileBoard + cardTurn / CubicBezierEasing
ThemeRevealHeadless(库模块 ohosMain 无头门面)
↓ 纯函数 evaluate(style, size, density, origin, fraction, time) → Frame
Bridge.kt(@CName × 2,扁平 JSON 进出)
↓ UTF-8 JSON
N-API themeRevealVersion() / themeRevealDispatch()
↓
ArkUI Index.ets(三 Tab)
请求与响应(三个 op):
styles : {} → 12 项预设全参数(front/duration/wobble[]/stroke·glow·char/envelope/tiles/accentKind)
evaluate : {"style":"iris","accent":"#FF6FA8FF","progress":0.42,"time":1.234,
"width":1080,"height":2340,"density":3.0,"originX":540,"originY":1170}
→ {"ok":true,"fraction":…,"progress":…,"intensity":…,
"origin":{x,y,baseX,baseY},"shake":{x,y},
"edge":{strokePx,glowPx,charPx,glowPasses:3,glowPassAlpha:0.13,accent,char},
"front":{kind:"radial|slit|box|tiles", points|points+points2|cards}}
version : {} → {"version":"0.1.0-alpha01-ohos.1","kotlin":"…","compose":"…","presets":12}
错误 : {"ok":false,"op":"…","error":"…"}
两个不对称设计:progress 传原始 fraction(0…1,未 ease),引擎内部用与 RevealController 相同的 CubicBezierEasing(0.5f, 0f, 0.75f, 0.75f) 做 ease——缓动归引擎,ArkTS 只管线性累计时间;time 传原始秒数,驱动 wobble / sway / shake 这些"不该随缓动减速的有机运动",对应上游"eased progress 驱动几何、raw time 驱动生命"的双时钟契约。
3.2 门面为什么放库模块、哪些是真件哪些是镜像
TileBoard 与 cardTurn 是库的 internal 声明,只有同模块源集可见——所以门面 ThemeRevealHeadless.ohos.kt 放在 theme-reveal/src/ohosMain(放桥模块就够不到了)。门面内部:
| 能力 | 来源 | 性质 |
|---|---|---|
| 12 个预设 | ArrivalStyle.iris(accent) 等真实工厂函数 | 真实上游代码 |
| 强度包络 | style.envelope.intensity(progress) 真实调用 | 真实上游代码 |
| 牌局排程 | internal TileBoard.layout(...) + cardTurn(place, progress) | 真实上游代码 |
| 揭示缓动 | CubicBezierEasing(0.5f, 0f, 0.75f, 0.75f)(ohos Compose 线同款曲线类,与 RevealController 私有 Ease 同参) | 真实曲线类 + 同参 |
| radial / slit / box 多边形、wobble 求和、sway、shake | 按 ThemeRevealHost.kt 私有绘制函数逐公式镜像(常量同步:128/64/32 步采样、sway 率 2.3、震颤频率 47/61、辉光 3 遍 × 0.13、卡片变暗 0.5、交错 0.6) | 镜像公式 |
验收底线写在门面注释里:给定样式、尺寸、origin、进度与时间,这里产出的多边形点必须与上游绘制通道会描出的点一致(浮点舍入内)。
3.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("ThemeRevealDispatch")
fun themeRevealDispatch(request: String): String = try {
val json = MiniJson.parse(request)
when (json.str("op")) {
"version" -> versionResponse()
"styles" -> stylesResponse()
"evaluate" -> evaluateResponse(json)
else -> err(json.str("op").ifBlank { "(missing)" }, "unsupported op")
}
} catch (t: Throwable) {
err("dispatch", t.message ?: t.toString())
}
非法 JSON、未知样式、非正尺寸、不支持的 op,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libthemereveal_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohosthemereveal.so)
│ ThemeRevealDispatch / ThemeRevealVersion
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复刻揭示语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("ThemeRevealVersion")
fun themeRevealVersion(): String = "0.1.0-alpha01-ohos.1"
@CName("ThemeRevealDispatch")
fun themeRevealDispatch(request: String): String
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
ThemeRevealDispatch | themeRevealDispatch(req) | styles 表 + evaluate 逐帧几何(预设 / 包络 / TileBoard / 缓动 / 前沿多边形) |
ThemeRevealVersion | themeRevealVersion() | fork 版本握手(真机版本行的数据来源) |
数字格式化有专门纪律:桥侧 num() 把所有 float 序列化为至多两位小数的定点数(自实现整型拼装),避免 Float.toString() 在极小值上产出科学计数法文本。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"themeRevealDispatch", nullptr, NapiThemeRevealDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"themeRevealVersion", nullptr, NapiThemeRevealVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohosthemereveal_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosthemereveal_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。
4.4 CMake 和类型声明
add_library(ohosthemereveal SHARED IMPORTED)
set_target_properties(ohosthemereveal PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(themereveal_napi SHARED napi_init.cpp)
target_link_libraries(themereveal_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohosthemereveal)
OHOS_ARCH 为空或 libohosthemereveal.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const themeRevealDispatch: (request: string) => string;
export const themeRevealVersion: () => string;
第 5 阶段:ArkUI 宿主
5.1 时间轴与触发
private tick(): void {
if (!this.revealing) { return; }
const now = Date.now();
const dt = now - this.lastTickMillis;
this.lastTickMillis = now;
this.elapsedMillis += dt;
const pace = this.accelerated ? 150 : this.durationMillis; // 上游 accelerate() 的 150ms
this.revealFraction = Math.min(this.revealFraction + dt / pace, 1);
const res = this.evaluate(this.revealFraction, this.elapsedMillis / 1000);
this.applyFrame(res);
if (this.revealFraction >= 1) { this.commit(); } else {
this.timerId = setTimeout((): void => this.tick(), 16);
}
}
时间轴归 ArkTS:16ms 帧循环累计原始 fraction 与原始秒数,点击宿主把 pace 切到 150ms(对应上游 InputPolicy.Block 下 tap 即 accelerate() 的输入法);几何与缓动归引擎:每帧 evaluate 取回遮罩点集、边缘参数、震颤偏移与卡片状态,applyFrame 把 points 拼成 SVG path 字符串刷新 @State。停止调用 evaluate,画面立即冻结——动画值确实来自 Kotlin。
5.2 页面交互
效果 Tab 自下而上:调色板圆点行(6 个上游调色板)、Theme Reveal 大标题与当前世界名、底部 4×3 Arrival 按钮网格。点按钮 → 以按钮中心为 origin 启动揭示;揭示中:incoming 世界容器 .clip(new Path({commands})) 按引擎遮罩裁剪、其上叠三层辉光(宽 = glowPx·pass/3、透明度 = 0.13·intensity)与焦边、主前沿线,整个世界按 shake 平移;tiles 样式不用遮罩,改为整版卡片——静止卡画旧世界色块、翻动卡用 .rotate({ x/y 轴, angle: dir·angle, perspective }) 加变暗层与 accent 描边。几何 Tab 是工程师视图:滑杆手动推进 fraction,300×400 预览区实时渲染,旁边列出最近一帧的原始 JSON 字段。关于 Tab 展示版本握手行、架构分层与仓库 / 社区链接。
5.3 对比度和错误边界
六个调色板深浅交替(Paper/Midnight/Mint/Plum/Citrus/Ember,与上游 sample Palettes.kt 同值),每档主操作按钮取 incoming 调色板 accent、文字取 onAccent,保证任何深浅组合下对比度成立;ok:false 时错误原文进状态行。Native 调用本身抛错(理论不该发生)同样被 try/catch 收进日志。"引擎真在跑"的验收法:揭示进行中暂停 ArkTS 帧循环(几何 Tab 手动模式天然如此)——拖一格滑杆画一帧,前沿形状/辉光/卡片角度随 fraction 变化而变化,全部数值可见地来自 evaluate 返回。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohosthemereveal)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ └── Bridge.kt @CName × 2 + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.themereveal、versionName 0.1.0-alpha01-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/libohosthemereveal_api.h
├── src/main/cpp/types/libthemereveal_napi/{index.d.ts, oh-package.json5}
├── src/main/ets/common/Palettes.ets 上游 6 调色板 + 12 Arrival 表
└── src/main/ets/pages/Index.ets ArkUI 三 Tab 验收页面
6.2 构建、签名和安装
./gradlew :theme-reveal:jvmTest # 14 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
# DevEco Studio 打开 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.themereveal -m entry
四、完整代码对照
4.1 整体架构
com.mofeejegi.themereveal/**(上游引擎 + Composable 原样编译)
│ ArrivalStyle × 12 / Envelope / TileBoard + cardTurn / ThemeRevealHost
ThemeRevealHeadless(库模块 ohosMain 无头门面)
│ 真实预设工厂 + 真实包络/排程 + 镜像前沿几何
Bridge.kt + MiniJson
│ ThemeRevealDispatch / ThemeRevealVersion
libthemereveal_napi.so
│ themeRevealDispatch / themeRevealVersion
Index.ets
│ 时间轴(16ms / 150ms)+ JSON parse + @State 更新
▼
ArkUI 真机页面(三 Tab:效果 / 几何 / 关于)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_compose_theme_reveal、sample/* 移出 |
gradle/libs.versions.toml | Kotlin 2.4.20-0.1.0-07 + Compose 双线(ohos 1.12.0-0.1.0-07 / jvm 1.12.0) |
build.gradle.kts(根) | 版本 0.1.0-alpha01-ohos.1、全局禁用 Ohos*Test* 任务 |
theme-reveal/build.gradle.kts | 纯 KMP:jvm + ohosArm64 + ohosX64,依赖按 ohosMain / jvmMain 双线分声明 |
theme-reveal/src/ohosMain/.../ThemeRevealHeadless.ohos.kt | 唯一新增库源文件:无头门面(真实调用 + 镜像几何) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 2 个 C ABI、MiniJson、定点数字格式化、错误通道 |
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 | 鸿蒙适配主文档(18 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | ArrivalStyle.iris(cut/bloom/…) | 12 个预设工厂(styles 表与 evaluate 的数据源) |
| 引擎 | Envelope.intensity(progress) | rampIn/rampOut 强度包络(wobble/辉光/震颤的统一缩放) |
| 引擎 | TileBoard.layout + cardTurn | 牌局几何与排程:整像素边缘、洗牌/波扫/源扩散次序、0.6 交错 |
| 引擎 | CubicBezierEasing(0.5, 0, 0.75, 0.75) | RevealController 的揭示缓动(缓入线性出) |
| 门面 | ThemeRevealHeadless.evaluate(...) | 一帧揭示几何的纯函数求值 |
| Native | ThemeRevealDispatch / ThemeRevealVersion | C ABI JSON 导出 |
| N-API | themeRevealDispatch(req) / themeRevealVersion() | 向 ArkTS 暴露能力 |
| ArkTS | evaluate(fraction, seconds) / tick() | 帧循环取几何并刷新 @State |
4.4 ArkTS 与 Kotlin 的边界
const res = JSON.parse(themeRevealDispatch(JSON.stringify({
op: 'evaluate', style: 'iris',
accent: incoming.accent, // "#FF6FA8FF"
progress: this.revealFraction, // 原始 fraction(未 ease)
time: this.elapsedMillis / 1000, // 原始秒(驱动 wobble/sway/shake)
width: this.hostWidthPx, height: this.hostHeightPx,
density: this.density, // display densityPixels
originX: this.originPx, originY: this.originPy
}))) as Frame;
// res.front.points → "M x,y L x,y … Z" → .clip(new Path({ commands }))
val style = ThemeRevealHeadless.presetStyle(name, accentArgb) // 真实上游工厂
val frame = ThemeRevealHeadless.evaluate(
style, width, height, density, originX, originY,
fraction, timeSeconds, // 双时钟原样进引擎
)
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:真编译 Compose 整库而不是提取内核或改写 UI
qrose 抽"零依赖编码器内核",是因为它的 Painter 层拖不动 Compose 依赖;compose-theme-reveal 没有"纯 Kotlin 内核"可抽——所有能力都长在 Compose API 上。既然社区制品线已经有完整的 Compose ohos klib(runtime / foundation / ui / animation),正确答案就是把上游 commonMain 连同 Composable 一起原样编译进 .so(shimmer 已验证),再用无头门面把绘制通道的输入输出暴露出来。改写 UI 成 ArkTS 动画等于把库手抄一遍,反而引入漂移风险。
决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 沿用 9.6.1
Compose ohos 线 1.12.0-0.1.0-07 基于 Kotlin 2.4.20 构建,旧钉法 2.2.21 读不了 2.4 元数据;上游本来就是 Kotlin 2.4.10,2.4.20 同代零降级。Gradle 方面 2.4.20 线支持 9.x(compottie / KStore 先例),上游 wrapper 9.6.1 原样保留——只有 2.2.21 旧线才必须降 8.14.1,两条工具链线的经验不能互相套用。
决策 3:根构建替换(cmp_qrose 模式),sample 留盘
上游根构建(AGP 9 KMP 插件、六个平台目标、vanniktech 发布)对鸿蒙没有保留价值,整个替换为极简 ohos 构建;上游五个构建文件原样存为 *.upstream 五件。sample/*(androidApp / iosApp / desktopApp / webApp / shared)是上游多平台示例,与鸿蒙交付无关,留盘不进 settings.gradle.kts——它们的存在方式只有这一种。
决策 4:门面放库模块 ohosMain,时间轴归 ArkTS、几何归引擎
TileBoard / cardTurn 是 internal,门面必须与库同模块才够得到;这也是全仓唯一新增库源文件的位置依据。职责切分上,withFrameNanos 帧时钟与 accelerate() 输入法是组合环境概念,跨不了 C ABI——ArkTS 镜像时间轴(16ms 帧循环、150ms 冲刺),每帧把原始 fraction 与原始秒交给引擎;缓动(CubicBezierEasing 同参曲线)、包络、牌局排程与前沿几何全部在 Kotlin 侧求值。"停帧即冻结"因此成立:画面上任何一帧的遮罩形状都来自当次 evaluate 返回。
决策 5:jvmTest 交叉验证 + 测试零改动
引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;14 项 jvmTest 跑的就是上游原测试文件,是"引擎没漂"的闸门。Compose 依赖双线声明(ohos 线无 *-desktop 子制品),测试统一 JVM 跑 + 根构建禁用 Ohos*Test*——测试文件一个字节不动,交叉验证的有效性才成立。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x86_64)、JDK 21、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与 HarmonyOS 真机(arm64)。
6.2 JVM 交叉验证
./gradlew :theme-reveal:jvmTest
14 项用例全部通过(上游原测试文件,零改动):
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
TileBoardTest | 9 | 牌局几何:列/行计算、整像素边缘、洗牌/波扫/源扩散的 places 分布、cardTurn 交错与钳制 |
EnvelopeTest | 3 | 包络:rampIn 上升、平台、rampOut 衰减、边界与参数校验 |
FrontShapeTest | 2 | 前沿形状:Tiles 正尺寸校验、默认参数 |
依赖换线没有 API 漂移:runtime / foundation / ui / animation 从 Maven Central 1.12.0 换到 eazytec 1.12.0-0.1.0-07 线后版本主体不变,上游测试照跑全绿——这正是"换线不换版"的直接证据。
6.3 构建与符号验证
:theme-reveal:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:theme-reveal:jvmTest: BUILD SUCCESSFUL(14 项)
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL(2m38s)
copyKnSoArm64 / copyKnSoX64: 三件套回填 example/harmonyApp/entry/libs/
llvm-nm -D: ThemeRevealDispatch / ThemeRevealVersion / libohosthemereveal_symbols 均为全局 T 符号(双 ABI)
CompileArkTS(修复 LineJoinStyle/LineCapStyle/HitTestMode 命名后): finished
PackageHap → 签名 → install bundle successfully → start ability successfully
KN so(debugShared,未 strip,含完整 Compose 运行时静态链接):arm64 28,196,128 字节(约 26.9 MiB)、x86_64 26,969,912 字节(约 25.7 MiB);libc++_shared.so 分别 1,850,184 / 1,731,432 字节。
6.4 功能用例
- 启动后关于 Tab 出现
themeRevealVersion() → 0.1.0-alpha01-ohos.1与{"op":"version"}的 Kotlin / Compose 制品线版本(来自真实 Native 调用,非静态文字); - 效果 Tab 默认 Paper 浅色世界,底部 4×3 网格列出 12 个 Arrival 按钮(与上游 sample 命名一致);
- 点 Iris:光圈从按钮中心扩散,前沿细描边 + 紧辉光,结束提交为 Midnight 深色世界(深浅交替);
- 点 Cut / Slash / Curtain:直缝/斜缝/竖幕从按钮中心向两侧分开,缝沿有 accent 辉光线;
- 点 Bloom:无前沿线的宽软光晕、花瓣状 wobble 双向旋转、前沿整体左右漂移;
- 点 Chaos:尖化频带撕裂的锯齿前沿、宽辉光、世界震颤,包络几乎立即起效;
- 点 Paper burn:亮 ember 前沿线 + 深色焦边带 + 暖辉光,页面安静燃烧无震颤;
- 点 Aperture:矩形门从 origin 等比张开,四角保持方正,全帧呼吸脉冲;
- 点 Flipboard / Cascade / Shutters:整版卡片按洗牌/对角波/百叶次序逐张 3D 翻转,翻动卡有 accent 描边与变暗,邻居交替方向(Flipboard);
- 揭示进行中点宿主空白处:揭示以 150ms 快速冲向全覆盖(加速不改终点);
- 几何 Tab:滑杆手动推进 fraction,预览区遮罩/卡片逐帧变化,旁边字段(progress/intensity/origin/shake/edge/points 数)随动——全部来自
evaluate返回; - 未知样式名 / 非法请求 → 状态行显示
ok:false与错误原文,不崩溃。
6.5 验证结论
揭示引擎(预设 / 包络 / 牌局排程 / 缓动 / 前沿几何)、Compose 运行时 ohos klib、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,效果 Tab 展示了真实的主题揭示会话:版本握手、12 种到达样式的全屏揭示、origin 定位、点击加速与几何 Tab 的逐帧原始数据(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/compose-theme-reveal.git
cd compose-theme-reveal
export JAVA_HOME="/path/to/jdk-21"
./gradlew :theme-reveal:compileKotlinOhosArm64 :theme-reveal:compileKotlinOhosX64 # klib
./gradlew :theme-reveal:jvmTest # 14 项交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
# DevEco Studio 打开 example/harmonyApp,Signing Configs 自动签名后:
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.themereveal -m entry
hdc shell hilog | grep ThemeRevealNapi
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 CMP 适配,必须让
:theme-reveal真正编译成ohosArm64/ohosX64klib 并链进动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;gradlew启动器认JAVA_HOME环境变量,gradle.properties的org.gradle.java.home要等 JVM 起来才生效——首编JAVA_HOME is not set就是这个;- settings 里 include
:example:nativeApp时example/目录必须已存在,否则 Gradle 9 报Configuring project with invalid directory——先建目录再启动构建; - Kotlin/Native 侧没有
java.lang.Math:Math.toDegrees要换成angle * (180f / PI.toFloat()); androidx.compose.ui.graphics.isSpecified是扩展属性,光 importColor不够,要显式import androidx.compose.ui.graphics.isSpecified;- 桥模块逐公式镜像上游几何时,
kotlin.math.min这类单函数 import 漏一个就报"None of the following candidates is applicable",且候选提示会误导到 Sequence 重载——按报错行号核对 import 列表; - ArkTS 5.1.0(18) 下形状描边属性名是
strokeLineJoin(LineJoinStyle.Round)/strokeLineCap(LineCapStyle.Round),不是strokeJoin(StrokeJoin…)/LineCap;命中测试枚举是HitTestMode.None,不是HitTestBehavior——16 个编译错误全部来自这三个命名; Circle().fill(...)在 5.1.0(18) 兼容线上报"SDK 26 才支持",改普通容器圆点(borderRadius+backgroundColor)规避;Path({commands})的.clip(...)只有弃用告警(功能正常),替代 API 在兼容线上的签名未核实前不动;- HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); - 签名材料由 DevEco 自动写入
build-profile.json5,仓库内保持空signingConfigs,证书绝不进入 AtomGit;so 三件套与 hvigor 产物全部 gitignore。
8.2 已知问题
- OHOS 社区 Compose 线尚无组合宿主:
ThemeRevealHost/rememberRevealControllerComposable 编进了.so但无法挂进 ArkTS 树,视觉由 ArkUI 按引擎几何复刻; - 卡片透视翻转用 ArkUI
rotate3D 近似,不是上游Matrix的完整二维射影映射(w = 1 − k·(x − axis)); RevealController的挂起提交与请求合并语义由 ArkTS 时间轴镜像;InputPolicy.Block的输入拦截近似为"点击宿主即加速";- mask 前沿多边形(radial / slit / box)是私有绘制函数的公式镜像,上游常量变更时门面需同步——"逐点一致"写进了验收底线;
- android / ios / js / wasmJs 目标剥离(源文件留在树里),发布插件一并剥离,本 fork 不发 Maven Central;
- ArkTS 页面存在
clip弃用告警(功能正常,替代 API 待核实后切换)。
8.3 未来优化方向
- 补全真机效果图矩阵(12 种到达样式逐一截图,入
doc/img/); - 用
hdc uitest写自动化截图脚本,把功能用例 12 条变成可回归的验收矩阵; - 评估
evaluate批量帧(一次请求返回 N 帧)以降低 JSON 往返频率; - 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 跟踪社区组合宿主进展,宿主可用后把
ThemeRevealHost直接接回 ArkTS 树,Demo 切换为真实双重组合。
九、总结
9.1 核心难点回顾
上游 theme-reveal(全部 commonMain,纯 Compose UI 库)
→ 验证社区 Compose ohos 制品线(runtime/foundation/ui/animation 1.12.0-0.1.0-07)
→ 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1 沿用)
→ 根构建替换(cmp_qrose 模式,Compose 依赖双线声明)
→ ohosArm64 / ohosX64 KLIB(上游源码 + Composable 原样)
→ 无头门面 ThemeRevealHeadless(真实预设/包络/排程 + 镜像几何)
→ C ABI(@CName × 2)+ 扁平 JSON(双时钟契约)
→ C++ N-API(DisposeString 纪律)
→ ArkTS 时间轴(16ms / 150ms)逐帧 evaluate
→ ArkUI 真机三 Tab 验收(12 种到达 × 6 调色板 / 几何 / 关于)
9.2 封装层次
- 引擎层:上游原版
ArrivalStyle预设、Envelope包络、TileBoard+cardTurn排程、RevealController状态机与CubicBezierEasing缓动; - 门面层:无头求值
evaluate,把绘制通道的输入输出收敛为纯函数; - Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理时间轴(帧循环 / 冲刺加速)、JSON 解析、Path 拼接、遮罩裁剪与卡片 3D 渲染;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 先确认库的"可无头化边界"再定路线:qrose 有纯 Kotlin 编码器可提取,KStore 有纯 Kotlin 引擎可整体复用,compose-theme-reveal 什么都没有——所有能力长在 Compose API 上。此时只要社区制品线齐备,「真编译 Compose 整库 + 无头门面」就是唯一不重写代码的路;路线选择必须基于库的分层事实,而不是套上一个库的成功模板;
- 时间轴与几何分权是纯 UI 库过桥的关键:把帧时钟、输入法这些"组合环境概念"留给 ArkTS 镜像,把缓动、包络、排程这些"引擎数学"留在 Kotlin 求值,中间用"原始 fraction + 原始秒"双时钟契约缝合——既保住了"引擎真在跑"的可验收性,又不需要把
withFrameNanos拖过 C ABI; - 交叉验证是"引擎没漂"的唯一可信证明:14 项
jvmTest跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;UI 库的 Demo 验收重点天然落在"画面每一帧都来自真实 Native 求值"这条停帧冻结检验上。
9.4 适配成果
当前适配已完成:
- compose-theme-reveal
0.1.0-alpha01-ohos.1真实引擎的双 ABI Kotlin/Native 编译(上游commonMain23 个文件零重写,唯一新增库源文件是无头门面); - 社区 Compose ohos 制品线在纯 Compose UI 库上的完整落地(runtime / foundation / ui / animation
1.12.0-0.1.0-07,官方坐标换线不换版); - 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号,双 ABI); - DevEco Stage ArkUI 三 Tab 验收页面(12 种到达 × 6 个上游调色板全屏揭示、origin 定位、150ms 冲刺、几何 Tab 原始数据、版本握手);
- 14 项 JVM 交叉验证全绿(上游原测试文件)+ HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证与效果图;
- 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)