本文记录 compose-theme-reveal 接入 OpenHarmony 的完整过程,覆盖纯 Compose UI 库的盘点、社区 Compose ohos 制品线验证、无头门面设计、真实引擎的 ohosArm64 / ohosX64 KLIB 编译、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、internal TileBoard + 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 / shakesway 漂移后的前沿圆心;世界震颤的整像素偏移(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 MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21(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,上游所用线)
Gradle9.6.1(沿用上游 wrapper;2.4.20 线支持 Gradle 9.x)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.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-reveal23Compose 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 / ohosX64 klib(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 方法覆盖内核
ThemeRevealDispatchthemeRevealDispatch(req)styles 表 + evaluate 逐帧几何(预设 / 包络 / TileBoard / 缓动 / 前沿多边形)
ThemeRevealVersionthemeRevealVersion()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.tomlKotlin 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.kt2 个 C ABI、MiniJson、定点数字格式化、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 三 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(...)一帧揭示几何的纯函数求值
NativeThemeRevealDispatch / ThemeRevealVersionC ABI JSON 导出
N-APIthemeRevealDispatch(req) / themeRevealVersion()向 ArkTS 暴露能力
ArkTSevaluate(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 项用例全部通过(上游原测试文件,零改动):

测试类用例数钉住什么
TileBoardTest9牌局几何:列/行计算、整像素边缘、洗牌/波扫/源扩散的 places 分布、cardTurn 交错与钳制
EnvelopeTest3包络:rampIn 上升、平台、rampOut 衰减、边界与参数校验
FrontShapeTest2前沿形状: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 功能用例

  1. 启动后关于 Tab 出现 themeRevealVersion() → 0.1.0-alpha01-ohos.1 与 {"op":"version"} 的 Kotlin / Compose 制品线版本(来自真实 Native 调用,非静态文字);
  2. 效果 Tab 默认 Paper 浅色世界,底部 4×3 网格列出 12 个 Arrival 按钮(与上游 sample 命名一致);
  3. 点 Iris:光圈从按钮中心扩散,前沿细描边 + 紧辉光,结束提交为 Midnight 深色世界(深浅交替);
  4. 点 Cut / Slash / Curtain:直缝/斜缝/竖幕从按钮中心向两侧分开,缝沿有 accent 辉光线;
  5. 点 Bloom:无前沿线的宽软光晕、花瓣状 wobble 双向旋转、前沿整体左右漂移;
  6. 点 Chaos:尖化频带撕裂的锯齿前沿、宽辉光、世界震颤,包络几乎立即起效;
  7. 点 Paper burn:亮 ember 前沿线 + 深色焦边带 + 暖辉光,页面安静燃烧无震颤;
  8. 点 Aperture:矩形门从 origin 等比张开,四角保持方正,全帧呼吸脉冲;
  9. 点 Flipboard / Cascade / Shutters:整版卡片按洗牌/对角波/百叶次序逐张 3D 翻转,翻动卡有 accent 描边与变暗,邻居交替方向(Flipboard);
  10. 揭示进行中点宿主空白处:揭示以 150ms 快速冲向全覆盖(加速不改终点);
  11. 几何 Tab:滑杆手动推进 fraction,预览区遮罩/卡片逐帧变化,旁边字段(progress/intensity/origin/shake/edge/points 数)随动——全部来自 evaluate 返回;
  12. 未知样式名 / 非法请求 → 状态行显示 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 踩坑复盘

  1. 只改 ArkTS 页面不算 CMP 适配,必须让 :theme-reveal 真正编译成 ohosArm64 / ohosX64 klib 并链进动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. gradlew 启动器认 JAVA_HOME 环境变量,gradle.properties 的 org.gradle.java.home 要等 JVM 起来才生效——首编 JAVA_HOME is not set 就是这个;
  4. settings 里 include :example:nativeApp 时 example/ 目录必须已存在,否则 Gradle 9 报 Configuring project with invalid directory——先建目录再启动构建;
  5. Kotlin/Native 侧没有 java.lang.Math:Math.toDegrees 要换成 angle * (180f / PI.toFloat());
  6. androidx.compose.ui.graphics.isSpecified 是扩展属性,光 import Color 不够,要显式 import androidx.compose.ui.graphics.isSpecified;
  7. 桥模块逐公式镜像上游几何时,kotlin.math.min 这类单函数 import 漏一个就报"None of the following candidates is applicable",且候选提示会误导到 Sequence 重载——按报错行号核对 import 列表;
  8. ArkTS 5.1.0(18) 下形状描边属性名是 strokeLineJoin(LineJoinStyle.Round) / strokeLineCap(LineCapStyle.Round),不是 strokeJoin(StrokeJoin…) / LineCap;命中测试枚举是 HitTestMode.None,不是 HitTestBehavior——16 个编译错误全部来自这三个命名;
  9. Circle().fill(...) 在 5.1.0(18) 兼容线上报"SDK 26 才支持",改普通容器圆点(borderRadius + backgroundColor)规避;Path({commands}) 的 .clip(...) 只有弃用告警(功能正常),替代 API 在兼容线上的签名未核实前不动;
  10. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  12. 签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持空 signingConfigs,证书绝不进入 AtomGit;so 三件套与 hvigor 产物全部 gitignore。

8.2 已知问题

  • OHOS 社区 Compose 线尚无组合宿主:ThemeRevealHost / rememberRevealController Composable 编进了 .so 但无法挂进 ArkTS 树,视觉由 ArkUI 按引擎几何复刻;
  • 卡片透视翻转用 ArkUI rotate 3D 近似,不是上游 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 三条经验

  1. 先确认库的"可无头化边界"再定路线:qrose 有纯 Kotlin 编码器可提取,KStore 有纯 Kotlin 引擎可整体复用,compose-theme-reveal 什么都没有——所有能力长在 Compose API 上。此时只要社区制品线齐备,「真编译 Compose 整库 + 无头门面」就是唯一不重写代码的路;路线选择必须基于库的分层事实,而不是套上一个库的成功模板;
  2. 时间轴与几何分权是纯 UI 库过桥的关键:把帧时钟、输入法这些"组合环境概念"留给 ArkTS 镜像,把缓动、包络、排程这些"引擎数学"留在 Kotlin 求值,中间用"原始 fraction + 原始秒"双时钟契约缝合——既保住了"引擎真在跑"的可验收性,又不需要把 withFrameNanos 拖过 C ABI;
  3. 交叉验证是"引擎没漂"的唯一可信证明:14 项 jvmTest 跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;UI 库的 Demo 验收重点天然落在"画面每一帧都来自真实 Native 求值"这条停帧冻结检验上。

9.4 适配成果

当前适配已完成:

  • compose-theme-reveal 0.1.0-alpha01-ohos.1 真实引擎的双 ABI Kotlin/Native 编译(上游 commonMain 23 个文件零重写,唯一新增库源文件是无头门面);
  • 社区 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。

参考文档

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐