开源鸿蒙平台 CMP 三方库「FlexiUI」适配全流程
本文记录
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 命令串与描边参数,ArkUIPath组件直接回放; - 版本握手(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 Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| JDK | 21(org.gradle.java.home 指向 Homebrew openjdk@21) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm 保留作钉值测试) |
| Gradle | 8.14.1(example 独立 wrapper;2.2.21 线的既定配套) |
| DevEco product | 5.1.0(18) 兼容(DevEco Studio 26 / API 26 SDK 编译) |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.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-core | 26 | 全部 import androidx.compose.*;api compose.foundation + BetterAndroid compose-extension | 令牌提取(主题 6 文件 + 组件 21 文件的 Defaults 与行为数学抄录进引擎) |
flexiui-resources | 9 | ImageVector 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 方法 | 覆盖内核 |
|---|---|---|
FlexiTheme | flexiTheme(req) | 20 套方案的 9 槽位色 + 形状 + 尺寸 + 字阶 |
FlexiComponents | flexiComponents(req) | 20 个组件的 Defaults(colors/style/animation + 文件:行号) |
FlexiBehavior | flexiBehavior(req) | Switch layout/target/trackColor/dragThreshold、Slider layout/valueToOffset/offsetToValue/snap |
FlexiIcon | flexiIcon(req) | 8 个图标的 SVG pathData + 描边参数 |
FlexiVersion | flexiVersion() | 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.toml | Kotlin 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.kt | 21 组方案常量 + resolve(镜像 ColorScheme.toColors)+ ARGB 工具 |
nativeApp/.../FlexiComponentSpecs.kt | 20 个组件规格(colors/style/animation + source 出处) |
nativeApp/.../FlexiBehavior.kt | Switch/Slider 行为数学(公式逐行对齐上游) |
nativeApp/.../Bridge.kt | 5 个 C ABI、try/catch 错误通道 |
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 | 鸿蒙适配主文档(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/version | JSON 分发 |
| N-API | flexiTheme(req) 等 5 个 | 向 ArkTS 暴露能力 |
| ArkTS | loadTheme() / 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 代码):
| 分组 | 用例数 | 钉住什么 |
|---|---|---|
| 色彩方案 | 4 | defaultLight 全 9 槽位、dark/black 关键槽、7 色系抽检、resolve 分支矩阵(含 System 回落) |
| 形状/尺寸/字阶 | 3 | primary 15 / secondary 10 / circle;12 项尺寸全量;五档字号 + 1.5em 行高 + Bold 位 |
| 颜色工具 | 2 | alpha 量化(0.3→0x4D、0.35→0x59);lerp 端点与中点(0x93777777) |
| 行为数学 | 4 | Switch 布局(maxOffset 20)、判定边界(9.9/10.0)、轨道色 lerp;Slider 换算与 snap |
| 图标 | 1 | 8 个图标、viewport/strokeWidth/路径首段 |
| MiniJson | 3 | 扁平解析(含逗号串)、转义、定点格式化(永不科学计数法) |
| 引擎端到端 | 6 | theme/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 功能用例
- 启动后状态行出现
桥版本 0.1.0-alpha01-ohos.1 · Kotlin 2.2.21-1.0.0(来自真实 Native 调用,非静态文字),主题 Tab 显示已加载 9 色 · 5 档字阶 · 12 项尺寸(浅色 / default); - 依次点 8 个色系 chip:9 个色块与十六进制值整套切换,如 blue 浅色 themePrimary 变
#FF0099DF、red 变#FFFF5545; - 开深色:backgroundPrimary 变
#FF2D2D2D暗底、文字变亮;再开纯黑:backgroundPrimary 变#FF000000; - 字阶 Tab 五档按真实字号渲染,titlePrimary 25sp Bold 与 secondary 12sp 对比明显,行高一致 1.5em;
- Switch 点击直接开/关(250ms FastOutSlowIn 动画);拖拽时轨道色随 fraction 从
#27777777渐到#FF777777(逐帧走桥),松手按 Native 判定吸附; - Slider 拖拽/点按换算 value;切"步进 2"后松手吸附到 0/73.33/146.67/220 锚点;
- Dialog 弹出按 0.8→1.1→1 三段缩放,scrim 为 35% 黑;CheckBox 勾选后白色 checkMark 路径出现在 4dp 圆角方框内;
- 图标 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 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让令牌内核真正编译成
ohosArm64/ohosX64动态库并打进 HAP; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement最前,否则配置期直接Unresolved reference: ohosArm64;- 本机默认 JBR 25 起不动 Gradle 8.14.1(
Unsupported class file major version),org.gradle.java.home必须钉回 21; - commonMain 里
String.format/Math.floor是 JVM-only API,KN 编译直接失败——十六进制补零与定点格式化要手写; - KN 浮点转字符串可能出
1.0E-5科学计数法,打断 ArkTSJSON.parse——MiniJson 输出必须定点化(×1e6 取整再剥零); - entry 的 NAPI 类型包(
file:./src/main/cpp/types/libflexiui_napi)不会被 hvigor 可靠地自动安装,cd entry && ohpm install要手动跑,否则桥函数在 ArkTS 里静默变any; @Builder参数不能传 lambda——卡片结构换成@Component+@BuilderParam+ 尾随闭包,否则 UI DSL 内部大面积假语法错;- ArkTS 严格模式禁无类型对象字面量:
JSON.stringify({...} as Record<string, Object>)不合规,必须const rec: Record<string, Object> = {}逐键赋值; curves.cubicBezierCurve(...)返回ICurve,字段直接声明Curve会类型不匹配;Path绘图组件的属性是.fill()/.stroke(),不是 Shape 容器的fillColor/strokeColor——属性名混用直接编译失败;~/ohos/command-line-tools的 hvigorw 对5.1.0(18)兼容工程报00303168 SDK component missing,命令行打包必须用 DevEco 自带 hvigor + 自带 node;- KN 全量 CAdapter 头有 42KB,
cpp/include只留前缀精简版——DisposeStablePointer/DisposeString恰为真实ExportedSymbols结构体前两成员,布局兼容; build-profile.json5的签名材料由 DevEco 自动写入,提交前必须还原signingConfigs: [],证书绝不进入 AtomGit;- 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 三条经验
- 适配路线跟着当天制品可达性走:同一批社区仓库里,KStore 因为 kotlinx ohos 制品补齐走了真编译整库,FlexiUI 因为 Compose 与 BetterAndroid 制品缺席回到内核提取——两种路线都对,判断依据都是"制品线上现在有什么",而不是历史结论。先花半天做可达性验证,比先选路线再硬凑依赖便宜得多;
- UI 库的适配切口是"设计语言 = 数据":色板、度量、时长、贝塞尔是数据,可提取可钉值;行为数学是纯函数,可过桥;组件树是代码,留给工具链对齐。切口切在数据上,ArkTS 侧每一份令牌都有 文件:行号 审计链,适配才不会退化成"抄了一份设计稿";
- 上游没有测试时,钉值断言是唯一闸门: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。
参考文档
更多推荐

所有评论(0)