本文记录 FlexiUI 接入 OpenHarmony 的完整过程,覆盖 UI 组件库盘点、Compose 制品线可达性验证、设计语言内核提取、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

FlexiUI 是 BetterAndroid(HighCapable)的 Compose Multiplatform UI 组件库:不依赖任何 Material 组件,用自己的设计语言提供 Scaffold、AppBar、Button、CheckBox、RadioButton、Switch、Slider、ProgressIndicator、Tab、NavigationBar、ItemBox、AreaBox、Surface、TextField、Dropdown、FlexiDialog 等一整套组件,上层由 FlexiTheme(colors, shapes, typography, sizes) 统一注入主题。本次适配与 KStore 的"真编译整库"路线(kotlinx ohos 制品线补齐后引擎原样编译)正好相反:flexiui-core 的组件树逐文件依赖 androidx.compose.* 与 BetterAndroid 私有扩展制品(compose-extension / ui-extension / system-extension),两者在社区 Maven 上都没有 ohos 变体,社区鸿蒙侧 Compose 也停在 1.9.2——组件树编不了,但设计语言编得了。20 套色彩方案 × 9 个语义槽位、形状/尺寸/字阶令牌、20 个组件的默认规格(颜色映射、度量、动效时长、贝塞尔,逐项带上游 文件:行号 出处)、8 个矢量图标与 Switch/Slider 的行为数学被提取成纯 Kotlin 内核,编译成 OpenHarmony 动态库,ArkUI 页面用这些令牌重新拼装组件做验收宿主。

项目地址: AtomGit/oh-tpc/FlexiUI.git

上游仓库: BetterAndroid/FlexiUI

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配

FlexiUI 为 Compose Multiplatform 应用提供"设计语言 + 组件"这一层的完整答案:FlexiTheme 四组令牌(Colors 9 槽位语义色、Shapes 三档形状、Sizes 12 项度量、Typography 五档字阶)经 CompositionLocal 注入,组件侧 SwitchDefaults.style()、SliderDefaults.colors() 这类 Defaults 对象把语义色解析成组件色、把度量落成 Dp 值与动效参数。上游在 Android、桌面、iOS 三端共享同一套 commonMain。OpenHarmony 应用同样需要这套设计语言,但上游构建(Kotlin 2.2.21 / Gradle 9.2.0 / Gropify 1.0.1 / AGP 8.13.2)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以画出组件,却无法证明 FlexiUI 的设计令牌在 OpenHarmony ARM64 设备上完成编译、链接和下发。适配需要同时验证令牌内核、行为数学、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
组件树不可编flexiui-core 全部 26 个 commonMain 文件 import androidx.compose.*,社区鸿蒙侧 Compose 是 1.9.2,接不住上游 1.9.3 工具链链路。
依赖矩阵api(compose.foundation) 之外还 api 了 BetterAndroid 私有制品(compose-extension / compose-multiplatform),社区 Maven 上没有 ohos 变体,也没有源码可换线。
构建插件冲突上游根构建走 Gropify + AGP,与鸿蒙 Kotlin 插件线无法共存于一个 Gradle 构建。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
交互语义跨不了桥pointerInput 拖拽、animateColorAsState 过渡是 Compose 运行时概念,C ABI 只能同步一来一回。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:设计语言内核(令牌 + 组件规格 + 行为数学)的提取与 ohos 编译、独立 example 构建隔离(miuix 模式)和 C ABI/N-API 桥接层。组件树完全由上游原版实现负责(留在树里等工具链对齐),ArkTS 只负责调用 Native、解析 JSON 和按令牌渲染。

1.2 库提供的能力

Demo 提供一个真实的设计语言会话,对应桥接的全部入口:

  • 主题令牌(theme):flexiTheme({scheme, dark, black}) 一次性下发当前方案的全量令牌——9 个语义槽位的 #AARRGGBB、3 项形状度量、12 项尺寸、5 档字阶,页面整套换肤不复制任何常量;
  • 组件规格(components):flexiComponents({component, ...}) 下发 20 个组件的 colors(已按方案解析)+ style(上游字段名原样的度量)+ animation(时长与贝塞尔),每项带上游 文件:行号;
  • 行为数学(behavior):flexiBehavior(...) 让 ArkTS 的手势驱动与 Compose 实现同一套公式——Switch 的松手判定(offsetX >= halfWidth)、轨道色 lerp、Slider 的取值换算与步进吸附;
  • 矢量图标(icon):flexiIcon(...) 下发 8 个图标的 SVG 命令串与描边参数,ArkUI Path 组件直接回放;
  • 版本握手(version):flexiVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
colors / style / animation令牌与规格,颜色统一 #AARRGGBB 或 "transparent"。
checked / settledOffsetX / offsetX / value行为数学的计算结果。
source组件规格的上游 文件:行号 出处,供审计。

上游完整能力(CompositionLocal 注入、组件树、涟漪、Material ripple 包装)全部留在 flexiui-core 原文件里,JVM 侧保留 23 项 jvmTest 钉值用例(上游没有 commonTest,断言为手工对照源码逐值抄录),证明 ohos .so 里下发的令牌没有漂移。

1.3 实现适配

维度要求
代码复用令牌值与行为公式与上游逐字一致(1:1 抄录 + 文件:行号);上游源文件零改动、零重写,flexiui-core / flexiui-resources / samples / docs 原样留盘。
平台目标example/nativeApp 声明 jvm + ohosArm64 + ohosX64,jvm 跑钉值测试,双 ohos 出动态库。
构建隔离鸿蒙构建收敛在 example/ 独立 Gradle 工程(miuix 模式),上游 Gropify 根构建不动——两条插件线不能共存。
桥接稳定5 个窄 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律内核零第三方依赖(纯 Kotlin 标准库),不存在制品线对齐问题;工具链钉社区既定线 Kotlin 2.2.21-1.0.0 + Gradle 8.14.1。
UI 完整四 Tab 覆盖全部桥接入口:主题/字阶/组件/图标,可交互组件的行为决策走桥。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。FlexiUI 的组件树没有也不会在当前工具链下跑在 ArkUI 上;ArkTS Demo 是"令牌拼装的近似还原",精确同源的只有过桥数据与行为数学——这条边界写进了仓库 README 的已知限制。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、验证 Compose/BetterAndroid 制品可达性、确定交付物
第 2 阶段:目标与依赖打通 ── example/ 独立构建、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、上游零改动合并
第 3 阶段:桥协议建模     ── 令牌 JSON 契约、颜色表示(ARGB Long + #AARRGGBB)、错误通道
第 4 阶段:原生桥接       ── @CName C ABI × 5、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 四 Tab 页面、令牌驱动渲染、手势接行为数学
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
flexiui-core/                         上游 Compose 组件库(26 个 commonMain 文件,留盘对照,不进 ohos 构建)
flexiui-resources/                    上游 ImageVector 图标(FlexiIcons + 8 个矢量文件,留盘对照)
samples/                              上游示例(androidApp / desktopApp / composeApp / iosApp),留盘
docs/                                 上游使用文档(guide.md 等)
example/settings.gradle.kts           独立 ohos 构建(eazytec Maven 置前,仅 include :nativeApp)
example/nativeApp/                    令牌引擎 + Kotlin/Native 动态库(commonMain 引擎 / ohosMain 桥 / jvmTest 钉值)
example/harmonyApp/                   DevEco Stage、CMake N-API 和 ArkTS 四 Tab 页面
README.OpenHarmony.md / _CN.md        鸿蒙适配说明(中英双份)
README.md / README.upstream.md        首页重写为 OHOS 适配版 / 上游原文保存

构建拓扑走 miuix 模式(example 独立构建):上游根构建(Gropify / AGP / version catalog)原样保留但与鸿蒙无关,example/settings.gradle.kts 里 rootProject.name = "ohos_flexiui"、只 include :nativeApp。与 KStore 的 cmp_qrose 根构建替换模式相比,差异源于仓库形态:FlexiUI 的 AtomGit main 分支原本只有文档(README/LICENSE/截图),完整源码在 compose 分支——两者是无共同祖先的历史,本次以 merge 收编(README/LICENSE/img-src 冲突保留 main 侧),鸿蒙构建再单独落在 example/,互不污染。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(org.gradle.java.home 指向 Homebrew openjdk@21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作钉值测试)
Gradle8.14.1(example 独立 wrapper;2.2.21 线的既定配套)
DevEco product5.1.0(18) 兼容(DevEco Studio 26 / API 26 SDK 编译)
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.flexiui
export JAVA_HOME="/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home"
java -version    # openjdk 21.0.12.1

注意本机默认 java 是 JBR 25——Gradle 8.14.1 起不动(Unsupported class file major version),必须显式指回 21。

1.3 源码依赖度盘点
模块commonMain 文件依赖形态处置
flexiui-core26全部 import androidx.compose.*;api compose.foundation + BetterAndroid compose-extension令牌提取(主题 6 文件 + 组件 21 文件的 Defaults 与行为数学抄录进引擎)
flexiui-resources9ImageVector path DSL(compose.ui.graphics)path 提取(转 SVG 命令串,描边参数随行)
samples/4 个平台示例AGP / 桌面 / iOS留盘不进构建

盘点定下提取面的三类内容:数据(ColorScheme.kt 的 21 组 Colors 常量、Shapes/Sizes/Typography 默认值、各组件 Defaults 的映射与度量)——值一字不改;行为数学(Switch.kt 的 maxOffsetX/松手判定/trackColor lerp、Slider.kt 的取值换算/步进锚点)——公式原样;组件树(@Composable 函数体)——不提取,留在上游文件里。上游没有 commonTest,防漂移手段只能是手写钉值断言(见 6.2)。

1.4 页面边界
标题区          FlexiUI OHOS / 状态行(桥版本 · Kotlin 版本 / 已加载 9 色 · 5 档字阶 · 12 项尺寸)
Tab 切换        主题 / 字阶 / 组件 / 图标
主题 Tab        8 色系 chip + 深色/纯黑开关、9 槽位色板(名称 + #AARRGGBB)、形状度量(15/10/circle 实渲)、12 项尺寸令牌
字阶 Tab        五档按真实字号/字重/行高渲染(titlePrimary 25sp Bold … secondary 12sp)
组件 Tab        Button/IconButton、可拖拽 Switch、CheckBox(对勾复用 checkMark 路径)、RadioButton、
                可拖拽步进吸附 Slider、确定态圆/线 ProgressIndicator、ItemBox、Tab 指示器、
                NavigationBar、FlexiDialog(0.8→1.1→1 缩放 + scrim)
图标 Tab        8 个图标的 path 数据回放(fill/stroke/cap/join 参数随行)

页面启动时先调 flexiVersion() 做握手,再 flexiTheme 拉默认方案令牌整体着色。

第 2 阶段:目标与依赖打通

2.1 制品线验证(本适配的成败手)

与 KStore 先查 kotlinx ohos 制品同构,本适配先验证两件事在社区 Maven(https://maven.eazytec-cloud.com)上的可达性:

androidx.compose.* / org.jetbrains.compose.*(社区鸿蒙 CMP 线)
  → 存在,但版本停在 1.9.2,且只覆盖 compose 自身
com.highcapable.betterandroid.*(compose-extension / ui-extension / system-extension / compose-multiplatform)
  → 不存在 ohos 变体(上游只发 android/jvm/ios,私有 Maven)

两个结论直接定死路线:其一,上游 1.9.3 的组件树降级到社区 1.9.2 编不过(miuix、compose-fluent-ui 已验证过同代降级路线不可行);其二,就算 Compose 对齐了,BetterAndroid 私有制品这关也过不去——flexiui-core 的 commonMain 里 ComponentPadding、borderOrElse 这类扩展用得满篇都是。真编译整库出局,设计语言内核提取成为唯一可验收路径。反过来,内核一旦定义为纯数据 + 纯函数,它对第三方依赖的需求归零,工具链选择就完全自由了。

2.2 工具链钉版(为什么是 2.2.21-1.0.0 + Gradle 8.14.1)

内核零第三方依赖,不存在 KStore 那种"coroutines 制品线反推 stdlib 2.4.20"的压力,直接沿用社区已批准的 HarmonyOS Kotlin 2.2.21-1.0.0 钉法(qrose / haze / miuix 同款),Gradle 8.14.1 是这条线的既定配套。上游源码本来就是 Kotlin 2.2.21 编的,引擎抄录件与之完全同代。

example/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_flexiui"

include(":nativeApp")
2.3 编译内核

example/nativeApp/build.gradle.kts 一个模块承担三件事:jvm 钉值、双 ohos 动态库、so 回填:

kotlin {
    applyDefaultHierarchyTemplate()

    compilerOptions {
        optIn.add("kotlin.experimental.ExperimentalNativeApi")
    }

    jvmToolchain(21)
    jvm()                                   // 钉值测试用

    ohosArm64 { binaries.sharedLib { baseName = "ohosflexiui" } }
    ohosX64 { binaries.sharedLib { baseName = "ohosflexiui" } }
}

fun cxxShared(triple: String): File {       // 从 ~/.konan 定位匹配 triple 的 libc++_shared.so
    ...
}

tasks.register<Copy>("copyKnSoArm64") {     // libohosflexiui.so + libc++_shared.so
    ...                                     // → harmonyApp/entry/libs/arm64-v8a/
    finalizedBy 挂在 linkDebugSharedOhosArm64 上
}

实测:

cd example
./gradlew :nativeApp:jvmTest                # 23 项钉值全绿
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
# BUILD SUCCESSFUL:arm64 ≈8.9MB / x86_64 ≈8.0MB(debug 未裁剪,含 KN 运行时)

上游 flexiui-core 一个文件都没进这个构建——引擎是抄录件不是引用件,这是"提取路线"与"真编译路线"在工程上的分界。

2.4 上游零改动与合并策略

适配内容全部是新增文件(example/、README 四件套、.gitignore 追加),git status 对上游树永远干净。上游 compose 分支更新时的同步顺序:git merge origin/compose → 逐文件重对令牌 → 更新 jvmTest 断言 → 全绿 → 才允许动 Demo。合并提交与适配提交在历史上分开,审计时一眼能分清"上游来了什么"和"鸿蒙侧改了什么"。

第 3 阶段:桥协议建模

3.1 JSON 契约
flexiui-core 源码(上游,留盘对照)
        │ 令牌/规格/公式提取(1:1 + 文件:行号)
FlexiEngine + FlexiColorSchemes + FlexiComponentSpecs + FlexiIcons + FlexiBehavior
        ↓ 扁平 JSON 进出(手写 MiniJson)
Bridge.kt(@CName × 5)
        ↓ UTF-8 JSON
N-API flexiTheme / flexiComponents / flexiBehavior / flexiIcon / flexiVersion
        ↓
ArkUI Index.ets(四 Tab)

请求与响应:

请求 : {"scheme":"red","dark":false,"black":false}                         → flexiTheme
       {"component":"switch","scheme":...,"inAreaBox":false,"nested":false} → flexiComponents
       {"component":"switch","op":"target","offsetX":15}                    → flexiBehavior
       {"name":"*"} 或 {"name":"checkMark"}                                 → flexiIcon
响应 : {"ok":true,"colors":{...9 槽位},"shapes":{...},"sizes":{...},"typography":[...5 档]}
       {"ok":true,"component":"switch","colors":{...},"style":{...},"animation":{...},"source":"Switch.kt:281-335"}
       {"ok":false,"error":"unknown scheme: ..."}

两个不对称设计:typography 与 icons 是数组(字阶按档序渲染、图标按名遍历),其余全部扁平键值;flexiVersion() 无参数,握手专用。所有响应以 ok 开头,错误永远走 ok:false。

3.2 令牌引擎建模

颜色表示是引擎的地基:上游 Color(0xA6777777) 打包位是 ARGB,引擎用 Long 原样承载、以 #AARRGGBB 下发——与 Compose Color 的位级一致让"抄录"可以是机械动作。唯一两处 alpha 推导(DropdownMenu.activeColor = ThemePrimary@0.3、Dialog scrim = Black@0.35)按 Compose toArgb 的 (a*255+0.5) 量化复现(0.3→0x4D、0.35→0x59);Switch 轨道色 lerp 按通道线性插值再量化,jvmTest 钉住 0.5 处的 0x93777777。

组件规格按上游 Defaults 的三分结构建模:colors(语义色 → 组件色的映射结果)、style(Dp/Float 度量,字段名与上游 data class 一致)、animation(时长 + 贝塞尔控制点,如 ProgressIndicator 线性四段 750/850/567/533ms 与四条 CubicBezierEasing)。两个请求布尔还原上游的 CompositionLocal 语义:inAreaBox 对应 LocalInAreaBox(Button 在 AreaBox 内换 contentColor/backgroundColor/childShape),nested 对应嵌套 AreaBox(前景色降为 ForegroundSecondary)。

行为数学是无状态纯函数:Switch 的 maxOffsetX = trackWidth − thumbDiameter − padding.horizontal(40−13−7=20vp)、松手 offsetX >= halfWidth 判定、fraction = (offsetX/maxOffsetX).coerceIn(0,1) 的轨道色;Slider 的 maxOffsetX = trackWidth − thumbDiameter、value ↔ offset 线性换算、steps+2 个锚点的最近吸附。单位无关(dp 进 dp 出),ArkTS 用 vp 一比一驱动。

3.3 错误通道

每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:

@CName("FlexiTheme")
fun flexiThemeExport(request: String): String = try {
    FlexiEngine.theme(request)
} catch (t: Throwable) {
    FlexiJson.obj("ok" to false, "error" to (t.message ?: t.toString()))
}

非法 JSON、未知 scheme/component/op/icon、缺字段,全部走同一个 ok:false 通道,ArkTS 侧统一渲染进状态行。

第 4 阶段:原生桥接(技术难点)

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libflexiui_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosflexiui.so)
  │ FlexiTheme / FlexiComponents / FlexiBehavior / FlexiIcon / FlexiVersion
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制设计语言,所以选择 C ABI + JSON。符号按能力域切 5 个窄口子而不是一个大分发函数——ArkTS 侧 import { flexiTheme } from 'libflexiui_napi.so' 的调用点即文档。

4.2 Kotlin/Native 导出函数
@CName("FlexiTheme")       fun flexiThemeExport(request: String): String
@CName("FlexiComponents")  fun flexiComponentsExport(request: String): String
@CName("FlexiBehavior")    fun flexiBehaviorExport(request: String): String
@CName("FlexiIcon")        fun flexiIconExport(request: String): String
@CName("FlexiVersion")     fun flexiVersionExport(): String
C ABI 符号ArkTS 方法覆盖内核
FlexiThemeflexiTheme(req)20 套方案的 9 槽位色 + 形状 + 尺寸 + 字阶
FlexiComponentsflexiComponents(req)20 个组件的 Defaults(colors/style/animation + 文件:行号)
FlexiBehaviorflexiBehavior(req)Switch layout/target/trackColor/dragThreshold、Slider layout/valueToOffset/offsetToValue/snap
FlexiIconflexiIcon(req)8 个图标的 SVG pathData + 描边参数
FlexiVersionflexiVersion()fork 版本握手(真机版本行的数据来源)
4.3 C++ N-API 方法分发

五个导出共用一个 WrapStringCall 模板:napi_typeof 校验必须是 string、调 KN 函数、napi_create_string_utf8 拷出后立即经 libohosflexiui_symbols()->DisposeString 归还 Kotlin 运行时(绝不 free()):

napi_property_descriptor desc[] = {
    {"flexiTheme", nullptr, NapiFlexiTheme, ...},
    {"flexiComponents", nullptr, NapiFlexiComponents, ...},
    {"flexiBehavior", nullptr, NapiFlexiBehavior, ...},
    {"flexiIcon", nullptr, NapiFlexiIcon, ...},
    {"flexiVersion", nullptr, NapiFlexiVersion, ...},
};

libohosflexiui_api.h 按社区已审仓库同款手写精简版(只声明 ExportedSymbols 前缀与 5 个业务符号)——KN 生成的全量头有 42KB kref 类型,而 DisposeStablePointer/DisposeString 恰为真实结构体的前两个成员,前缀视图布局兼容,不拖整份生成头。

4.4 CMake 和类型声明
add_library(ohosflexiui SHARED IMPORTED)
set_target_properties(ohosflexiui PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(flexiui_napi SHARED napi_init.cpp)
target_link_libraries(flexiui_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosflexiui)

OHOS_ARCH 为空或 libohosflexiui.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。

export const flexiTheme: (request: string) => string;
export const flexiComponents: (request: string) => string;
export const flexiBehavior: (request: string) => string;
export const flexiIcon: (request: string) => string;
export const flexiVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 令牌加载和请求纪律
aboutToAppear(): void {
  this.loadTheme();
  this.loadIcons();
  const rec = JSON.parse(flexiVersion()) as Record<string, Object>;
  this.statusLine = `桥版本 ${getStr(rec, 'version', '?')} · Kotlin ${getStr(rec, 'kotlin', '?')}`;
}

private loadTheme(): void {
  const req = themeReq(this.scheme, this.dark, this.black);   // typed Record 构建
  const root = JSON.parse(flexiTheme(req)) as Record<string, Object>;
  // 9 槽位 → @State 色值 + ColorSlot[];shapes/sizes/typography 同步
}

ArkTS 严格模式两条铁律:请求对象不能写 {a:1} 字面量(arkts-no-untyped-obj-literals),必须 const rec: Record<string, Object> = {} 逐键赋值——仓库为此提供了 themeReq/specReq/behaviorReq 辅助函数;flexiComponents 等来自 .so 的函数若类型包未安装会静默变 any,cd entry && ohpm install 是前置条件。

5.2 页面交互

主题 Tab 自上而下:8 色系 chip(default/red/pink/purple/orange/yellow/green/blue)+ 深色/纯黑开关、9 槽位色板(64×40 圆角块 + 名称 + #AARRGGBB monospace 值)、形状度量(15dp/10dp/正圆三块实渲)、12 项尺寸令牌芯片。字阶 Tab 五档按真实字号渲染并标注 name · sp · em · Bold。组件 Tab 是令牌拼装画廊:Switch 用 PanGesture 拖拽——onUpdate 里 x = max(0, min(maxOffset, start + offsetX × 1.1))(1.1 是上游 thumbGain 对拖拽增量的放大)、轨道色逐帧走 flexiBehavior 的 trackColor、松手调 target op 拿 checked 再 animateTo 吸附;Slider 同构,切"步进 0/2/4"后松手走 snap op 吸附到锚点;CheckBox 勾选态里嵌 13vp 的 checkMark 路径(48 viewport 缩放 13/48);Dialog 按 0.8→1.1→1 三段缩放(LinearOutSlowIn 出场、FastOutSlowIn 收尾,控制点与上游 Compose 常量同值)。图标 Tab 用 Path().commands(pathData) 回放,fill/stroke/cap/join 逐 path 还原。

5.3 对比度和错误边界

色板块带 1vp textSecondary 描边保证浅色方案的白色前景在白底卡片上仍可辨;ok:false 时错误原文进状态行;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进状态行,页面永不白屏。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── settings.gradle.kts / gradle.properties / gradle/
│      独立 ohos 构建(eazytec 置前、Kotlin 2.2.21-1.0.0、Gradle 8.14.1、version=0.1.0-alpha01-ohos.1)
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosflexiui)+ cxxShared + copyKnSo*
│   └── src/
│       ├── commonMain/kotlin/flexiui/ohos/   MiniJson / FlexiColors / FlexiTokens /
│       │                                     FlexiComponentSpecs / FlexiIcons / FlexiBehavior / FlexiEngine / FlexiJson
│       ├── ohosMain/kotlin/flexiui/ohos/Bridge.kt      @CName × 5
│       └── jvmTest/kotlin/flexiui/ohos/FlexiTokensTest.kt   23 项钉值
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.flexiui、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/libohosflexiui_api.h
        ├── src/main/cpp/types/libflexiui_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 四 Tab 验收页面
6.2 构建、签名和安装
cd example
./gradlew :nativeApp:jvmTest                                            # 23 项钉值
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64

cd harmonyApp
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
  assembleHap -p product=default -p buildMode=debug --no-daemon

注意必须用 DevEco 自带 hvigor:~/ohos/command-line-tools 里的 hvigorw 对本工程(compatibleSdkVersion 5.1.0(18) 对 API 26 SDK)会报 00303168 SDK component missing。签名在 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.flexiui -m entry

四、完整代码对照

4.1 整体架构

flexiui-core / flexiui-resources(上游原样留盘,对照审计)
    │ 令牌 / 规格 / 公式提取(1:1 + 文件:行号)
FlexiEngine(commonMain 纯 Kotlin,零第三方依赖)
    │ FlexiTheme / FlexiComponents / FlexiBehavior / FlexiIcon / FlexiVersion
libflexiui_napi.so
    │ flexiTheme / flexiComponents / flexiBehavior / flexiIcon / flexiVersion
Index.ets
    │ JSON parse + @State 更新 + PanGesture 驱动行为数学
    ▼
ArkUI 真机页面(四 Tab)

4.2 文件清单

文件职责
example/settings.gradle.kts独立 ohos 构建、eazytec Maven 置前、ohos_flexiui、仅 include :nativeApp
example/gradle/libs.versions.tomlKotlin 2.2.21-1.0.0 + multiplatform 插件别名(仅此一条)
example/gradle.properties版本 0.1.0-alpha01-ohos.1、KN 双开关、JDK 21 路径
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../FlexiColors.kt21 组方案常量 + resolve(镜像 ColorScheme.toColors)+ ARGB 工具
nativeApp/.../FlexiComponentSpecs.kt20 个组件规格(colors/style/animation + source 出处)
nativeApp/.../FlexiBehavior.ktSwitch/Slider 行为数学(公式逐行对齐上游)
nativeApp/.../Bridge.kt5 个 C ABI、try/catch 错误通道
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鸿蒙适配主文档(19 节)

4.3 关键 API 对照

层次API作用
上游ColorScheme.toColors(darkMode)方案 + 明暗 → Colors(引擎 resolve 镜像之)
上游SwitchDefaults.style()trackWidth 40 / thumbRadius 6.5 / thumbGain 1.1(引擎按 文件:行号 抄录)
上游Switch 松手逻辑offsetX >= halfWidth 判定(FlexiBehavior.switchTarget 原样)
上游Slider 步进锚点List(steps+2){ it × maxOffset/(steps+1) } 最近吸附(sliderSnap 原样)
引擎FlexiEngine.theme/components/behavior/icon/versionJSON 分发
N-APIflexiTheme(req) 等 5 个向 ArkTS 暴露能力
ArkTSloadTheme() / specColor() / trackColorFor()令牌加载与按需取值

4.4 ArkTS 与 Kotlin 的边界

const req: Record<string, Object> = {};
req['scheme'] = 'blue'; req['dark'] = true; req['black'] = false;
const theme = JSON.parse(flexiTheme(JSON.stringify(req)));
// theme.colors.themePrimary === '#FF8091B1'(blue dark,来自 Native 而非页面常量)

// Switch 拖拽松手:
const res = JSON.parse(flexiBehavior(behaviorReq('switch', 'target', ['offsetX'], [this.swThumbX])));
const checked = res['checked'] as boolean;    // 判定在 Kotlin 侧,与 Compose 同一公式
fun switchTarget(offsetX: Double): Pair<Double, Boolean> {
    val max = switchLayout().getValue("maxOffsetX")   // 40 − 13 − 7 = 20
    val half = switchLayout().getValue("halfWidth")   // 10
    val x = offsetX.coerceIn(0.0, max)
    return if (x >= half) max to true else 0.0 to false
}

两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。

五、关键决策说明

决策 1:设计语言内核提取而不是真编译整库

KStore 走真编译的前提是 kotlinx ohos 制品线补齐;本适配第 2.1 节验证了 Compose 与 BetterAndroid 制品均不可达,真编译出局。UI 库的正确切口是"设计语言 = 数据":色板、度量、时长、贝塞尔是数据,可提取、可钉值;行为数学是纯函数,可过桥;组件树是代码,留给工具链对齐的那天。把切口切在数据上,ArkTS 侧拿到的每一份令牌都有 文件:行号 审计链,这是"适配"与"重写"的分界。

决策 2:example/ 独立构建而不是根构建替换

KStore 用 cmp_qrose 根构建替换,因为引擎模块本身要进 ohos 编译;FlexiUI 的内核是抄录件、不引用上游模块,独立 example/ 工程(miuix 模式)让上游 Gropify/AGP 根构建原样留盘——上游可以随时正常构建 android/desktop/iOS,鸿蒙侧升级也不碰上游一根毫毛。仓库形态也支持这个选择:main(文档)与 compose(源码)本就是无关历史,merge 收编后各自演进。

决策 3:工具链钉 2.2.21-1.0.0 + Gradle 8.14.1

内核零第三方依赖,没有 KStore 那种被 coroutines 制品线反推 stdlib 版本的压力,直接沿用社区已批准的 2.2.21-1.0.0 钉法(qrose / haze / miuix 同款),Gradle 8.14.1 为既定配套。上游源码同为 Kotlin 2.2.21,抄录件零版本摩擦。

决策 4:手写 23 项钉值断言而不是依赖上游测试

上游没有 commonTest(KStore 有 51 项上游原测试可直接用),防漂移只能自建:jvmTest 逐值断言 21 组方案的关键槽位、形状/尺寸/字阶全量、alpha 量化与 lerp 中点、Switch/Slider 数学边界(9.9/10.0 跨 halfWidth、snap 到 73.33)。断言的期望值直接抄自上游源码,“红即抽漏”——上游升级后对着新源码重抄重钉,全绿才算同步完成。

决策 5:5 个窄符号 + 行为数学过桥

符号按能力域切 5 个窄口子(theme/components/behavior/icon/version),JSON 字段演进不动 C 签名。行为数学过桥是本适配相对 miuix(纯令牌)的增量:Switch 松手判定、轨道色 lerp、Slider 步进吸附在 Kotlin 侧执行,ArkTS 手势只是驱动输入——同一公式在 Android 上跑在 pointerInput 里、在鸿蒙上跑在 PanGesture 回调里,判定结果天然一致。

六、测试与验证

6.1 测试环境

本次验证使用 macOS(arm64)、JDK 21(Homebrew openjdk@21)、DevEco Studio 26(API 26 SDK)、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

cd example && ./gradlew :nativeApp:jvmTest

23 项用例全部通过(与 ohos .so 同一份 commonMain 代码):

分组用例数钉住什么
色彩方案4defaultLight 全 9 槽位、dark/black 关键槽、7 色系抽检、resolve 分支矩阵(含 System 回落)
形状/尺寸/字阶3primary 15 / secondary 10 / circle;12 项尺寸全量;五档字号 + 1.5em 行高 + Bold 位
颜色工具2alpha 量化(0.3→0x4D、0.35→0x59);lerp 端点与中点(0x93777777)
行为数学4Switch 布局(maxOffset 20)、判定边界(9.9/10.0)、轨道色 lerp;Slider 换算与 snap
图标18 个图标、viewport/strokeWidth/路径首段
MiniJson3扁平解析(含逗号串)、转义、定点格式化(永不科学计数法)
引擎端到端6theme/components/behavior/icon/version 的 JSON 断言与错误通道

6.3 HAP 验证

:nativeApp:jvmTest: 23 tests passed
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
nm -D: FlexiTheme / FlexiComponents / FlexiBehavior / FlexiIcon / FlexiVersion 均为全局 T 符号
BuildNativeWithCmake: finished(双 ABI 编出 libflexiui_napi.so)
CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

entry-default-unsigned.hap 约 8.6 MB,内含双 ABI 各三件套(libflexiui_napi.so / libohosflexiui.so / libc++_shared.so);HAP 内 strip 后 KN so arm64 约 3.1MB、x86_64 约 2.4MB(debugShared 未 strip 时分别约 8.9MB / 8.0MB,含 Kotlin/Native 运行时);llvm-readelf -d 确认 NAPI so 的 NEEDED 全短名、RUNPATH=$ORIGIN。

6.4 功能用例

  1. 启动后状态行出现 桥版本 0.1.0-alpha01-ohos.1 · Kotlin 2.2.21-1.0.0(来自真实 Native 调用,非静态文字),主题 Tab 显示 已加载 9 色 · 5 档字阶 · 12 项尺寸(浅色 / default);
  2. 依次点 8 个色系 chip:9 个色块与十六进制值整套切换,如 blue 浅色 themePrimary 变 #FF0099DF、red 变 #FFFF5545;
  3. 开深色:backgroundPrimary 变 #FF2D2D2D 暗底、文字变亮;再开纯黑:backgroundPrimary 变 #FF000000;
  4. 字阶 Tab 五档按真实字号渲染,titlePrimary 25sp Bold 与 secondary 12sp 对比明显,行高一致 1.5em;
  5. Switch 点击直接开/关(250ms FastOutSlowIn 动画);拖拽时轨道色随 fraction 从 #27777777 渐到 #FF777777(逐帧走桥),松手按 Native 判定吸附;
  6. Slider 拖拽/点按换算 value;切"步进 2"后松手吸附到 0/73.33/146.67/220 锚点;
  7. Dialog 弹出按 0.8→1.1→1 三段缩放,scrim 为 35% 黑;CheckBox 勾选后白色 checkMark 路径出现在 4dp 圆角方框内;
  8. 图标 Tab 8 个图标全部由 Native pathData 回放,checkMark 为 7 单位圆头描边、arrowForward 为填充箭头。

6.5 验证结论

设计语言内核、行为数学、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的令牌会话:版本握手、20 套方案切换、字阶渲染、可交互组件画廊与图标回放(见下节效果图)。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/FlexiUI.git
cd FlexiUI/example
export JAVA_HOME="/path/to/jdk-21"
./gradlew :nativeApp:jvmTest                                           # 23 项钉值
./gradlew :nativeApp:linkDebugSharedOhosArm64 :nativeApp:linkDebugSharedOhosX64
cd harmonyApp
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
  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.flexiui -m entry
hdc shell hilog | grep FlexiUINapi

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让令牌内核真正编译成 ohosArm64 / ohosX64 动态库并打进 HAP;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 本机默认 JBR 25 起不动 Gradle 8.14.1(Unsupported class file major version),org.gradle.java.home 必须钉回 21;
  4. commonMain 里 String.format / Math.floor 是 JVM-only API,KN 编译直接失败——十六进制补零与定点格式化要手写;
  5. KN 浮点转字符串可能出 1.0E-5 科学计数法,打断 ArkTS JSON.parse——MiniJson 输出必须定点化(×1e6 取整再剥零);
  6. entry 的 NAPI 类型包(file:./src/main/cpp/types/libflexiui_napi)不会被 hvigor 可靠地自动安装,cd entry && ohpm install 要手动跑,否则桥函数在 ArkTS 里静默变 any;
  7. @Builder 参数不能传 lambda——卡片结构换成 @Component + @BuilderParam + 尾随闭包,否则 UI DSL 内部大面积假语法错;
  8. ArkTS 严格模式禁无类型对象字面量:JSON.stringify({...} as Record<string, Object>) 不合规,必须 const rec: Record<string, Object> = {} 逐键赋值;
  9. curves.cubicBezierCurve(...) 返回 ICurve,字段直接声明 Curve 会类型不匹配;
  10. Path 绘图组件的属性是 .fill() / .stroke(),不是 Shape 容器的 fillColor / strokeColor——属性名混用直接编译失败;
  11. ~/ohos/command-line-tools 的 hvigorw 对 5.1.0(18) 兼容工程报 00303168 SDK component missing,命令行打包必须用 DevEco 自带 hvigor + 自带 node;
  12. KN 全量 CAdapter 头有 42KB,cpp/include 只留前缀精简版——DisposeStablePointer / DisposeString 恰为真实 ExportedSymbols 结构体前两成员,布局兼容;
  13. build-profile.json5 的签名材料由 DevEco 自动写入,提交前必须还原 signingConfigs: [],证书绝不进入 AtomGit;
  14. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可。

8.2 已知问题

  • Compose 组件树未接入鸿蒙:Demo 是令牌拼装的近似还原,精确同源的只有过桥数据与行为数学;涟漪(Material ripple 包装)、hover 态、Tab 指示条滑动插值是 ArkTS 近似实现;
  • System 色彩方案在桥上回落 Default(上游是各平台 expect/actual,Android 走动态取色),响应显式携带 systemColorAvailable:false,ArkTS 不假装它是真的系统色;
  • ProgressIndicator 只演示确定态,不确定态的多段 keyframes(1332/6660ms、286°/290° 跳变)参数已过桥但未逐帧复刻;
  • 上游没有 commonTest,钉值断言是手工对照——上游升级需人工重对源码更新常量与断言;
  • 图标每次进 Tab 全量拉取 flexiIcon("*"),未做按需缓存(数据量小,暂可接受);
  • BetterAndroid 私有制品无 ohos 变体是组件树接回的硬前提,需上游配合,非本 fork 可控;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.flexiui 匹配的签名。

8.3 未来优化方向

  • 等 Compose 与 BetterAndroid 制品补齐 ohos 目标后,把组件树接进 ComposeArkUIViewController,令牌引擎降级为对照基准;
  • 组件 Tab 增加更多组件(AppBar/StickyHeaderBar/TextField 的规格已过桥,页面未铺全);
  • 用 uitest dumpLayout 写自动化走查与截图脚本,替代手点验收;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套。

九、总结

9.1 核心难点回顾

上游 flexiui-core + flexiui-resources(Compose 组件树,留盘对照)
    → 验证 Compose / BetterAndroid 制品可达性(均无 ohos 变体 → 真编译出局)
    → 切口切在"设计语言 = 数据"(令牌 + 规格 + 行为数学,1:1 + 文件:行号)
    → example/ 独立构建(miuix 模式,Kotlin 2.2.21-1.0.0 / Gradle 8.14.1)
    → ohosArm64 / ohosX64 双 ABI 动态库 + 23 项 jvmTest 钉值
    → C ABI(@CName × 5)+ 扁平 JSON(手写 MiniJson,定点格式化)
    → C++ N-API(WrapStringCall 模板 + DisposeString 纪律)
    → ArkTS 四 Tab(令牌驱动渲染 + PanGesture 接行为数学)
    → ArkUI 真机验收(方案切换 / 字阶 / 组件画廊 / 图标回放)

9.2 封装层次

  • 引擎层:色彩方案常量与 resolve、形状/尺寸/字阶令牌、20 个组件规格、8 个图标、行为数学(全部纯 Kotlin);
  • Native 层:生成双 ABI 动态库并通过 5 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、typed Record 请求构建、令牌渲染、手势驱动与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 适配路线跟着当天制品可达性走:同一批社区仓库里,KStore 因为 kotlinx ohos 制品补齐走了真编译整库,FlexiUI 因为 Compose 与 BetterAndroid 制品缺席回到内核提取——两种路线都对,判断依据都是"制品线上现在有什么",而不是历史结论。先花半天做可达性验证,比先选路线再硬凑依赖便宜得多;
  2. UI 库的适配切口是"设计语言 = 数据":色板、度量、时长、贝塞尔是数据,可提取可钉值;行为数学是纯函数,可过桥;组件树是代码,留给工具链对齐。切口切在数据上,ArkTS 侧每一份令牌都有 文件:行号 审计链,适配才不会退化成"抄了一份设计稿";
  3. 上游没有测试时,钉值断言是唯一闸门:23 项 jvmTest 的期望值逐个抄自上游源码(含"有意为之"的值,如纯黑方案 themeTertiary 的 0x45 与深色 0x40 之差),“红即抽漏”。页面看起来对不算验收——看起来对只证明没糊,不证明和上游逐位一致。

9.4 适配成果

当前适配已完成:

  • FlexiUI 0.1.0-alpha01(compose 分支)设计语言内核的双 ABI Kotlin/Native 编译(上游源文件零改动,提取件与上游逐值一致);
  • 5 个 C ABI 符号和 C++ N-API 包装层(nm -D 验证全局 T 符号,llvm-readelf 验证 NEEDED 全短名);
  • DevEco Stage ArkUI 四 Tab 验收页面(20 套方案切换、五档字阶、10 类组件画廊、8 图标回放,可交互组件行为决策走 Native);
  • 23 项 JVM 钉值全绿 + HAP 构建(双 ABI 六件套 so,约 8.6MB)、签名、设备安装和真机验证;
  • 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐