开源鸿蒙平台kmp鸿蒙三方库ohos_Reorderable适配全流程
本文记录 Reorderable 重排库接入 OpenHarmony 的完整过程,覆盖工程盘点、
ohosArm64示例架构、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 重排界面、HAP 签名和真机验收。与“把列表页面重新写一份 ArkTS”不同,本次适配复用 Kotlin 侧的重排状态模型和验收逻辑,让列表、网格、瀑布流、横向容器以及固定项规则经过真实的 Kotlin/Native 产物传给 ArkUI。这样可以验证共享代码确实在 OpenHarmony 设备上运行,而不是只验证一套页面副本。
本项目的核心是 KMP/CMP Reorderable 库,示例 UI 使用 ArkTS Stage 页面和 ArkUI 容器,采用独立的“重排工作台”布局,展示插入式移动、网格交换、锁定项、长按模式、拖动句柄和 Native 自检。
项目地址: AtomGit/oh-tpc/ohos_Reorderable
开发工具: DevEco Studio
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
Reorderable 是一个面向 Jetpack Compose 和 Compose Multiplatform 的重排库。它的核心价值不在某一个列表页面,而在于重排状态、项目索引、固定项判断、列表插入和网格交换规则可以在共享代码中组织,然后交给不同平台的容器和手势层使用。
OpenHarmony 应用不能直接把 JVM 或 Android Compose 产物安装到 ARM64 设备。如果只把页面重新写成 ArkTS,虽然可以画出几个按钮,却无法验证 Kotlin 共享模型是否真的在鸿蒙设备上运行,也容易在插入式移动、网格交换和锁定项处理上出现第二套实现。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 示例工程默认没有 ohosArm64(),无法生成 OpenHarmony KLIB 和 ARM64 动态库。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。 |
| 容器实现不同 | Compose 的 LazyColumn、LazyRow、LazyVerticalGrid 和瀑布流容器不能直接作为 ArkUI Stage 页面使用。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| 手势模型不同 | Compose 的拖动句柄和长按手势需要映射为 ArkUI 的触摸事件,并处理滚动、点击和松手时机。 |
| 消费验证不足 | 根工程编译通过,不代表独立的 example 工程能链接动态库和打包 HAP。 |
| 交付链路复杂 | 原生库、CMake、HAP、签名、设备安装和 ARM64 ELF 依赖都需要单独检查。 |
因此,本次实现把适配边界放在三个地方:Kotlin/Native 目标配置、原生桥接层和 ArkUI 交互层。重排状态和验收规则仍由 Kotlin 维护,ArkTS 负责页面状态、容器切换和触摸反馈。
1.2 库提供的能力
Reorderable 的公共能力包括列表项目重排、网格项目交换、拖动句柄、长按拖动句柄、锁定项处理和多个 Compose 容器适配。本次示例没有把这些规则重新实现成一套独立的 ArkTS 业务模型,而是选择最容易在设备上验证、又能体现容器差异的四类场景:
- 列表模式:使用插入式移动,项目拖到新位置后,后续项目顺序连续移动。
- 网格模式:使用交换式移动,两个网格单元交换位置,能够体现
LazyVerticalGrid的重排语义。 - 瀑布模式:使用不同高度卡片展示
LazyVerticalStaggeredGrid的连续重排概念。 - 横向模式:使用横向容器展示
LazyRow的拖动句柄和横向移动状态。
页面另外提供固定的“收件箱”项目、UP/DN 单步操作、拖动句柄、长按模式切换、全部重置和 Native 自检。固定项目不会进入重排,其他项目的状态按布局模式分别保存。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 项目模型、布局模式、移动规则、锁定项判断和自检逻辑由 Kotlin 共享。 |
| 平台目标 | 为示例模块增加 ohosArm64,生成 libreorderable.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象直接暴露给 ArkTS。 |
| UI 完整 | 页面要能切换四种容器、拖动句柄、单步移动、切换长按状态、重置并显示自检结果。 |
| 可测试 | JVM 测试、Native 链接、ArkUI/HAP 构建和真机交互分别验收。 |
| 签名安全 | 仓库只保留未签名工程,证书、profile 和密码由开发者手动配置。 |
| 仓库规范 | README、文章、效果图和项目地址统一使用 AtomGit。 |
说明: 本次交付提供源码适配和独立 OpenHarmony 示例,不新增一个替代 Reorderable 公共 API 的 ArkTS 重排库。这样可以保持上游 API 和平台实现边界清晰。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 Reorderable 模块、公共能力和示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、仓库和独立消费工程
第 3 阶段:状态与序列化 ── 建立 BoardItem、BoardMode、移动规则和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API 和内存释放
第 5 阶段:能力封装 ── ArkUI 四种容器、触摸拖动、按钮操作和自检页
第 6 阶段:示例与验证 ── HAP 构建、签名、设备安装和真机验收
每个阶段都使用真实产物作为下一阶段输入:shared 先验证重排模型,nativeApp 再把模型链接为 ARM64 动态库,ohosApp 通过 CMake 和 N-API 加载动态库,最后由 DevEco 负责 HAP 打包和签名。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点原库源码和公共 API
根工程保留 Reorderable 原有的多平台模块和 Compose 代码。OpenHarmony 示例放在 example/ 下,避免把 DevEco 工具链、ArkTS 文件和证书配置混入库模块:
reorderable/ 核心重排库模块
reorderable/compose/ Compose Multiplatform 重排实现
demoApp/ 原有 Compose 示例
example/shared/ OpenHarmony 示例共享模型和 JVM 测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 应用和 ArkUI 页面
scripts/ 构建、签名工程和 HAP 辅助脚本
docs/openharmony/ 验收记录和效果图
example 是一个独立的 Gradle 工程。它不会把 DevEco 工程当成 Kotlin 子模块,因此可以分别执行 Gradle 和 Hvigor,也可以将 ohosApp 复制到另一个目录完成手动签名。
1.2 固定工具链和版本矩阵
本次示例使用 Kotlin Multiplatform 2.2.21-1.0.0、Gradle Wrapper、JDK 17 或 21、OpenHarmony API 20 ARM64 Native SDK 和 arm64-v8a HAP ABI。根 Reorderable 模块继续保留原有平台矩阵,OpenHarmony 的 Kotlin/Native 依赖由 example/settings.gradle.kts 中的社区 Maven 仓库解析。
example/ohosApp
↓ CMake + N-API
example/nativeApp/libreorderable.so
↓ project dependency
example/shared
↓ shared Kotlin reorder model
Reorderable move semantics
这里的 shared 不是 ArkUI 页面数据的临时缓存,而是明确的跨平台模型层。它定义 BoardItem、BoardMode、ReorderBoard 和 CheckResult,负责生成四种布局的项目快照;nativeApp 只负责把模型转换成 C ABI 可返回的 JSON,ohosApp 只负责读取 JSON、展示状态和处理触摸输入。
1.3 创建适配示例目录
参考 diff 工程的主要交互是编辑两段文本并比较差异。Reorderable 的核心能力是项目重排,如果沿用文本编辑器布局,会掩盖列表插入、网格交换和拖动句柄能力。因此页面选择了一个独立的重排工作台:顶部是布局切换,中间是操作按钮和说明,底部是可交互项目卡片以及库自检入口。
页面不会复用上一个适配项目的界面。列表、网格、瀑布和横向四种容器使用同一份共享状态,但每种布局的 ArkUI 容器和卡片排列单独实现,确保用户可以直观看到库能力在不同容器中的表现。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
共享模块保留 JVM 测试,同时增加 ohosArm64():
plugins {
kotlin("multiplatform")
}
kotlin {
jvm()
ohosArm64()
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
JVM 目标让重排规则可以在不连接设备的情况下运行测试,ohosArm64 则让同一份 commonMain 代码进入 Kotlin/Native 动态库。
2.2 focused build 的作用
Native 模块构建一个 shared library,并用 linker map 限制导出的符号:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "reorderable"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
OpenHarmony 动态库不是直接由 ArkUI 加载的 JavaScript 包,而是通过 CMake 链接到 libentry.so。因此构建时需要同时处理 Kotlin/Native 链接器参数和 OpenHarmony NDK 库。
2.3 配置插件仓库和依赖仓库
example/settings.gradle.kts 配置 OpenHarmony 社区 Maven、Maven Central 和 Gradle Plugin Portal:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
}
}
示例的 Kotlin 插件版本在 example/build.gradle.kts 中固定:
plugins {
kotlin("multiplatform") version "2.2.21-1.0.0" apply false
}
group = "sh.calvin.reorderable.example"
version = "3.1.0-ohos.1"
OpenHarmony 示例脚本使用 JDK 17 或 21。执行脚本前先确认:
export JAVA_HOME="/path/to/jdk-21"
java -version
2.4 通过构建产物消费共享库
example/nativeApp 依赖 :shared,并通过 prepareOhos 将 Kotlin/Native 产物复制到 DevEco 工程:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libreorderable.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libreorderable_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("ohosApp/entry"))
}
动态库和生成头文件属于构建产物,.gitignore 会排除它们。仓库保留复制任务,这样每台开发机都可以从源码重新生成与本机工具链匹配的文件。
第 3 阶段:状态与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 之间不能直接共享 Kotlin 对象。为了让边界稳定,示例使用一个 JSON 响应描述当前布局和项目状态:
ReorderBoard
↓ BoardSnapshot
Kotlin/Native JSON encoder
↓ UTF-8 buffer
C++ N-API string
↓
ArkTS JSON.parse
↓
ArkUI layout and interaction state
这样项目顺序、锁定标志、长按开关和移动次数始终在 Kotlin 模型中生成,ArkTS 不需要了解 Kotlin data class 的内部结构,也不会重新计算一套移动规则。JSON 字段可以随着示例能力扩展,而 C ABI 入口仍然保持稳定。
3.2 BoardItem 和 BoardSnapshot 设计
页面需要的不只是项目标题,还需要副标题、锁定状态、布局模式和移动次数:
data class BoardItem(
val id: Int,
val title: String,
val subtitle: String,
val locked: Boolean,
)
data class BoardSnapshot(
val mode: String,
val longPress: Boolean,
val moves: Int,
val items: List<BoardItem>,
)
locked 在 Kotlin 中统一判断。ArkTS 只把项目映射到卡片;锁定项目不显示移动按钮,也不会参与拖动。moves 用来验证点击和拖动确实经过共享模型,而不是只改变页面局部状态。
3.3 BoardMode 和四种移动语义
ReorderBoard.move 通过布局模式选择移动规则:
enum class BoardMode(val wireName: String) {
LIST("list"),
GRID("grid"),
STAGGERED("staggered"),
ROW("row"),
}
fun move(mode: BoardMode, from: Int, to: Int): Boolean {
if (list[from].locked || list[to].locked) return false
if (mode == BoardMode.GRID) {
val item = list[from]
list[from] = list[to]
list[to] = item
} else {
list.add(to, list.removeAt(from))
}
moves++
return true
}
列表、瀑布和横向模式使用插入式移动,网格模式使用交换式移动。四种布局分别持有一份列表,因此在网格中移动项目不会修改列表模式的顺序。
3.4 错误响应和内存限制
Native 层返回的快照对象包含以下字段:
{
"mode": "list",
"longPress": false,
"moves": 1,
"items": [
{
"id": 0,
"title": "收件箱",
"subtitle": "固定分区 · 不参与重排",
"locked": true
},
{
"id": 2,
"title": "发布清单",
"subtitle": "今天 11:00",
"locked": false
}
]
}
Kotlin/Native 计算异常会在桥接层转换为 ArkTS 可捕获的调用异常。C++ 层创建 ArkTS 字符串后立即调用 ReorderSampleFree,避免 Native 堆内存一直由 JavaScript 持有。模式和索引参数在 N-API 层检查整数类型,避免无效输入直接进入重排模型。
第 4 阶段:原生桥接(技术难点)
4.1 问题:Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 ReorderBoard、BoardItem、List 和异常对象属于 Kotlin 运行时对象。ArkTS 通过 N-API 接收到的是 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象使用。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ shared reorder model
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 导出基础字段数组 | 不需要 JSON | 字段扩展和错误处理困难 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写重排模型 | 页面调用简单 | 逻辑重复,结果可能与 Kotlin 不一致 | ❌ |
4.3 Kotlin/Native 导出函数
NativeApi.kt 导出六个函数:
@CName("ReorderSampleSnapshot")
fun snapshotNative(mode: Int): CPointer<ByteVar> = ...
@CName("ReorderSampleMove")
fun moveNative(mode: Int, from: Int, to: Int): Int = ...
@CName("ReorderSampleToggleLongPress")
fun toggleLongPressNative(): Int = ...
@CName("ReorderSampleReset")
fun resetNative() { ... }
@CName("ReorderSampleRunChecks")
fun checksNative(): CPointer<ByteVar> = ...
@CName("ReorderSampleFree")
fun freeNative(value: CPointer<ByteVar>?) { ... }
返回值使用 nativeHeap.allocArray<ByteVar> 分配,并以 0 结尾,满足 C 字符串约定。所有返回字符串都必须由 ReorderSampleFree 释放。
4.4 C++ N-API 方法分发
C++ 模块注册五个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"snapshot", nullptr, Snapshot, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"move", nullptr, Move, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"toggleLongPress", nullptr, ToggleLongPress, nullptr, nullptr,
napi_default, nullptr},
{"reset", nullptr, Reset, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
move 检查参数数量和整数类型;快照和自检方法都遵循“调用 Native → 生成 ArkTS 字符串 → 释放 Native 缓冲区”的顺序。CMake 将 Kotlin/Native 动态库作为 imported library:
add_library(reorderable SHARED IMPORTED)
set_target_properties(reorderable PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libreorderable.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE reorderable libace_napi.z.so)
4.5 N-API 生命周期
ArkTS snapshot(mode)
│
▼
ReadInt + argument check
│
▼
ReorderSampleSnapshot(mode)
│
▼
napi_create_string_utf8(...)
│
▼
ReorderSampleFree(nativeBuffer)
│
▼
return JS string
C++ 负责把 Native 缓冲区转换成 ArkTS 字符串,并在返回前释放缓冲区。页面调用者不需要知道 Kotlin/Native 的堆实现,也不会因为切换布局或重复自检而积累 Native 内存。
第 5 阶段:能力封装
5.1 四种重排能力封装
共享层先把 Reorderable 概念封装成四种可验证模型:
| 模型 | 页面标题 | 表现的能力 |
|---|---|---|
LIST | 列表 | 插入式移动、固定分区、UP/DN 操作 |
GRID | 网格 | 两个网格单元交换、拖动句柄、独立状态 |
STAGGERED | 瀑布 | 不同高度卡片、连续容器排列、重排提示 |
ROW | 横向 | 横向容器、水平拖动句柄、横向状态 |
模型还提供 longPress 和 moves,让页面可以在不理解 Kotlin 业务代码的情况下展示当前状态和验收结果。
5.2 ArkTS 页面分区
Index.ets 只有一个 Stage 页面,按垂直方向分成:
标题区 REORDERABLE / 重排工作台 / OHOS · KMP
布局选择区 列表 / 网格 / 瀑布 / 横向
状态区 可交互示例、移动次数和当前提示
操作区 长按模式、重置全部布局
内容区 对应 ArkUI 容器和项目卡片
自检入口 查看库自检、重新运行自检
这个布局与参考 diff 工程的双文本编辑器完全不同,更适合展示重排库。列表、网格、瀑布和横向四种布局共用状态区和操作区,但每种容器单独渲染项目卡片。
5.3 ArkTS 状态和触摸状态
页面状态包括当前布局、Native 快照、自检结果和拖动中的触摸状态:
@State private mode: number = 0;
@State private board: BoardSnapshot = EMPTY_BOARD;
@State private checks: CheckResult[] = [];
@State private showChecks: boolean = false;
@State private dragOffset: number = 0;
private draggingIndex: number = -1;
private dragOriginIndex: number = -1;
private dragStartX: number = 0;
private dragStartY: number = 0;
句柄使用 ArkUI onTouch 接收 TouchType.Down、Move、Up 和 Cancel。拖动过程中只更新卡片预览位移,松手时以拖动起点计算目标索引并调用 Native move 一次,避免一段鼠标或触摸轨迹被拆成多次移动。
5.4 页面交互预设
页面提供四个布局按钮、项目卡片按钮和自检按钮:
- 切换按钮更新
mode,并重新请求当前布局的snapshot; - UP/DN 调用
move(mode, from, to),适合精确移动一格; - “拖动”按钮扩大命中区域,支持直接触摸和鼠标拖动;
- 开启长按模式时,句柄文案变为“按住拖动”,并保留共享层长按状态;
- “重置全部布局”清空四种布局的移动记录并恢复初始顺序;
- “查看库自检”显示共享层返回的每项 PASS/FAIL 结果。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── build.gradle.kts
├── settings.gradle.kts
├── shared/
│ ├── build.gradle.kts
│ └── src/commonMain + src/commonTest/
├── nativeApp/
│ ├── build.gradle.kts
│ └── src/ohosArm64Main/
│ ├── kotlin/.../NativeApi.kt
│ └── linker/shared-library.map
└── ohosApp/
└── entry/
├── src/main/ets/pages/Index.ets
└── src/main/cpp/napi_init.cpp
三个模块分别承担共享重排模型、Kotlin/Native 动态库和 DevEco Stage 页面职责。
6.2 原生模块注册
entry/src/main/cpp/napi_init.cpp 注册名为 entry 的 N-API 模块:
static napi_module reorderableModule = {
1, 0, nullptr, Init, "entry", nullptr, {0}
};
extern "C" __attribute__((constructor))
void RegisterReorderableModule() {
napi_module_register(&reorderableModule);
}
注册的方法与 ArkTS 类型声明一致:
export const snapshot: (mode: number) => string;
export const move: (mode: number, from: number, to: number) => number;
export const toggleLongPress: () => number;
export const reset: () => void;
export const runChecks: () => string;
6.3 Native 动态库准备
先在仓库根目录执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
或者只执行示例任务:
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
成功后应存在:
example/ohosApp/entry/libs/arm64-v8a/libreorderable.so
example/ohosApp/entry/src/main/cpp/include/libreorderable_api.h
6.4 构建与安装
仓库提供的工程不会写入个人签名材料。可以复制一份不含构建缓存和证书的 DevEco 工程:
python3 scripts/prepare-signing-project.py "$HOME/reorderable_ohos_signing"
在 DevEco Studio 中手动配置 API 20 ARM64 签名后构建 HAP:
./scripts/build-hap.sh "$HOME/reorderable_ohos_signing"
安装并启动:
hdc list targets
hdc -t <target-id> install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <target-id> shell aa start \
-a EntryAbility -b sh.calvin.reorderable.demo
最后按四种布局、UP/DN、拖动、长按状态、重置和 5/5 PASS 自检验收页面。
四、完整代码对照
4.1 整体架构
ReorderBoard
├─ BoardItem / BoardMode / CheckResult
├─ items(mode)
├─ move(mode, from, to)
├─ toggleLongPress()
├─ reset()
└─ checks()
↓ JSON
NativeApi.kt
├─ ReorderSampleSnapshot
├─ ReorderSampleMove
├─ ReorderSampleToggleLongPress
├─ ReorderSampleReset
├─ ReorderSampleRunChecks
└─ ReorderSampleFree
↓ C ABI
napi_init.cpp
├─ snapshot()
├─ move()
├─ toggleLongPress()
├─ reset()
└─ runChecks()
↓ ArkTS
Index.ets
├─ JSON.parse
├─ renderBoard
├─ handleTouch
├─ shift / resetBoard
└─ renderChecks
4.2 文件清单
| 文件 | 作用 |
|---|---|
example/shared/src/commonMain/.../ReorderBoard.kt | 项目模型、四种布局状态、移动规则和自检。 |
example/shared/src/commonTest/.../ReorderBoardTest.kt | JVM 侧布局移动和锁定项测试。 |
example/nativeApp/.../NativeApi.kt | Kotlin/Native C ABI、JSON 和内存释放。 |
example/nativeApp/.../shared-library.map | 限制导出的 Native 符号。 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | C++ N-API 方法和模块注册。 |
example/ohosApp/entry/src/main/cpp/CMakeLists.txt | imported library 和 N-API 链接。 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | ArkUI 状态、四种布局、触摸和自检页。 |
scripts/build-openharmony.sh | 根测试、示例测试和 Native 准备。 |
scripts/prepare-signing-project.py | 复制无签名材料的 DevEco 工程。 |
scripts/build-hap.sh | 调用 Hvigor 构建 HAP。 |
docs/openharmony/images/reorderable-openharmony-workbench.png | 运行效果图。 |
4.3 关键 API 对照
ReorderSampleSnapshot → 返回某个布局的项目顺序和状态
ReorderSampleMove → 执行列表插入或网格交换
ReorderSampleToggleLongPress→ 切换共享层长按状态
ReorderSampleReset → 恢复四种布局的初始顺序
ReorderSampleRunChecks → 返回五项共享验收检查
ReorderSampleFree → 释放 Native 字符串
ArkTS 侧只依赖五个方法,避免把 Native 指针、Kotlin 对象和内存管理细节带入页面逻辑。
4.4 ArkTS 与 TypeScript / Kotlin 的语法差异(本次实际踩到的)
| 能力 | Kotlin/Native | ArkUI |
|---|---|---|
| 布局类型 | 生成 BoardMode | 选择 Column、Grid、WaterFlow 或横向 Row |
| 项目顺序 | 维护 List<BoardItem> | 使用 ForEach 渲染卡片 |
| 插入移动 | list.add(to, list.removeAt(from)) | 调用 move 后重新解析快照 |
| 网格交换 | 交换两个索引位置 | 网格按钮和拖动松手调用相同 API |
| 固定项 | locked 字段 | 隐藏控制按钮并拒绝触摸拖动 |
| 触摸输入 | 不持有 UI 触摸对象 | TouchType.Down/Move/Up/Cancel |
| 状态刷新 | snapshotJson | JSON.parse(snapshot(mode)) |
五、关键决策说明
决策 1:把 ohosArm64 加入示例的公共构建约定
示例模块加入 ohosArm64(),让同一份共享重排模型进入 Kotlin/Native 动态库。Reorderable 的 Compose 手势和 ArkUI Stage 的触摸生命周期不同,因此让 Kotlin 负责跨平台移动规则,让 ArkUI 负责 OpenHarmony 原生容器和触摸输入,在不改变 Reorderable 公共 API 的情况下验证真实的 KMP/CMP 数据链路。
决策 2:独立消费者必须通过构建产物消费
example/shared、example/nativeApp 和 example/ohosApp 按独立工程组织,先验证共享模型,再准备动态库,最后由 DevEco 打包。这样可以同时覆盖 Gradle 变体、KLIB、CMake、N-API 和 HAP 链路,避免根工程通过但下游无法消费。
决策 3:JSON 作为跨语言数据契约
如果在 ArkTS 中生成项目顺序,Native 层只剩一个空壳,无法证明 Kotlin/Native 代码被设备真正调用。现在由 ReorderBoard 生成项目、锁定标志、移动次数和布局状态,ArkTS 只做 JSON 解析和页面响应。
决策 4:桥接层只开放六个 C ABI 入口
示例页面字段会随交互能力增加而变化,JSON 可以向后兼容新增字段。C ABI 只保留布局索引、项目索引和字符串返回,不需要为每一个 UI 属性扩展 N-API 方法签名。
决策 5:页面按照库能力重新设计
参考 diff 工程的文本编辑交互不适合重排库,工作台把四种容器放在同一个选择区,用户可以在一次运行中对比插入式移动和网格交换,并在卡片上看到固定状态、UP/DN 和拖动句柄。新的页面主题、配色和卡片布局也与上一个示例区分开。
决策 6:把库验证和设备验证分开
移动次数和项目顺序由共享模型统一维护,JVM 测试验证规则,Kotlin/Native 编译验证目标,Hvigor 验证 HAP,真机验证 N-API、ArkUI 容器和触摸操作。证书、profile 和密码属于开发机材料,不应放入 AtomGit;开发者在 DevEco Studio 中手动签名,签名工程与源码工程分离。
六、测试与验证
6.1 测试环境
本次真机验收使用:
| 项目 | 值 |
|---|---|
| 设备 | HUAWEI Mate 60 Pro;同时使用 HarmonyOS 电脑窗口进行交互复现 |
| ABI | ARM64 |
| DevEco | DevEco Studio 6 系列 |
| HAP 包名 | sh.calvin.reorderable.demo |
| 入口 | EntryAbility |
| 示例版本 | 3.1.0-ohos.1 |
6.2 静态检查与单元测试
共享测试覆盖:
(cd example && ./gradlew :shared:jvmTest)
Native 动态库准备任务为:
(cd example && ./gradlew :nativeApp:prepareOhos)
检查点包括四种布局都能移动项目、锁定项目不能移动、网格使用交换语义、长按状态可以切换以及多布局状态相互独立。
6.3 原生桥接和 HAP 验证
DevEco 构建阶段需要确认:
- CMake 能找到
libreorderable.so; - Ninja 能生成
libentry.so; - ArkTS 编译可以解析
libentry.so的类型声明; - HAP 包含
libs/arm64-v8a下的动态库; - 签名由当前开发机的 DevEco 配置完成。
构建命令为:
DEVECO=/Applications/DevEco-Studio.app/Contents
export PATH="$DEVECO/tools/node/bin:$DEVECO/tools/ohpm/bin:$PATH"
export DEVECO_SDK_HOME="$DEVECO/sdk"
cd example/ohosApp
"$DEVECO/tools/hvigor/bin/hvigorw" \
--mode module \
-p module=entry@default \
-p product=default \
-p buildMode=debug \
assembleHap --no-daemon
6.4 功能验证用例
用例 1:默认页面和自检
启动应用后,页面显示 REORDERABLE、重排工作台、四个布局按钮和默认列表。页面底部进入库自检后,显示 5/5 PASS,包含列表插入式重排、网格交换式重排、锁定项不可移动、长按句柄状态和多布局状态独立五项结果。
用例 2:四种布局切换
按顺序点击列表、网格、瀑布、横向:
| 按钮 | 页面内容 | 主要验证点 |
|---|---|---|
| 列表 | 可交互示例 | 固定分区、插入式移动、UP/DN |
| 网格 | 两列项目卡片 | 网格交换、标题和操作区不重叠 |
| 瀑布 | 不同高度卡片 | 连续排列和独立项目状态 |
| 横向 | 水平卡片列表 | 横向容器和水平拖动句柄 |
每次切换都会更新说明文本、容器和当前布局的项目顺序。
用例 3:按钮移动
点击项目上的 UP 或 DN,ArkTS 根据项目 ID 计算当前位置,再调用 move(mode, from, to)。列表、瀑布和横向模式执行插入,网格模式执行交换,移动次数会增加。
用例 4:触摸拖动
按住可移动项目上的“拖动”按钮并移动,卡片会跟随触摸位置产生预览位移;松手后只提交一次移动。列表拖动“设计评审”到下一项后,顺序变为“收件箱、发布清单、设计评审、用户访谈”;网格拖动则交换两个网格单元。
用例 5:长按模式和固定项
点击“开启长按模式”,状态变为“长按模式已开启:按住句柄开始拖动”,句柄文案变为“按住拖动”。固定的“收件箱”不显示移动按钮,触摸和按钮操作都不能改变它的位置。
用例 6:重置和库自检
点击“重置全部布局”,四种布局恢复初始顺序,移动次数归零,长按状态关闭。点击“查看库自检”,确认五项共享检查全部显示 PASS(通过)。
6.5 验证结论
本次验收结果:
sharedJVM 验收通过;nativeAppARM64 动态库生成成功;- ArkUI/Hvigor HAP 构建成功;
- 签名 HAP 安装成功并启动
EntryAbility; - 四种布局切换成功;
- 列表插入式移动和网格交换成功;
- 电脑窗口拖动可以改变顺序,移动次数按一次拖动提交一次;
- 固定项目保持不可移动;
- 首页库自检显示
5/5 PASS。
七、运行效果
7.1 获取运行截图
下面是 Reorderable 工作台运行效果图。页面包含标题、四种布局按钮、移动次数、长按模式、重置操作、固定分区、UP/DN 和“按住拖动”句柄。

7.2 界面文本快照
| 截图区域 | 对应实现 |
|---|---|
REORDERABLE / 重排工作台 | ArkUI 页面标题和平台说明。 |
| 列表 / 网格 / 瀑布 / 横向 | modeButton(),切换四种容器。 |
可交互示例 | 工作台状态卡片。 |
6 次移动 | BoardSnapshot.moves,反映共享模型移动次数。 |
项目已拖动到新位置 | 触摸拖动松手后的 ArkTS 状态提示。 |
关闭长按模式 | toggleLongPress() 返回的共享状态。 |
收件箱 / 固定 | BoardItem.locked 固定分区。 |
UP / DN | 单步移动按钮,调用共享 move。 |
按住拖动 | ArkUI onTouch 句柄和长按模式提示。 |
| 列表卡片顺序 | snapshot(mode) 返回的共享项目列表。 |
其他三种布局
- 网格:两列卡片和交换式移动,固定项目占据第一个网格位置。
- 瀑布:不同高度卡片连续排列,体现
LazyVerticalStaggeredGrid的容器语义。 - 横向:卡片沿水平方向排列,句柄使用相同的共享状态和触摸逻辑。
7.3 验证命令速查
# 根工程测试和 OpenHarmony 原生库准备
./scripts/build-openharmony.sh
# 单独运行共享测试和 Native 复制任务
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
# 准备不带个人证书的 DevEco 工程
python3 scripts/prepare-signing-project.py "$HOME/reorderable_ohos_signing"
# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh "$HOME/reorderable_ohos_signing"
# 安装和启动
hdc -t <target-id> install -r entry-default-signed.hap
hdc -t <target-id> shell aa start -a EntryAbility -b sh.calvin.reorderable.demo
八、遗留问题与改进方向
8.1 踩坑复盘
- Native 文件必须先复制:如果没有执行
prepareOhos,CMake 的 imported library 路径存在但文件不存在,Ninja 会直接失败。 - JDK 版本必须匹配:OpenHarmony 示例脚本使用 JDK 17 或 21,不能用不兼容的 JDK 版本绕过 Kotlin DSL 或 Native 编译器检查。
- 签名配置不能进源码:本机 DevEco 生成的 profile 可能带有绝对路径和密码,必须使用独立签名工程。
- 多设备 hdc 需要指定目标:同时连接真机和电脑设备时,安装命令要加
-t,否则 hdc 会提示需要确认设备。 - 网格和列表的移动语义不同:列表应该插入,网格应该交换,不能在 ArkTS 页面中用一个通用的数组操作替代共享模型规则。
- Button 手势会影响拖动:最初只给句柄绑定点击事件,拖动没有响应;随后改用
PanGesture,又遇到 Button 手势吞事件的问题,最终使用句柄onTouch接收原始触摸事件。 - 拖动事件需要一次提交:如果每个 Move 事件都立即调用 Native 移动,一次鼠标轨迹可能连续移动多次,因此现在采用“移动预览、松手提交”的方式。
8.2 已知问题
- 当前示例使用 ArkUI 容器和原始触摸事件表达核心重排能力,尚未实现完整 Compose Reorderable 的自动滚动、动画和复杂拖动占位效果。
- 瀑布流和网格的跨行拖动使用示例级目标索引计算,复杂大数据集仍需要结合真实容器布局信息优化。
- Native 模型以 JSON 返回,数据量很小时足够直观;大数据集需要进一步评估编码开销和增量更新策略。
- HAP 需要开发者在 DevEco Studio 中完成签名,仓库不会提供可直接发布的证书。
8.3 未来优化方向
- 将列表、网格、瀑布和横向卡片拆成可复用的 ArkUI 组件。
- 为触摸拖动增加自动滚动、目标占位、拖动阴影和更细致的命中区域。
- 在保持 JSON 契约稳定的前提下增加窗口化数据和增量刷新。
- 为 OpenHarmony 目标建立独立 CI,自动执行 JVM、Native、HAP 和依赖审计。
- 如果后续发布 OpenHarmony 变体,再为消费者提供明确的 AtomGit 仓库坐标和版本策略。
九、总结
9.1 核心难点回顾
Reorderable OpenHarmony 适配的难点不是把按钮画出来,而是让同一份 Kotlin 重排模型真正经过 Kotlin/Native、C ABI 和 N-API 到达设备上的 ArkUI 容器和触摸层:
共享模型 → Kotlin/Native → C ABI → C++ N-API → ArkTS JSON → ArkUI 容器
每一层都有清晰的输入和输出,出现问题时可以分别检查模型、动态库、符号、HAP 或页面状态。
9.2 封装层次
Reorderable root modules
└── example
├── shared 项目模型、移动规则、长按状态和 JVM 检查
├── nativeApp ohosArm64 动态库、C ABI、内存释放
└── ohosApp Stage、N-API、ArkUI 容器、触摸和 HAP
9.3 三条经验
- 先固定移动语义,再写 UI。 列表插入和网格交换由
ReorderBoard统一定义,ArkTS 不需要猜测 Kotlin 返回值。 - 把工具链问题和交互问题分开。 JVM、Native、CMake、Hvigor 和真机分层验证,能迅速定位是依赖、符号、签名还是触摸处理问题。
- 签名工程必须隔离。 开源仓库提交源码和脚本,开发者在 DevEco 中手动完成签名,既满足真机运行,也避免泄露本机密钥。
9.4 适配成果
- OpenHarmony 示例具备
ohosArm64Kotlin/Native 构建链路; - Kotlin 共享层提供四种布局模型、插入/交换规则和五项自检;
- Native 层通过六个 C ABI 函数向 ArkTS 提供 JSON 和状态操作;
- ArkUI 页面拥有独立的重排工作台 UI;
- 真机和电脑 HarmonyOS 窗口可以切换布局、单步移动和拖动项目;
- 拖动采用原始触摸事件,松手只提交一次排序;
- 中文、英文 README、本文、效果图和仓库地址统一使用 AtomGit;
- 签名材料、HAP 和原生构建产物不进入源码仓库。
参考文档
更多推荐



所有评论(0)