本文记录 ComposeReorderable 接入 OpenHarmony 的完整过程,覆盖 CMP 拖拽重排库盘点、ohosArm64/ohosX64 目标、Compose 源集隔离、纯 Kotlin 重排引擎、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。

ComposeReorderable 是面向 Compose Multiplatform 的 LazyList/LazyVerticalGrid 拖拽重排库。本次适配把上游的核心重排决策算法完整移植到 OpenHarmony ARM64 运行时,ArkUI 页面作为真机验收宿主,长按拾起、拖动交换、边缘自动滚动和取消回弹全部由 Kotlin 引擎驱动。

项目地址: AtomGit/oh-tpc/ComposeReorderable

上游仓库: aclassen/ComposeReorderable

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

ComposeReorderable(上游 aclassen/ComposeReorderable 0.9.7,Apache-2.0)为 Compose 提供 Modifier.reorderable、detectReorder / detectReorderAfterLongPress、ReorderableLazyListState / ReorderableLazyGridState:在 LazyColumn / LazyVerticalGrid 里长按拾起条目、拖动跨越交换(onMove)、边缘自动滚动(缓动加速)和取消回弹(DragCancelledAnimation)。KMP 项目通常把这些能力放在 commonMain,由 Android、桌面和 Web 共享。OpenHarmony 应用同样需要这套交互能力,但上游只有 jvm 和 js 两个目标,默认构建不会生成 OpenHarmony 的 Kotlin/Native 变体。

与协程这类无 UI 基础库不同,拖拽重排库的全部公开 API 都是 Compose 类型(Modifier、PointerInputScope、LazyListState),不能直接在鸿蒙上编译。而它的决策核心——目标判定、交换打分、自动滚动缓动——是与 UI 无关的纯算法。适配的关键判断是:把算法层和 Compose 层拆开,算法层进入 OpenHarmony,Compose 层隔离保留。

障碍具体问题
目标缺失上游只有 jvm() + js(IR),必须增加 ohosArm64() 才能生成 OpenHarmony KLIB。
版本断层上游是 Kotlin 1.8.0 / CMP 1.3.0 / Gradle 7.5.1 老栈;鸿蒙社区 Kotlin 是 2.2.21-1.0.0 另一条版本线。
API 已删除源码使用的 forEachGesture、changedToUpIgnoreConsumed 在新版 Compose 中已被删除,直接升级编译不过。
语言边界ArkTS 不能持有 Kotlin 对象,@CName 业务符号也不会自动挂给 ArkTS,需要 C ABI + N-API + JSON。
宿主差异手势识别、布局信息和滚动控制在 Compose 与 ArkUI 中是两套体系,交换/自动滚动语义必须由引擎统一下发。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在 Kotlin 目标配置、纯逻辑重排引擎和 ArkUI 验收层。重排语义仍由公共 Kotlin 实现负责,ArkTS 只负责采集手势、上报布局快照和应用引擎返回的交换与滚动。

1.2 库提供的能力

示例提供两个可观察场景,对应上游的两种宿主容器:

  • 列表 List:30 个等高条目,长按 500ms 拾起,垂直拖动跨越交换,拖到上下边缘自动滚动;
  • 网格 Grid:2 列 24 格,同样的拾起/交换/自动滚动流程,验证引擎的 2D 矩形推理。

引擎单步返回的 DragEffect 包含:

字段作用
dragging会话是否存活。
offsetLeft / offsetTop被拖条目相对当前槽位的平移量,直接驱动 .translate。
swapFrom / swapTo本帧应执行的数据交换,-1 表示无。
autoScroll主轴方向每帧滚动像素,带符号,0 表示停。
startIndex / currentIndex会话起点与当前位置,对应上游 onDragEnd(startIndex, endIndex)。

共享层还完整保留上游四个核心算法:findTargets 相交 + 中心距排序、chooseDropItem 四方向打分、calcAutoScrollOffset 越界检测和 interpolateOutOfBoundsScroll 缓动加速。

1.3 实现适配

维度要求
代码复用重排决策算法逐行移植到 KMP commonMain,不改判定语义。
平台目标库模块增加 ohosArm64 + ohosX64,生成双 ABI 动态库。
桥接稳定使用单个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址。
CMP 兼容上游 Compose 源码隔离在 composeMain 保留,等工具链对齐后接回;ArkUI 负责真机验收。
UI 完整支持长按拾起、拖动交换、边缘自动滚动、取消回弹、重置和状态行实时反馈。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。CMP 工程可以在工具链对齐后复用同一份引擎实现,在自己的 Compose 页面中获得与上游一致的重排行为契约。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点源集、隔离 Compose 代码和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64/ohosX64、钉死鸿蒙工具链
第 3 阶段:重排引擎建模   ── 逐行移植 findTargets/chooseDropItem/自动滚动缓动
第 4 阶段:原生桥接       ── C ABI、C++ N-API、JSON 契约和内存释放
第 5 阶段:ArkUI 宿主     ── 手势采集、布局快照、交换应用和滚动循环
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
reorderable/src/commonMain/    纯 Kotlin 重排引擎(ItemRect/DragEffect/ReorderEngine)
reorderable/src/composeMain/   上游 9 个 Compose 源文件(未注册源集,不参与编译)
example/nativeApp/             Kotlin/Native 动态库(@CName 桥 + MiniJson)
example/harmonyApp/            DevEco Stage、CMake N-API 和 ArkTS 页面
doc/img/                       真机效果图

上游 9 个 Compose 文件通过 git mv 从 commonMain 移到 composeMain,保持 100% 相似度的纯重命名;composeMain 不注册为 Kotlin 源集,ohos 编译器完全看不见它,文件保留供对照上游 API,等 CMP 工具链对齐后接回。Gradle 工程与 DevEco 工程分开执行,example/harmonyApp 不是根项目的 Kotlin 子模块。

1.2 工具链矩阵
项目配置
Kotlin Multiplatform2.2.21-1.0.0(HarmonyOS Kotlin,来自 eazytec Maven)
JDK21
OpenHarmony 目标ohosArm64 + ohosX64
Gradle8.14.1
DevEco product5.1.0(18) / HarmonyOS
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.reorderable
export JAVA_HOME="/path/to/jdk-21"
java -version

settings.gradle.kts 中 eazytec 的两个 Maven 仓库必须放在 pluginManagement 和 dependencyResolutionManagement 的最前面,否则 ohosArm64 目标无法解析:

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()
    }
}
1.3 页面边界
标题区          ComposeReorderable · OpenHarmony / CMP 拖拽重排库 · Kotlin/Native ohosArm64/ohosX64
Tab 区          列表 List / 网格 Grid / 重置
状态区          引擎实时响应(drag idx=2→2 scroll=0.0 off=(8.3, -60.1))
列表区          30 条等高条目,条目 0 展示仓库地址
网格区          2 列 24 格单元格

页面启动即渲染列表,长按条目 500ms 进入拖拽会话,与上游 detectReorderAfterLongPress 的默认行为一致。

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

2.1 加入 ohosArm64

库模块去掉全部 Compose 依赖、maven-publish 和 signing,只声明 Kotlin 目标:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)
    jvm()
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {}
    }
}

jvm() 保留用于交叉验证引擎行为;上游的 :android、:desktop、:web 示例目录留在磁盘但不参与构建,settings.gradle.kts 只 include :reorderable 和 :example:nativeApp。根构建禁用所有 Ohos*Test 任务:

subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }
}
2.2 构建核心库和 Native 产物
./gradlew :reorderable:compileKotlinJvm
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 \
  :example:nativeApp:linkDebugSharedOhosX64

链接任务的 finalizedBy 会把产物和匹配 triple 的 libc++_shared.so 一起拷进 DevEco 工程:

example/harmonyApp/entry/libs/arm64-v8a/libohosreorderable.so   (8.35 MB)
example/harmonyApp/entry/libs/arm64-v8a/libc++_shared.so
example/harmonyApp/entry/libs/x86_64/libohosreorderable.so      (7.57 MB)
example/harmonyApp/entry/libs/x86_64/libc++_shared.so
2.3 动态库模块消费库工程
kotlin {
    ohosArm64 {
        binaries.sharedLib { baseName = "ohosreorderable" }
    }
    ohosX64 {
        binaries.sharedLib { baseName = "ohosreorderable" }
    }

    sourceSets {
        ohosMain.dependencies {
            implementation(project(":reorderable"))
        }
    }
}

example/nativeApp 通过 project 依赖直连库模块,linkDebugSharedOhosArm64 完成后自动执行 copyKnSoArm64,DevEco 工程无需手工拷贝 .so。

第 3 阶段:重排引擎建模

3.1 JSON 契约
Index.ets 手势事件
        ↓ dx/dy + items 布局快照
ReorderableStep(request: String)
        ↓ MiniJson 解析
ReorderEngine
        │ findTargets / chooseDropItem / calcAutoScrollOffset
        ▼
DragEffect
        ↓ UTF-8 JSON
N-API reorderableStep()
        ↓
ArkUI 应用交换 / 平移 / 滚动

页面不复制重排判定逻辑,每一次交换和滚动都由 Kotlin 引擎计算。

3.2 结果数据类
class ItemRect(
    val index: Int,
    val left: Int,
    val top: Int,
    val right: Int,
    val bottom: Int,
)

class DragEffect(
    val dragging: Boolean,
    val offsetLeft: Float,
    val offsetTop: Float,
    val swapFrom: Int,
    val swapTo: Int,
    val autoScroll: Float,
    val startIndex: Int,
    val currentIndex: Int,
)

ItemRect 对应上游从 LazyListItemInfo / LazyGridItemInfo 读取的 left/top/right/bottom,由宿主填入当前布局。请求协议:

action: reset | start | drag | tick | end | cancel
start  → orientation / viewStart / viewEnd / maxScroll / x / y / items
drag   → dx / dy / items(可覆盖 viewStart / viewEnd)
tick   → items(自动滚动循环重估)
3.3 运行逻辑

目标判定:与被拖条目视觉矩形相交的候选按中心距排序(逐行移植上游 findTargets):

val dx = (centerX - (item.left + item.right) / 2).absoluteValue
val dy = (centerY - (item.top + item.bottom) / 2).absoluteValue
val dist = dx * dx + dy * dy

交换决策:按拖动方向对候选打分,取跨越幅度最大者(逐行移植上游 chooseDropItem):

if (dy < 0) {
    val diff = item.top - curY
    if (diff > 0 && item.top < draggedItemInfo.top) {
        val score = diff.absoluteValue
        if (score > highScore) { highScore = score; target = item }
    }
}

自动滚动:越界量经 EaseOutQuad 限幅、时间经 EaseInQuint 在 1500ms 内加速(逐行移植上游 interpolateOutOfBoundsScroll):

val cappedScroll = sign(viewSizeOutOfBounds) * maxScroll *
    EaseOutQuadInterpolator(outOfBoundsRatio)
val timeRatio = if (time > ACCELERATION_LIMIT_TIME_MS) 1f
    else time.toFloat() / ACCELERATION_LIMIT_TIME_MS
return cappedScroll * EaseInQuintInterpolator(timeRatio)

因为算法本来就是 2D 矩形推理,引擎天然同时支持垂直列表和网格,无需为 Grid 写第二套逻辑。

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

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry
  │ const char*
  ▼
Kotlin/Native C ABI
  │ ReorderableStep → ReorderEngine
  ▼
UTF-8 JSON + explicit dispose

ArkTS 无法直接调用 @CName 业务符号(HarmonyOS CAdapter 不导出业务函数),所以用 CMake 再包一层 libreorderable_napi.so。直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制状态语义,所以选择 C ABI + JSON。

4.2 Kotlin/Native 导出函数
@CName("ReorderableStep")
fun reorderableStep(request: String): String {
    return try {
        respond(ReorderableBridge.step(request))
    } catch (t: Throwable) {
        """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
    }
}

请求解析使用文件内手写的 MiniJson 递归下降解析器(object/array/string/number/bool/null),刻意不引入 kotlinx.serialization,保持动态库零额外 KN 依赖。数字输出为定点格式:整数不带小数点、浮点最多两位、绝不出现科学计数法。

4.3 C++ N-API 方法分发
static napi_value NapiReorderableStep(napi_env env, napi_callback_info info) {
    // 单 string 参数 → 引擎 → 单 string 返回
    const std::string request = JsToUtf8(env, args[0]);
    const char *raw = ReorderableStep(request.c_str());
    napi_value out = nullptr;
    napi_create_string_utf8(env, raw, NAPI_AUTO_LENGTH, &out);
    DisposeKnString(raw);   // 立即归还 Kotlin/Native 字符串内存
    return out;
}

N-API 只负责参数类型检查、字符串转换和释放,不实现任何重排语义。释放走 KN 导出的 libohosreorderable_symbols()->DisposeString。

4.4 CMake 和类型声明
set(KN_SO ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/libohosreorderable.so)

add_library(ohosreorderable SHARED IMPORTED)
set_target_properties(ohosreorderable PROPERTIES IMPORTED_LOCATION ${KN_SO})

add_library(reorderable_napi SHARED napi_init.cpp)
target_link_libraries(reorderable_napi PUBLIC libace_napi.z.so libhilog_ndk.z.so ohosreorderable)
export const reorderableStep: (request: string) => string;

$ORIGIN rpath 与 POST_BUILD 拷贝保证三个 .so(NAPI + KN + libc++_shared.so)在每个 ABI 目录内成套出现,缺件安装即报 9568347。

第 5 阶段:ArkUI 宿主

5.1 调用引擎
import { reorderableStep } from 'libreorderable_napi.so';

private step(req: object): EngineEffect {
  const raw: string = reorderableStep(JSON.stringify(req));
  return JSON.parse(raw) as EngineEffect;
}

应用一步结果:

private applyDragEffect(fx: EngineEffect, phase: string): void {
  this.dragDx = fx.offsetLeft;
  this.dragDy = fx.offsetTop;
  this.moveBySwap(fx);            // swapFrom/swapTo ≥ 0 → add(to, removeAt(from))
  if (Math.abs(fx.autoScroll) > AUTO_SCROLL_EPSILON) {
    this.startAutoScroll();       // 16ms tick 循环 + scroller.scrollBy
  } else {
    this.stopAutoScroll();
  }
}
5.2 页面交互

每个条目绑定 GestureGroup(Parallel, LongPressGesture(500ms), PanGesture):长按触发 start(携带命中坐标与布局快照),Pan 的每帧增量触发 drag,抬起触发 end,打断触发 cancel。宿主维护滚动偏移(onDidScroll + scroller.currentOffset()),实时重建 viewport 相对坐标的 items 快照上报引擎。拖拽期间 enableScrollInteraction(false) 锁住列表手势滚动,防止与引擎自动滚动双滚。

5.3 视觉反馈和错误边界

被拖条目应用 .translate + .zIndex(10) + .scale(1.03) + shadow 浮起;end 后 200ms EaseOut 动画归位;cancel 由宿主播放回弹(对应上游 DragCancelledAnimation)。引擎异常以 {"ok":false,"error":...} 返回,状态行直接展示错误文本,页面不崩溃。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/src/ohosMain/kotlin/example/nativeapp/Bridge.kt
└── harmonyApp/
    ├── entry/libs/{arm64-v8a,x86_64}/          Gradle 拷入的 KN so
    ├── entry/src/main/cpp/CMakeLists.txt
    ├── entry/src/main/cpp/napi_init.cpp
    ├── entry/src/main/cpp/types/libreorderable_napi/index.d.ts
    └── entry/src/main/ets/pages/Index.ets
6.2 构建、签名和安装
./gradlew :example:nativeApp:linkDebugSharedOhosArm64
# DevEco 打开 example/harmonyApp → 自动签名 → Build HAP(s)

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.reorderable

签名遵循仓库规范:build-profile.json5 只保留 "signingConfig": "default" 占位,签名材料由 DevEco 自动签名生成在本机,证书绝不进入仓库。

四、完整代码对照

4.1 整体架构

ReorderEngine.kt
    │ findTargets / chooseDropItem / interpolateOutOfBoundsScroll
    ▼
Bridge.kt
    │ @CName("ReorderableStep") + MiniJson
    ▼
libreorderable_napi.so
    │ reorderableStep(request) → response
    ▼
Index.ets
    │ JSON parse + swap + translate + scrollBy
    ▼
ArkUI 真机页面

4.2 文件清单

文件职责
reorderable/build.gradle.ktsohosArm64/ohosX64 目标,零 Compose 依赖
reorderable/src/composeMain/...上游 9 个 Compose 源文件(隔离保留)
reorderable/.../engine/ReorderEngine.kt重排决策核心,逐行移植上游算法
reorderable/.../engine/ItemRect.ktitem 的 viewport 矩形快照
reorderable/.../engine/DragEffect.kt单步结果契约
example/nativeApp/build.gradle.kts动态库链接与 so 拷贝
example/nativeApp/.../Bridge.ktC ABI、JSON 返回和内存释放
example/harmonyApp/.../napi_init.cppN-API 导出和参数检查
example/harmonyApp/.../Index.etsArkUI 拖拽页面
README.OpenHarmony_CN.md适配文档、接口对照和验收记录

4.3 关键 API 对照

层次API作用
KMPReorderEngine.onDragStart命中测试并冻结手指锚点
KMPReorderEngine.onDrag累积增量并执行目标判定
KMPReorderEngine.onTick自动滚动循环重估
KMPReorderEngine.onDragEnd/Cancelled返回 onDragEnd(start, end) 对
NativeReorderableStep单入口 JSON 请求/响应
N-APIreorderableStep向 ArkTS 暴露引擎
ArkTSapplyDragEffect应用平移、交换和滚动

上游 Compose API 与引擎字段的完整映射:

上游 API引擎/N-API 字段
detectReorderAfterLongPressLongPress 500ms → action:start
Modifier.reorderable(state)Pan 增量 → action:drag 循环
draggingItemLeft/TopoffsetLeft/offsetTop
onMove(from, to)swapFrom/swapTo
scrollChannel 自动滚动autoScroll + action:tick
onDragEnd(startIndex, endIndex)end 响应的 startIndex/currentIndex
DragCancelledAnimationcancel + 宿主回弹动画
visibleItemsInfoitems 布局快照

4.4 ArkTS 与 Kotlin 的边界

const fx: EngineEffect = JSON.parse(
  reorderableStep(JSON.stringify(req))) as EngineEffect;
fun step(request: String): DragEffect // Bridge.kt 内分发到引擎

两者之间只传输 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构;交换判定、打分和滚动速度全部产自 Kotlin 侧。

五、关键决策说明

决策 1:去 Compose 化并隔离 composeMain

上游 API 全部是 Compose 类型,且 forEachGesture 等手势 API 在新版 Compose 已删除,跨版本混搭不现实。把 Compose 代码移入未注册的 composeMain 源集,既让 ohos 编译通过,又保留上游文件供工具链对齐后原样接回。

决策 2:双 ABI 目标 ohosArm64 + ohosX64

真机是 arm64,DevEco 模拟器是 x86_64。只出 arm64 会让模拟器安装报 9568347,双目标让两条验收路径都可用。

决策 3:引擎放在库模块而非 Demo

ReorderEngine 位于 :reorderable 的 commonMain,是库的 OpenHarmony 交付物本体;example/nativeApp 只做桥接。这样 JVM 目标可以交叉验证同一份引擎,后续 CMP 接回时也复用同一实现。

决策 4:JSON 作为跨语言数据契约,手写 MiniJson

JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加字段而不暴露内部对象布局。手写解析器避免为动态库引入 kotlinx.serialization 的额外 KN 依赖。

决策 5:ArkUI 作为验收宿主,引擎保留 CMP 行为契约

当前页面验证 OpenHarmony Native 链路与交互语义;CMP 工具链对齐后,composeMain 接回并直接消费同一引擎,重排手感与 Android/桌面一致。

六、测试与验证

6.1 测试环境

本次验证使用 macOS(Homebrew OpenJDK 21)、Gradle 8.14.1、DevEco Studio 5.1.0 Release(SDK 5.1.0(18)、HarmonyOS ARM64 Native SDK)和华为 HarmonyOS 真机(ARM64)。

6.2 编译与符号验证

./gradlew :reorderable:compileKotlinJvm          # BUILD SUCCESSFUL
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 \
  :example:nativeApp:linkDebugSharedOhosX64      # BUILD SUCCESSFUL
$ llvm-nm -D entry/libs/arm64-v8a/libohosreorderable.so
000000000011d19c T ReorderableStep
000000000011d8dc T libohosreorderable_symbols

6.3 HAP 验证

CompileArkTS: finished
PackageHap: finished
SignHap: finished
BUILD SUCCESSFUL in 981 ms

签名 HAP 内确认双 ABI 三件套齐全(libohosreorderable.so + libreorderable_napi.so + libc++_shared.so)。

6.4 功能用例

  1. 列表 Tab 长按条目 500ms,条目浮起(放大 + 阴影 + 置顶);
  2. 拖动跨越相邻条目,数据立即重排,被拖条目跟随移动;
  3. 拖到列表上/下边缘,自动滚动启动并缓动加速(约 1.5s 到最大速度);
  4. 松手落位,状态行显示 end idx=起点→终点;
  5. 拖动被打断(cancel),条目 200ms 回弹到原槽位;
  6. 网格 Tab 重复以上流程,验证 2 列交换;「重置」恢复初始顺序。

6.5 验证结论

Native 动态库、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在华为 HarmonyOS 真机上成功安装并进入前台,长按拾起、拖动交换、边缘自动滚动和取消回弹全部由 Kotlin 引擎驱动,状态行实时展示引擎响应。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/ComposeReorderable.git
cd ComposeReorderable
export JAVA_HOME="/path/to/jdk-21"
./gradlew :reorderable:compileKotlinJvm
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 \
  :example:nativeApp:linkDebugSharedOhosX64
# DevEco 打开 example/harmonyApp,自动签名后 assembleHap
hdc -t <设备序列号> install -r \
  example/harmonyApp/entry/build/default/outputs/default/entry-default-signed.hap

八、遗留问题与改进方向

8.1 踩坑复盘

  1. eazytec Maven 仓库必须放在 settings.gradle.kts 列表最前,否则 ohosArm64 目标 unresolved;
  2. 只改 ArkTS 页面不算 KMP 适配,必须真正编译 ohosArm64 动态库并让页面消费真实引擎结果;
  3. @CName 业务符号 ArkTS 拿不到,必须 CMake 再包一层 N-API;
  4. KN so 旁边必须成套携带 libc++_shared.so 并设好 $ORIGIN rpath,缺件安装报 9568347;
  5. ArkTS SDK 版本差异会咬人:panDirection 已更名 direction、scrollable 在 List/Grid 上不存在(改用 enableScrollInteraction)、GestureEvent.globalX/globalY 移到了 fingerList 元素上;
  6. 所有 Ohos*Test 任务需在根构建禁用,ohos 测试目标不可用;
  7. 签名工程必须与源码分离,证书不能进入 AtomGit。

8.2 已知问题

  • composeMain 中的上游 Compose 实现尚未在鸿蒙编译,等待社区 CMP 工具链与 Kotlin 2.2.x 对齐;
  • 本 fork 移除了 maven publishing,暂以 project 依赖直连消费;
  • 上游 canDragOver 过滤与首项滚动修正属 LazyList 专属语义,ArkUI 宿主由自身滚动体系承担;
  • 非均匀尺寸/多列网格的布局快照正确性由宿主负责上报,引擎不感知 ArkUI 布局细节;
  • JSON 由桥接层手动生成,生产业务可替换为序列化库。

8.3 未来优化方向

  • 社区 CMP 工具链就绪后把 composeMain 接回正式源集,在 Compose UI 中直接消费引擎;
  • 恢复 maven publishing,发布 -ohos 变体到社区仓库供 CMP 工程依赖;
  • 增加 canDragOver 等价过滤字段与横向列表/瀑布流验收用例;
  • 在 CI 中加入 Native 链接、符号检查(llvm-nm)和 HAP 构建;
  • 补充不同设备 ABI、API 版本和滚动行为的验收矩阵。

九、总结

9.1 核心难点回顾

KMP commonMain 重排引擎(逐行移植上游算法)
    → ohosArm64/ohosX64 Kotlin/Native
    → C ABI(ReorderableStep)
    → C++ N-API(libreorderable_napi.so)
    → ArkTS JSON 解析 + 手势采集
    → ArkUI 真机页面(交换/平移/自动滚动)

9.2 封装层次

  • KMP 层:提供目标判定、交换打分、自动滚动缓动和会话状态机;
  • Native 层:生成双 ABI 动态库并通过单个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:采集长按/拖动手势、上报布局快照、应用交换与滚动;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 适配 UI 库先拆层:与 UI 无关的决策算法进 commonMain,Compose 壳隔离保留,两边的验收互不阻塞;
  2. 用 JSON 和单个 C ABI 代替跨语言对象传递,并明确字符串释放责任;
  3. 分别记录编译、链接、符号、N-API、HAP 和真机行为,避免把编译成功误认为设备链路可用。

9.4 适配成果

当前适配已完成:

  • :reorderable 的 ohosArm64 + ohosX64 双目标构建,上游 9 个 Compose 文件以纯重命名隔离到 composeMain;
  • 逐行移植上游判定算法的纯 Kotlin 重排引擎(List/Grid 通用);
  • @CName("ReorderableStep") C ABI、手写 MiniJson 和 CMake NAPI 包装;
  • DevEco Stage ArkUI 双 Tab 验收页面(长按拾起、交换、边缘自动滚动、取消回弹、状态行);
  • HAP 构建、自动签名、设备安装和华为 HarmonyOS 真机验证;
  • 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐