开源鸿蒙平台 CMP 三方库「Cloudy」适配全流程
本文记录
Cloudy接入 OpenHarmony 的完整过程,覆盖纯 Compose 特效库的依赖度盘点、鸿蒙工具链选型、源码集拆分(composeMain 停车 + 三处外科手术)、Mirage 着色器引擎的ohosArm64/ohosX64真编译、Kotlin/Native 动态库、C ABI/N-API 桥接、CPU 模糊光栅化、ArkUI 真机页面、HAP 构建和真机验收。
Cloudy是 skydoves 的 Kotlin Multiplatform 特效库:Modifier.cloudy()自模糊与背景模糊、Sky渐进模糊、liquidGlass液态玻璃、Mirage开放着色器栈(eDSL 编写内核、编译器降级 AGSL/SKSL/GLES 三方言),Android 走 RenderEffect/AGSL/GLES 加 native CPU 回退,skiko 平台走 Skia。本次适配与 KStore(不碰 UI、依赖制品线齐全、真编译整库)恰好是光谱的两端:Cloudy是纯 Compose UI 库,公开 API 全部是Modifier扩展,社区 Compose-for-OHOS 制品线尚未对齐上游 Compose 1.11.x / Kotlin 2.4,一行import androidx.compose.*今天都编不进 ohos。因此本路线回到 cmp_qrose / day-and-night-switch 审核通过的方案:Compose 面字节不动git mv停靠composeMain,把库中真正与 UI 无关的引擎——Mirage eDSL、三方言编译器、内核库、uniform schema 机制——真编译成 OpenHarmony KLIB,经@CNameC ABI + C++ N-API 输出扁平 JSON,ArkTS 三 Tab 页面作为验收宿主;库的核心语义"模糊"则以 CPU 3-pass box 光栅化在桥上复述,真机可对比、可调参、可计时。
项目地址: AtomGit/oh-tpc/Cloudy.git
上游仓库: skydoves/Cloudy
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
Cloudy 为 Compose Multiplatform 应用提供"内容与背景的实时模糊"这一件事的完整答案:Modifier.cloudy() 一行给任意组件加自模糊(Android 31+ RenderEffect GPU、30- native C++ CPU、iOS/macOS/Desktop Skia BlurEffect、WASM WebGL);Modifier.cloudy(sky = ...) 在模糊之上叠加 Sky 渐进梯度——上缘清晰、向下渐浓,配 CloudyState 可交互控显;liquidGlass 是 Apple 风格的折射+高光镜头;Mirage 则把着色器开放给业务:用类型化 eDSL(Float2/Half4、运算符与 swizzle 糖、branch/guard 控制流)编写内核,MirageCompiler 降级为 AGSL(Android 33+)、SKSL(skiko 全平台)或 GLSL ES(Android 29-32 离屏 FBO),内置 duotone/chromatic/specular/foil/rainyWindow 预设目录。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、WASM 共享。OpenHarmony 应用同样需要这套特效能力,但上游构建(Kotlin 2.4.0 / Compose 1.11.1 / AGP 9.2.1 / Gradle 9.6.1)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明着色器引擎与模糊语义在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证引擎内核、编译器输出、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Kotlin 2.4.0 / Gradle 9.6.1 / AGP 9.2.1(com.android.kotlin.multiplatform.library)与鸿蒙工具链无法共存于一个 Gradle 构建。 |
| 依赖矩阵 | 整个 Compose 面(runtime/foundation/ui + skiko)没有 ohos 制品——这是与 KStore 的本质差别:KStore 的 kotlinx 四件套有 ohos 线,Compose 没有。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 特效语义跨不了桥 | Modifier 是 Compose 组合期概念;GraphicsLayer/RenderEffect/skia ImageFilter 都是宿主渲染管线的类型,C ABI 只能同步一来一回。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:纯 Kotlin 引擎(Mirage eDSL + 编译器 + 内核库)的 ohos 编译、Compose 面的停车隔离、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。着色器编译语义完全由上游原版实现负责,模糊语义由桥上的 CPU 光栅化复述,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
Demo 提供一次真实的特效会话,对应桥接的全部入口:
- 预设目录(presets):
MiragePresets.names返回 7 个内置 look(chromatic / oilSlick / soapBubble / metallicFoil / pearl / foil / duotone); - uniform schema(schema):按声明序返回任意预设的 uniform 布局(名称 / GLSL 类型 / 默认值 / isColor / isTexture);
- 着色器编译(shader):把预设经真实 MirageCompiler 编译为
sksl/agsl/glslEs三种方言之一的完整程序源码——与 Android AGSL、skiko SKSL 走的同一条上游代码路径; - 模糊(blur):对 base64 RGBA 缓冲做 CPU 高斯近似模糊(3-pass box),支持
uniform全幅与progressive上缘渐显两种模式——上游Modifier.cloudy()/Sky渐进语义在无 GPU RuntimeShader 宿主下的 Kotlin 复述; - 版本握手:
cloudyVersion()返回 fork 版本号,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
source / length | shader 返回的完整着色器源码与字符数。 |
uniforms | schema 返回的 uniform 布局数组。 |
bytes / elapsedMs | blur 返回的 base64 RGBA 结果与耗时。 |
引擎侧完整能力(eDSL 运算符与 swizzle、branch/guard 控制流、编译器预利用分析与标准 uniform 门控、GLSL ES 翻译器、内核库、程序缓存键)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 19 项 desktopTest 交叉验证用例(含 skia RuntimeEffect 真实执行 SKSL 的光栅测试),证明 ohos .so 里执行的引擎逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 31 个 Compose 文件 git mv 停靠 composeMain 字节不动;18 个纯引擎文件原样编译;仅 3 个边界文件做外科手术式拆分 + 新增 1 个公共入口;不重写任何内核常量。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm("desktop") 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);app / app-android / benchmark / docs / cloudy-native(Android NDK)/ iosApp 留盘不进构建。 |
| 桥接稳定 | 使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | Kotlin 钉社区批准的 2.2.21-1.0.0 老线(Gradle 随线降 8.14.1);测试统一 JVM 跑,上游测试零改动。 |
| UI 完整 | 三 Tab 覆盖全部桥接入口:模糊对比与调参、Mirage 预设编译输出、版本握手。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。
Cloudy的 Compose 面(Modifier扩展、@Composable状态)整体停车等待社区制品线对齐;ArkTS Demo 是桥暴露的引擎能力与模糊语义的验收宿主。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 49 个 commonMain 文件的依赖度、界定"纯引擎"边界、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版(Gradle 降 8.14.1)、测试依赖处置
第 3 阶段:源码集拆分 ── 31 文件停靠 composeMain、三处外科手术(ShaderValues/MirageParams/StdUniforms)、MiragePresets 公共入口
第 4 阶段:桥协议与光栅化 ── 4 op JSON 契约、错误通道、CPU 3-pass box 模糊与 progressive 渐显
第 5 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 6 阶段:ArkUI 宿主 ── 三 Tab 验收页、HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
cloudy/ 上游特效库(进 ohos 构建:jvm("desktop") + ohosArm64 + ohosX64)
cloudy/src/commonMain/ 纯 Kotlin 引擎(18 个上游文件原样编译 + 新增 StdUniforms/MiragePresets)
cloudy/src/composeMain/ 上游 Compose 面停车(31 文件 git mv 字节不动 + 1 个拆分侧文件)
cloudy/src/composeTest/ 依赖 Compose/skia 场景的上游测试停车(23 文件)
cloudy/src/{android,ios,macos,wasmJs,skiko}Main/ 上游平台源集原位保留(目标未启用,Gradle 不编译)
cloudy-native/ Android NDK RenderScript CPU 模糊 .so,留盘不进 settings
app/ app-android/ benchmark/ docs/ iosApp/ 上游示例与基准,留盘不进构建
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson + BlurRasterizer)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
README.upstream.md 上游原文保存
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :cloudy 与 :example:nativeApp,rootProject.name = "ohos_cloudy"。上游的 spotless / dokka / vanniktech 发布 / roborazzi / AGP classpath / Compose 编译器参数全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven,社区批准老线) |
| JDK | 21(org.gradle.java.home 指向本机 JDK,Windows 下无系统 java 时必须显式指定) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm("desktop") 保留作交叉验证) |
| Gradle | 8.14.1(上游 9.6.1 降级;2.2.21 线不支持 Gradle 9.x,与 KStore 的 2.4.20 线经验相反) |
| 测试 | kotest 6.2.1(JVM 跑)+ org.jetbrains.compose.desktop:desktop-jvm-windows-x64:1.11.1(skia 光栅测试) |
| DevEco product | 5.1.0(18) 兼容、modelVersion 5.1.0 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.cloudy、versionName 1.0.0-alpha01-ohos.1 |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
上游 cloudy/src/commonMain 共 49 个 .kt 文件,其中 32 个 import androidx.compose.*。逐文件扫描(import 级 + 同包符号级复查)后得到拆分总账:
| 处置 | 文件数 | 内容 |
|---|---|---|
| 整体停靠 composeMain | 30 | Modifier 扩展(Cloudy/CloudyBackground/LiquidGlass/MirageModifier)、Sky/CloudyState 状态、预设目录 MirageShaders、RainyWindow、expect 声明(PlatformBitmap/GyroLight/MirageBackend 等 17 处)与引擎背面的 Effect 链(Effect/EffectNode/MirageFilterChain/MirageBackendProgram/MirageProgramCache…) |
| 原样保留编译 | 18 | eDSL(Branch/Expression/ShaderFunction/TraceContext/Diagnostics/ReservedNames/RuntimeEffectEmitter/ShaderValues 主体)、编译器(MirageCompiler/CompiledProgram/MirageGlslEs/MiragePreamble/MirageKernels/MirageProgramCache 除外)、MirageShader、MirageParams 主体、LiquidGlassShaderSource、ExperimentalMirage |
| 外科手术拆分 | 3 | ShaderValues.kt(拆出 5 个 Compose 声明)、MirageParams.kt(换纯 float2 声明)、MirageUniformBinding.kt(STD_* 常量下沉) |
| 新增 | 2 | internal/StdUniforms.kt(下沉的 3 个常量)、MiragePresets.kt(纯 Kotlin 公共入口) |
expect/actual 的盘点结论很关键:上游全部 expect 声明都在 Compose 面文件里(Modifier.cloudy 系列、PlatformBitmap、MirageBackendProgram、currentDialect 等 17 处),它们随停靠整体离开编译;留下的纯引擎没有一处 expect——这就是"引擎可以独立编译"的直接证据。上游 desktopMain 仅有的 2 个 actual(GyroLight/PlatformBitmap)是对停靠 expect 的实现,同步停靠到 composeMain/desktop/。
1.4 页面边界
Tab 切换 Blur / Mirage / About
Blur Tab 原图 vs 模糊结果双图对比(程序化生成 360×520 渐变+圆斑测试图)
radius 滑杆(1-64)、uniform/progressive 开关、运行按钮、耗时状态行
Mirage Tab 7 预设 × 3 方言下拉、编译按钮、uniform schema 表、着色器源码滚动区
About Tab 版本握手(绿/红圆点 + 真实 Native 返回的版本串)、架构分层、仓库链接
页面启动时先调 cloudyVersion() 做握手并断言版本号,再拉取预设目录。
第 2 阶段:目标与依赖打通
2.1 制品线验证(本适配的成败手)
先查社区 Maven(https://maven.eazytec-cloud.com)上的工具链与依赖形态,得到三个决定路线的结论:
org.jetbrains.kotlin.multiplatform:2.2.21-1.0.0(HarmonyOS Kotlin)
→ 提供 ohosArm64() / ohosX64() 目标与 konan ohos sysroot(aarch64-linux-ohos / x86_64-linux-ohos)
org.jetbrains.compose.*:1.11.x → 无 ohos 变体(社区线未对齐上游)
io.kotest:kotest-*:6.2.1 → JVM 可用,ohos 测试编译不可用(无制品)
org.jetbrains.compose.desktop:desktop-jvm-windows-x64:1.11.1 → JVM 可用(skia 光栅测试恢复)
其一,Kotlin 2.2.21-1.0.0 是社区早期批准的钉法(qrose / haze 同线),本库纯引擎不依赖任何 kotlinx 制品,老线足够;其二,Compose 无 ohos 线决定了"真编译整库"不可行(与 KStore 的路线分叉点),必须停车隔离;其三,kotest 没有 ohos 制品,测试统一 JVM 跑(见 2.4)。
2.2 工具链钉版(为什么是 2.2.21-1.0.0 + Gradle 8.14.1)
KStore 钉 2.4.20-0.1.0-07 是被 coroutines 制品线的 stdlib 元数据顶上去的;Cloudy 没有这类依赖牵引——纯引擎零第三方依赖,取社区批准的老线 2.2.21-1.0.0 最大化复用既有审核经验(qrose / haze / flagpack / day-and-night-switch / tagcloud 全在这条线上)。代价是 Gradle 必须从上游 9.6.1 降到 8.14.1:2.2.21 线的 Kotlin Gradle 插件不支持 Gradle 9.x。wrapper 一行改动,其余构建语义不变。
settings.gradle.kts 把 eazytec 仓库写在最前:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
gradlePluginPortal()
google()
mavenCentral()
}
}
rootProject.name = "ohos_cloudy"
include(":cloudy")
include(":example:nativeApp")
gradle.properties 钉版本并放开 native 分发下载:version=1.0.0-alpha01-ohos.1(上游版本 -ohos.N 规则)、kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true。根构建收敛为 13 行:版本从 gradle.properties 读入 allprojects,subprojects 全局禁用名字含 Ohos + Test 的任务。
2.3 编译纯引擎
cloudy/build.gradle.kts 收敛为纯 KMP 模块:
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
applyDefaultHierarchyTemplate()
jvmToolchain(21)
jvm("desktop") // 交叉验证用(含 skia 光栅测试)
ohosArm64()
ohosX64()
sourceSets {
commonMain.dependencies {} // 纯引擎:零第三方依赖
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.kotest.framework.engine)
implementation(libs.kotest.assertions.core)
}
val desktopTest by getting {
dependencies {
implementation(libs.kotest.runner.junit5)
implementation(libs.compose.desktop.windows) // skia RuntimeEffect 直连,见 2.4
}
}
}
}
实测:
./gradlew :cloudy:compileKotlinOhosArm64 :cloudy:compileKotlinOhosX64 # BUILD SUCCESSFUL
eDSL、编译器、内核库、MirageShader/MirageParams(纯化后)、MiragePresets 全部进入 ohos KLIB——这就是"引擎真编译"的落点。
2.4 测试依赖的处置:kotest 没有 ohos 制品,skia 从哪来
两条纪律的组合。其一,commonTest 的 kotest 依赖对 ohos 测试编译同样可见,解析不到 ohos 变体就会卡死 compileTestKotlinOhosArm64——处置与 KStore 相同:测试统一在 JVM 跑(desktopTest),根构建全局禁用 ohos 测试任务,上游测试文件零改动。
其二,保留的 eDSL 光栅测试通过 org.jetbrains.skia.RuntimeShaderBuilder / RuntimeEffect 真实执行编译出的 SKSL——上游这依赖来自 compose.desktop.currentOs 扩展,而 compose 插件已随根构建替换移除。处置是绕开插件直连制品坐标:
compose-desktop-windows = { module = "org.jetbrains.compose.desktop:desktop-jvm-windows-x64", version.ref = "composeDesktop" } # 1.11.1
Kotlin 2.2.21 编译器读取该 JVM 制品无障碍(只引用其中的 skia 类,未触 compose 符号),19 项测试含光栅用例全部保活。
第 3 阶段:源码集拆分(本次适配的核心工作量)
3.1 停车机制与三处外科手术
composeMain 不是声明的源集——不挂任何 target,Gradle 永不编译它,这就是"停车"。31 个文件 git mv 进去,字节不动、历史可追溯(git rename 相似度 100%)。但三个边界文件不能整体停靠,因为纯引擎引用了它们里面的符号:
| 文件 | 手术 | 原因 |
|---|---|---|
edsl/ShaderValues.kt | 拆出 5 个 Compose 声明(color(Color) / offset(Offset) / size(Size) / mix(Half4, Color, Float1) / UTexture.eval)到新文件 composeMain/.../ShaderValuesCompose.kt,其余(值接口、全部运算符与 swizzle 糖、color(argb: Long)、UFloatArray.get)留在 commonMain | eDSL 主体是纯的,只有 5 个 Compose 类型转换糖不是 |
MirageParams.kt | 移除 4 个 Compose 成员(uniform(Offset) / uniform(Size) / uniformColor(Color) / texture(ImageBitmap, TileMode))与 4 个句柄类(UOffset/USize/UColor/UTexture);新增纯 uniform2(FloatArray): UniformProvider<UVec2> 与 UVec2 : UniformHandle, Float2 | MirageShader<P : MirageParams> 与编译器的 schemaOf(paramsFactory) 需要这个类留在 commonMain;它原来唯一的 float2 声明通道是 Offset/Size 重载 |
internal/MirageUniformBinding.kt(已停靠) | 把 STD_RESOLUTION / STD_TIME / STD_DENSITY 三个常量逐字抽到新文件 commonMain/.../internal/StdUniforms.kt | 纯 MirageCompiler 引用这三个标准 uniform 名做发射门控 |
"为什么 MirageParams 要动刀"值得展开:上游 MirageShader 是 sealed class MirageShader<P : MirageParams>,MirageCompiler.compile(shader, dialect) 内部调 schemaOf(shader.paramsFactory) 读 schemaEntries——泛型边界把 params 类钉死在 commonMain。而上游声明 float2 uniform 的唯一途径是 uniform(Offset) / uniform(Size)(句柄分别是 UOffset/USize,且为 traced body 实现 Float2 接口)。纯化方案是同类型等价替换:uniform2(FloatArray) 发射同样的 float2 GLSL 类型、同样的 uniform 名、同样的声明序;UVec2 与 UOffset 一样实现 Float2,traced body 里 lensCenter.x、lensSize * 0.5f 原样可用;Float.NaN 分量顶替上游 Offset.Unspecified 自动取景哨兵(默认值从不进入编译出的源码,绑定侧两者同义)。uniformColor(Color) 与 uniform4(FloatArray) 都发射 float4,仅 Android 绑定期的色彩空间转换不同。
3.2 新公共入口:MiragePresets
上游预设目录 MirageShaders(object)整体停靠——它的参数类用了 Offset/Size/Color 默认值。桥需要预设,于是按 qrose 的 Qrose 对象先例新增纯 Kotlin 公共入口 MiragePresets:
| 上游声明 | 镜像声明 | 差异 |
|---|---|---|
MirageLensParams.lensCenter: UOffset by uniform(Offset.Unspecified) | PresetLensParams.lensCenter: UVec2 by uniform2(floatArrayOf(NaN, NaN)) | 同 float2 槽位,NaN 哨兵 |
ChromaticParams.chromaticKRGB: UVec4 by uniform4(krgb) | PresetChromaticParams.chromaticKRGB: UVec4 by uniform4(krgb) | 逐字相同 |
DuotoneParams.shadow: UColor by uniformColor(Color(0xFF1B1B3A)) | PresetDuotoneParams.shadow: UVec4 by uniform4([27/255, 27/255, 58/255, 1]) | 同 float4 槽位,sRGB 通道复述 |
MirageShaders.Duotone(traced body 用 UColor) | MiragePresets.Duotone(raw kernel:DUOTONE_KERNEL_AGSL/SKSL 编译器常量) | 内核字符串是编译器自己的常量,字节相同 |
内核体(chromatic 的牛顿环薄膜、foil 的眩光彩虹闪烁)从上游 MirageShaders.kt 逐字镜像,一个常量未改——差异 exclusively 在参数层。公开 API 四个:names / preset(name) / compile(name, dialect) / uniformSchema(name),内部走真实 MirageCompiler。7 个预设覆盖全部三个编译类别:Chromatic 家族五个 look(Composite)、Foil(Generate)、Duotone(Colorize)。
3.3 测试处置
| 处置 | 文件 | 数量 |
|---|---|---|
| 保留(纯) | commonTest:MirageBackendBandTest;desktopTest:MirageBranchTest / MirageLoopTest / MirageDiagnosticsTest / MirageReservedNamesTest / MirageShaderFunctionTest / MirageWriteSwizzleTest / RasterTestUtils | 8 |
| 停靠 composeTest | 依赖 Compose 类型、skia 场景(ImageComposeScene)、停靠符号(MirageShaders/Sky/MirageProgramCache/RainyWindowOptic)的用例 | 23 |
上游测试文件一个字节不动;保留的 8 个里包含通过 skia RuntimeEffect 真实执行 SKSL 的分支/循环/诊断用例——这比"只断言源码字符串"强一档。
第 4 阶段:桥协议与光栅化
4.1 JSON 契约
com.skydoves.cloudy/**(上游引擎原样编译 + MiragePresets 公共入口)
↓ MirageCompiler.compile / uniformSchema / MirageShader 工厂
Bridge.kt(@CName × 2,扁平 JSON 进出)+ BlurRasterizer
↓ UTF-8 JSON
N-API cloudyVersion() / cloudyDispatch()
↓
ArkUI Index.ets(三 Tab)
请求与响应:
请求 : {"op":"presets"}
{"op":"schema","name":"chromatic"}
{"op":"shader","name":"duotone","dialect":"sksl"}
{"op":"blur","width":360,"height":520,"pixels":"<base64 RGBA>",
"radius":25,"mode":"progressive","startY":0.0,"endY":0.45}
响应 : {"ok":true,"presets":[...]}
{"ok":true,"name":"...","uniforms":[{"name","type","default","isColor","isTexture"}]}
{"ok":true,"name":"...","dialect":"...","length":N,"source":"完整着色器源码"}
{"ok":true,"width":W,"height":H,"bytes":"<base64 RGBA>","radius":R,"mode":"...","elapsedMs":T}
{"ok":false,"error":"..."}
像素载荷选 base64 RGBA 而不是 PNG:ArkTS 侧 PixelMap.readPixelsToBuffer 一步拿到 RGBA 缓冲、createPixelMap(buffer, opts) 一步回显,省掉两侧的图像编解码——桥只做运算,不做格式转换。
4.2 CPU 光栅化:3-pass box ≈ Gaussian
BlurRasterizer 用 Kovesi/W3C 的三连 box 逼近高斯(UI 模糊常用的半径下与真高斯误差 < 3%):boxesForGauss(sigma, 3) 求三个 box 半径,每个 box 水平/垂直分离、滑动窗口累加、边缘钳制,三轮迭代输出。progressive 模式镜像上游 Sky 渐进的边缘衰减语义:startY/endY(高度比例)之间按 smoothstep 逐行混合原图与全模糊图,之上全清晰、之下全模糊。半径 1-64 钳制、宽高 ≤ 4096 校验,耗时用 TimeSource.Monotonic 回显。
4.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("CloudyDispatch")
fun cloudyDispatch(request: String): String = try {
/* op 分发 */
} catch (t: Throwable) {
err("${t::class.simpleName}: ${t.message}")
}
非法 JSON、缺字段、未知 op/预设/方言、尺寸越界、base64 损坏、长度不匹配,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。
第 5 阶段:原生桥接(技术难点)
5.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libcloudy_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohoscloudy.so)
│ CloudyDispatch / CloudyVersion
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复述着色器编译语义,所以选择 C ABI + JSON。
5.2 Kotlin/Native 导出函数
@CName("CloudyDispatch")
fun cloudyDispatch(request: String): String // presets / schema / shader / blur 一口进
@CName("CloudyVersion")
fun cloudyVersion(): String // 版本握手(真机版本行的数据来源)
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
CloudyDispatch | cloudyDispatch(req) | 4 op 分发 + MirageCompiler + BlurRasterizer |
CloudyVersion | cloudyVersion() | fork 版本握手 |
实现细节沿 qrose 已验证模式:@CName 在 2.2.21 工具链下靠默认导入解析(不写 kotlinx.cinterop.CName);base64 用 kotlin.io.encoding.Base64 标准库;JSON 用手写 MiniJson(递归下降,~150 行),桥保持零第三方依赖的 .so。
5.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"cloudyDispatch", nullptr, NapiCloudyDispatch, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"cloudyVersion", nullptr, NapiCloudyVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohoscloudy_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohoscloudy_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。
5.4 CMake 和类型声明
add_library(ohoscloudy SHARED IMPORTED)
set_target_properties(ohoscloudy PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(cloudy_napi SHARED napi_init.cpp)
target_link_libraries(cloudy_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoscloudy)
OHOS_ARCH 为空或 libohoscloudy.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const cloudyDispatch: (request: string) => string;
export const cloudyVersion: () => string;
copyKnSoArm64 / copyKnSoX64 Copy 任务在 linkDebugSharedOhos* 之后自动把 libohoscloudy.so 和对应 triple(aarch64-linux-ohos / x86_64-linux-ohos)的 libc++_shared.so 回填 entry/libs/<abi>/。
第 6 阶段:ArkUI 宿主
6.1 三 Tab 与像素往返
aboutToAppear(): void {
const v: Record<string, Object> = JSON.parse(cloudyVersion()) as Record<string, Object>;
this.versionOk = String(v.version) === EXPECTED_VERSION; // 版本握手断言
this.buildSourceImage(); // 程序化 360×520 测试图
}
runBlur(): void {
const buffer = new ArrayBuffer(SRC_W * SRC_H * 4);
this.srcPixel.readPixelsToBuffer(buffer) // PixelMap → RGBA
.then(() => {
const b64 = new util.Base64Helper().encodeToStringSync(new Uint8Array(buffer));
const resp = JSON.parse(cloudyDispatch(`{"op":"blur","width":${SRC_W},...` +
`"pixels":"${b64}","radius":${Math.round(this.radius)},"mode":"${mode}"}`));
// → bytes base64 → decodeSync → createPixelMap → @State 回显右侧对比图
});
}
Mirage Tab 拉预设/方言下拉,编译后把 source 滚动展示、uniforms 渲染成表;ArkTS 侧严格用 Record<string, Object> + String() / Number() 显式转换,不写隐式 any。
6.2 构建、签名和安装
./gradlew :cloudy:desktopTest # 19 项交叉验证
./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.cloudy -m entry
四、完整代码对照
4.1 整体架构
com.skydoves.cloudy.edsl/**(上游 eDSL 原样编译)
com.skydoves.cloudy.internal/**(上游 MirageCompiler/CompiledProgram 内核原样编译)
│ MirageShader 工厂 + MiragePresets 公共入口(纯化参数层)
BlurRasterizer + Bridge.kt + MiniJson
│ CloudyDispatch / CloudyVersion
libcloudy_napi.so
│ cloudyDispatch / cloudyVersion
Index.ets
│ JSON parse + PixelMap 往返 + @State 更新
▼
ArkUI 真机页面(三 Tab)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_cloudy、上游示例模块移出 |
gradle/libs.versions.toml | Kotlin 2.2.21-1.0.0 + kotest 6.2.1 + desktop-jvm-windows-x64 钉版 |
build.gradle.kts(根) | 版本 1.0.0-alpha01-ohos.1、全局禁用 Ohos*Test* 任务 |
cloudy/build.gradle.kts | 纯 KMP:jvm(“desktop”) + ohosArm64 + ohosX64 |
cloudy/src/composeMain/** | 上游 Compose 面停车(31 文件 + ShaderValuesCompose 侧文件) |
cloudy/src/composeTest/** | 上游 Compose/skia 场景测试停车(23 文件) |
cloudy/.../MirageParams.kt(编辑) | 4 Compose 成员/句柄类 → uniform2 + UVec2 |
cloudy/.../edsl/ShaderValues.kt(编辑) | 5 个 Compose 转换糖拆出 |
cloudy/.../internal/StdUniforms.kt(新增) | STD_* 标准 uniform 名下沉 |
cloudy/.../MiragePresets.kt(新增) | 纯 Kotlin 预设目录公共入口(7 预设) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 2 个 C ABI、4 op 分发、MiniJson、错误通道 |
nativeApp/.../BlurRasterizer.kt | 3-pass box 高斯近似 + progressive 渐显 |
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 | 鸿蒙适配主文档(24 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | MirageShader.composite / colorize / generate | 上游工厂:traced body → 内核文本(原样编译进 .so) |
| 引擎 | MirageCompiler.compile(shader, dialect) | 三方言降级:AGSL / SKSL / GLSL ES(上游原件) |
| 引擎 | MirageCompiler.schemaOf(paramsFactory) | 从 params 声明序读 uniform 布局 |
| 入口 | MiragePresets.compile(name, dialect) | 桥用公共入口:预设 → 完整源码 |
| 入口 | MiragePresets.uniformSchema(name) | 预设 → uniform 布局(声明序 = 绑定序) |
| 光栅 | BlurRasterizer.blur / progressiveBlur | CPU 高斯近似 / 上缘渐显 |
| Native | CloudyDispatch / CloudyVersion | C ABI JSON 导出 |
| N-API | cloudyDispatch(req) / cloudyVersion() | 向 ArkTS 暴露能力 |
| ArkTS | runBlur() / runShader() | 触发操作并刷新 UI |
4.4 ArkTS 与 Kotlin 的边界
const src = cloudyDispatch('{"op":"shader","name":"chromatic","dialect":"sksl"}');
// JSON.parse(src).source 是完整 SKSL 程序:preamble + uniform 声明 + 内核
const blur = cloudyDispatch(`{"op":"blur","width":360,"height":520,
"pixels":"${b64}","radius":25,"mode":"progressive","startY":0,"endY":0.45}`);
// JSON.parse(blur).bytes → base64 RGBA → createPixelMap 回显
"shader" -> MiragePresets.compile(name, dialect) // 真实 MirageCompiler
"blur" -> BlurRasterizer.progressiveBlur( // CPU 复述 cloudy 核心语义
pixels, width, height, radius, startY, endY)
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:Compose 面停车,而不是重写或硬接
让 27+ 个 import androidx.compose.* 的文件编进 ohos 只有两条路:等社区 Compose-ohos 制品线对齐(不可控),或手工降级改写每个文件(等于把上游 fork 成另一个库,接回成本无穷大)。停车 composeMain(非声明源集,Gradle 永不编译)让上游文件字节不动——制品线对齐后 git mv 回原位即可接回,这是 qrose / day-and-night-switch 已审核的方案。
决策 2:工具链钉 2.2.21-1.0.0 老线,Gradle 降 8.14.1
与 KStore(2.4.20 线留 Gradle 9.x)相反:Cloudy 纯引擎零第三方依赖,没有 stdlib 元数据顶版本的牵引,取社区批准老线最大化复用审核经验;代价是 Gradle 9.6.1 → 8.14.1 的 wrapper 降级。两条工具链线的经验不能互相套用——选线依据是"依赖矩阵要什么",不是"哪条更新"。
决策 3:“纯内核"定义为 Mirage 引擎,而不是"没有内核”
day-and-night-switch 证明了纯 UI 库的可复用资产是视觉模型;Cloudy 的对应物是它的引擎层:eDSL 是类型化着色器语言(纯 Kotlin),编译器是三方言降级器(纯 Kotlin),内核库是字符串常量。把这三层真编译进 ohos,ArkTS 拿到的就是与 Android AGSL / skiko SKSL 同源的编译产物——不是"复述的着色器",是同一个编译器的输出。
决策 4:镜像参数层,而不是让引擎依赖 Compose
MirageShader<P : MirageParams> 的泛型边界把参数类钉在 commonMain,而上游参数类的 uniform 声明通道是 Compose 类型。方案是外科手术 + 类型等价替换(uniform2/UVec2 同 float2、uniform4 同 float4、NaN 哨兵同 Unspecified),加上 MiragePresets 镜像目录(内核体逐字照抄)。视觉常量零漂移,接回时 git revert 三个文件的编辑即可。
决策 5:desktopTest 交叉验证 + skia 直连
引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;19 项 desktopTest 跑的是上游原测试文件(零改动),其中分支/循环用例通过 skia RuntimeEffect 真实执行 SKSL——这是"内核没漂"的闸门。skia 依赖被 compose 插件移除后,直连 desktop-jvm-windows-x64 制品恢复,不重新引入插件。
六、测试与验证
6.1 测试环境
本次验证使用 Windows(x86_64)、JDK 21(本机 JDK,org.gradle.java.home 显式指定)、Gradle 8.14.1、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机。
6.2 JVM 交叉验证
./gradlew :cloudy:desktopTest
19 项用例全部通过(上游原测试文件,零改动):
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
MirageBackendBandTest | 3 | 频段布局(commonTest) |
MirageBranchTest | 5 | eDSL 分支降级 + skia RuntimeEffect 真实执行 |
MirageLoopTest | 3 | eDSL 循环展开 + 光栅断言 |
MirageShaderFunctionTest | 3 | 用户自定义着色器函数发射 |
MirageDiagnosticsTest | 2 | 编译期诊断(保留名冲突等) |
MirageWriteSwizzleTest | 2 | 写 swizzle 校验 |
MirageReservedNamesTest | 1 | 保留标识符拦截 |
拆分只动了参数层与 Compose 糖,eDSL/编译器/内核的发射行为被这 19 项钉死——ohos .so 里的引擎与上游 JVM 行为一致。
6.3 HAP 验证
:cloudy:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs/{arm64-v8a,x86_64}
llvm-nm -D: CloudyDispatch / CloudyVersion 均为全局 T 符号(双 ABI)
BuildNativeWithCmake: finished(双 ABI 编出 libcloudy_napi.so)
CompileArkTS: finished / PackageHap: finished / BUILD SUCCESSFUL
install bundle successfully / start ability successfully
libohoscloudy.so(debugShared,未 strip,含 Kotlin/Native 运行时)实测 arm64 约 4.4 MB、x86_64 约 4.1 MB;HAP 内双 ABI 各三件套(libcloudy_napi.so / libohoscloudy.so / libc++_shared.so)。
6.4 功能用例
- 启动后 About Tab 版本握手绿点,
cloudyVersion()返回1.0.0-alpha01-ohos.1(真实 Native 调用,页面断言期望值); - Blur Tab 自动生成 360×520 渐变+圆斑测试图,左侧原图渲染正常;
- 默认参数点运行,右侧回显 uniform 模糊结果,状态行显示模式、半径与耗时(elapsedMs);
- 拖动 radius 滑杆(1-64)重跑,模糊强度随半径单调变化;
- 打开 progressive 开关重跑,上缘清晰、向下渐浓——
startY=0/endY=0.45的 smoothstep 渐显肉眼可辨; - Mirage Tab 任选预设与方言点编译,源码区滚动展示完整程序(uniform 声明 + 内核),schema 表按声明序列出 uniform 布局;
- 7 个预设 × 3 种方言全部返回
ok:true; - 构造非法请求(未知 op / 未知预设 / 长度不匹配的 pixels),错误原文进状态行,应用不崩溃。
6.5 验证结论
Mirage eDSL、三方言编译器、内核库、uniform schema 机制、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的特效会话:版本握手、CPU 高斯/渐进模糊对比与调参、Mirage 预设的编译器级源码输出(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/Cloudy.git
cd Cloudy
export JAVA_HOME="/path/to/jdk-21"
./gradlew :cloudy:compileKotlinOhosArm64 :cloudy:compileKotlinOhosX64 # klib
./gradlew :cloudy:desktopTest # 19 项交叉验证
./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.cloudy -m entry
hdc shell hilog | grep Cloudy
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让
:cloudy的纯引擎真正编译成ohosArm64/ohosX64动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- 2.2.21 工具链线不支持 Gradle 9.x——上游 wrapper 9.6.1 必须降到 8.14.1,与 KStore 的 2.4.20 线(留 9.x)经验相反,选线前先确认依赖矩阵;
- Windows 下无系统 java 时,
org.gradle.java.home必须显式指向 JDK 21 根目录(注意解压目录本身可能就是 JDK 根,不要再拼子目录); - import 级扫描会漏判同包引用:
MirageProgramCache.kt不 importMirageBackendProgram(同包 internal 的 expect 类)却强依赖它,首轮分类漏停靠导致编译失败——依赖度盘点必须做符号级复查;同理STD_*常量定义在停靠文件里被纯编译器引用,要下沉(Code128Type 先例); - kotest 没有 ohos 制品:测试统一 JVM 跑 + 根构建全局禁用
Ohos*Test*任务,上游测试文件零改动; - 上游
desktopTest的 skia 依赖来自compose.desktop.currentOs(需 compose 插件):插件移除后直连org.jetbrains.compose.desktop:desktop-jvm-windows-x64制品即可恢复,不必重新引入插件; @CName在 2.2.21 工具链不在kotlinx.cinterop包,删掉 import 靠默认导入解析(qrose 同款);base64 直接用kotlin.io.encoding标准库;- 桥代码的 Kotlin/Native 编译器对 Float/Double 混算(
12.0 * Float)、.toDouble漏括号一类 JVM 上"能看懂"的写法直接报错,写完即编译验证,不要攒; - DevEco 打开工程后生成的
.idea/(内含 clangd 的 wecode-cpp.db 索引,可达十余 MB)会被git add -A扫进提交——.gitignore要预先覆盖example/harmonyApp/.idea/、.clangd、.clang-tidy,误提交后用追加提交移除; - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free();签名材料由 DevEco 自动写入build-profile.json5,提交前用git update-index --skip-worktree保护,证书绝不进入 AtomGit。
8.2 已知问题
Modifier.cloudy()/sky()/liquidGlass()/mirage()等 Compose API 在鸿蒙侧暂不可用(停车composeMain),接回条件是社区 Compose-for-OHOS 制品线与上游版本对齐;- ohos 预设目录不含 RainyWindow(其参数依赖
uniform shader纹理子槽,texture()成员属停车边界)与 Specular(范围控制;三个编译类别已全覆盖); - Duotone 的颜色 uniform 用
float4而非layout(color) vec4——仅 Android 绑定期的色彩空间转换差异,源码文本相同; - Demo 的 blur 走 CPU 光栅化(3-pass box 近似高斯),不是 GPU RuntimeShader 路径;Mirage 输出的是编译后的着色器源码,设备上尚无 GPU 执行宿主接它;
- 镜像参数类(
PresetChromaticParams等)与上游类名不同(避免与停靠代码重复声明),视觉常量逐字镜像; - android / js / wasmJs / apple 目标剥离(源文件留在树里),发布、spotless、dokka、ABI 校验一并剥离,本 fork 不发 Maven Central。
8.3 未来优化方向
- 社区 Compose-ohos 制品线对齐后执行接回(
git revert三处拆分编辑 +git mv停车文件回原源集 + 恢复 targets 与插件,步骤已写入 README.OpenHarmony_CN.md 第 20 节); - 补 Specular 预设与 RainyWindow(需先在纯参数层定义纹理子槽的等价声明);
- 评估 ArkUS 侧 GPU 宿主(RenderNode/自定义绘制)执行 Mirage 编译产物的可行性;
- 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。
九、总结
9.1 核心难点回顾
上游 cloudy commonMain(49 文件,32 个 import Compose)
→ 依赖度盘点界定纯引擎边界(eDSL + 编译器 + 内核库)
→ 工具链对齐 Kotlin 2.2.21-1.0.0(Gradle 降 8.14.1)
→ 根构建替换(cmp_qrose 模式)
→ 31 文件停靠 composeMain + 三处外科手术 + MiragePresets 公共入口
→ ohosArm64 / ohosX64 KLIB(引擎真编译)
→ C ABI(@CName × 2)+ 4 op JSON + CPU 3-pass box 光栅化
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析 + PixelMap 往返
→ ArkUI 真机三 Tab 验收(模糊对比 / Mirage 编译输出 / 版本握手)
9.2 封装层次
- 引擎层:上游原版 Mirage eDSL、MirageCompiler 三方言降级、内核库、uniform schema 机制;
- 入口层:
MiragePresets纯 Kotlin 预设目录(内核体逐字镜像,参数层类型等价替换); - 光栅层:CPU 3-pass box 高斯近似与 progressive 渐显,复述 cloudy 核心语义;
- Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、PixelMap 往返、操作分发与错误文本;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 纯 Compose 库的内核界定看"引擎与模型":UI 库不是没有可编译资产——渲染语言、编译器、算法内核、视觉常量都是。qrose 抽编码器、day-and-night 抽视觉模型、Cloudy 抽着色器引擎,方法论同源:把"皮"和"引擎"切开,皮停车、引擎真编译;
- 工具链线跟着依赖矩阵走:KStore 被 coroutines 元数据顶上 2.4.20 线(Gradle 留 9.x),Cloudy 零依赖取 2.2.21 老线(Gradle 降 8.14.1)——两条线的经验不能互相套用,选线依据是当天制品可达性;
- 停车 + 外科手术 + 公共入口三件套让接回成本趋近于零:字节不动的
git mv保历史,三个边界文件的最小编辑可git revert,新公共入口与 Compose 宿主共存不冲突——制品线对齐的那天,接回是一次代码 review 而不是一次重构。
9.4 适配成果
当前适配已完成:
- Cloudy
1.0.0-alpha01纯引擎(Mirage eDSL + 三方言编译器 + 内核库)的双 ABI Kotlin/Native 编译(18 个上游文件原样编译,3 个边界文件外科手术,2 个新增文件); - 31 个 Compose 文件 + 23 个 Compose 测试字节不动停车
composeMain/composeTest,接回路线文档化; - 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号,双 ABI); - CPU 高斯/渐进模糊光栅化与 4 op JSON 桥协议(presets / schema / shader / blur);
- DevEco Stage ArkUI 三 Tab 验收页面(模糊对比调参、Mirage 编译输出、版本握手);
- 19 项 JVM 交叉验证全绿(上游原测试文件,含 skia RuntimeEffect 真实执行 SKSL)+ HAP 构建(双 ABI 六件套 so)、签名、设备安装和真机验证;
- AtomGit 项目文档、OpenHarmony README(中英双份,主文档 24 节)和真机效果图。
适配过程 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)