本文记录 kotlinx.collections.immutable 适配 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64 目标、持久化集合能力、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 功能总览页面、未签名 HAP、调试签名和真机运行。

本次适配复用库原有的 commonMain 集合接口和实现,只新增 OpenHarmony ARM64 编译目标及验证宿主。ArkTS 页面通过 N-API 调用 Kotlin/Native 生成的真实集合结果,不在页面中重新实现一套集合逻辑。

项目地址: AtomGit/oh-tpc/kotlinx.collections.immutable

上游仓库: Kotlin/kotlinx.collections.immutable

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

kotlinx.collections.immutable 是 Kotlin 官方生态中的多平台集合库,提供不可变集合接口和持久化集合实现。标准库的 List、Set、Map 主要表达“调用方不能通过当前引用修改”,但不保证底层对象没有其他可变引用;本库通过持久化数据结构让集合快照可以安全共享。

原工程已经覆盖 JVM、JS、Wasm 和 Kotlin/Native 多个平台,但没有 OpenHarmony ohosArm64 目标。只修改 ArkTS 页面或只把标准集合转换成 JSON,都不能证明这份 KMP 集合实现真正运行在鸿蒙设备上。因此适配需要解决以下问题:

障碍具体问题
目标缺失核心 KMP 模块没有 ohosArm64(),无法生成 OpenHarmony KLIB。
工具链不一致Kotlin/Native、Gradle、OpenHarmony LLVM、JDK 和 Native SDK 必须匹配。
跨语言边界ArkTS 不能直接持有 Kotlin PersistentList、PersistentSet 或 PersistentMap,必须通过 C ABI、C++ N-API 和 JSON。
数据语义容易被误解示例需要同时展示 immutable 接口、persistent 实现、hash 工厂、转换函数和批量修改。
生命周期管理Native 返回的 C 字符串必须由明确的 Free 函数释放,避免页面重复读取造成内存泄漏。
交付链路复杂动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验收。

因此,本项目把适配边界放在三个地方:KMP 目标配置、Kotlin/Native C ABI/N-API 桥接层和 ArkUI 展示层。集合的创建、更新、结构共享和检查仍由 Kotlin 代码完成,ArkTS 只负责调用和展示。

1.2 库提供的能力

本库不是一个只提供 listOf 的工具类,而是一套完整的不可变和持久化集合 API:

能力主要 API说明
不可变接口ImmutableCollection、ImmutableList、ImmutableSet、ImmutableMap提供稳定、只读的集合契约。
持久化接口PersistentCollection、PersistentList、PersistentSet、PersistentMap修改操作返回新集合,旧集合保持不变。
有序工厂persistentListOf、persistentSetOf、persistentMapOf默认保留集合元素的插入迭代顺序。
哈希工厂persistentHashSetOf、persistentHashMapOf更注重时间和空间效率,不保证迭代顺序。
immutable 工厂immutableListOf、immutableSetOf、immutableMapOf提供语义明确的不可变集合入口。
转换函数toImmutable*、toPersistent*、toPersistentHash*把标准集合、数组、序列转换成库类型。
快照更新adding、removing、putting、replacingAt返回新快照,尽量复用旧集合结构。
批量修改builder、build、mutate在临时可变构建器中集中修改,再生成一个持久化结果。
适配器ImmutableListAdapter、ImmutableSetAdapter、ImmutableMapAdapter把已有只读集合包装成 immutable 接口。

1.3 实现适配

维度要求
代码复用集合接口、Trie/Vector/Map 实现、转换函数和自检逻辑全部来自 commonMain。
平台目标为核心模块增加 ohosArm64,生成 OpenHarmony KLIB 并链接 libimmutablecollections.so。
桥接稳定只导出目录、索引读取、随机读取、自检和释放四类 C ABI 函数。
UI 完整页面一次性列出 12 类主要功能,并显示 Kotlin 真实计算得到的结果。
可测试JVM 测试、Native 编译、动态库链接、HAP 构建和设备启动分别验收。
签名安全源码只保留签名入口,证书、profile 和密钥材料由开发者本机配置。
仓库规范项目文档、文章、效果图和源码链接统一使用 AtomGit。

示例页面是独立的真机验收宿主。业务项目仍然可以只依赖 kotlinx.collections.immutable,按照自身 Compose/CMP 或 ArkUI 状态管理方式使用集合。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点集合 API、KMP 模块和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:功能总览模型   ── 用 Kotlin 构造 12 类真实集合能力和统一 JSON 结构
第 4 阶段:原生桥接       ── Kotlin/Native C ABI、C++ N-API、参数检查和内存释放
第 5 阶段:ArkUI 页面     ── 展示 immutable/persistent API、转换、Builder 和 mutate
第 6 阶段:构建与验证     ── JVM 测试、Native 链接、未签名 HAP、调试签名和设备运行

每个阶段都使用真实产物作为下一阶段输入:先验证共享 Kotlin 逻辑,再把同一份代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后在鸿蒙设备上启动页面。

三、逐步实现过程

第 1 阶段:项目初始化

1.1 盘点公共 API 和工程边界

项目采用独立核心库、Native 宿主和 DevEco 工程三层结构:

kotlinx.collections.immutable/
├── core/                         KMP 核心库
│   ├── commonMain/src/            immutable/persistent API 和实现
│   ├── commonTest/src/            跨平台契约测试
│   └── build.gradle.kts           KMP 目标和发布配置
├── example/
│   ├── shared/                    JVM/OHOS 示例和功能检查
│   ├── nativeApp/                 ohosArm64 动态库和 C ABI
│   └── ohosApp/                   DevEco Stage、N-API 和 ArkUI 页面
├── scripts/                      构建、签名工程和 HAP 脚本
└── docs/openharmony/              构建与设备验收记录

example 是独立 Gradle 工程,DevEco 工程不是根项目的 Kotlin 子模块。这样 Gradle 可以单独发布和链接 Native,Hvigor 可以单独执行资源、CMake、ArkTS 和 HAP 任务。

1.2 固定工具链和版本矩阵
项目配置用途
Kotlin Multiplatform2.2.21-1.0.0支持参考工程使用的 OpenHarmony Native 目标。
JDK21Gradle、Kotlin 编译和 Native 任务。
OpenHarmony 目标ohosArm64ARM64 真机 KLIB 和动态库。
DevEco productdefaultStage HAP 构建。
DevEco Studio5.1.0 ReleaseIDE 版本,负责工程同步、HAP 构建与签名
ABIarm64-v8aHAP 原生库目录。
C++ 桥接N-API将 C 字符串转换为 ArkTS 字符串。

执行脚本前可以显式选择 JDK 21:

export JAVA_HOME="/path/to/jdk-21"
java -version

在 macOS 上,仓库脚本在 JAVA_HOME 未设置时会自动尝试使用 Homebrew 的 /opt/homebrew/opt/openjdk@21。

1.3 创建功能总览页面

功能总览不是随意填充几条字符串,而是由 Kotlin 侧建立真实目录:

PersistentList          persistentListOf + adding
PersistentSet           persistentSetOf + 去重
PersistentMap           persistentMapOf + putting
PersistentHashSet       persistentHashSetOf
PersistentHashMap       persistentHashMapOf
Immutable factories     immutableListOf / immutableSetOf / immutableMapOf
Conversions             toImmutable* / toPersistent*
+ and - operators       持久化加减运算
Builder                 builder() / build()
Batch mutate            mutate { }
Immutable adapters      三类 Adapter
Type interfaces         immutable/persistent 接口体系

ArkUI 页面直接渲染这个目录,因此用户打开 HAP 就能看到库的主要功能边界,而不是只看到一个随机列表。

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

2.1 加入 ohosArm64 目标

核心模块在保留现有目标的同时加入:

kotlin {
    applyDefaultHierarchyTemplate()
    explicitApi()

    linuxX64()
    linuxArm64()
    ohosArm64()
    macosX64()
    macosArm64()
    iosArm64()
    // 其他原有目标保持不变
}

ohosArm64 与 native 层级共享 commonMain 的集合实现,不增加一套鸿蒙专属 List、Set 或 Map。

2.2 Native focused build 的作用

example/nativeApp 构建一个受 linker map 约束的 shared library:

kotlin {
    ohosArm64 {
        binaries.sharedLib {
            baseName = "immutablecollections"
            linkerOpts("--entry=0")
            linkerOpts("--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}")
            linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
        }
    }
}

导出的符号为:

ImmutableCollectionsCatalog
ImmutableCollectionsGet
ImmutableCollectionsRandom
ImmutableCollectionsRunChecks
ImmutableCollectionsFree

链接器只开放这些明确入口,Kotlin/Native 内部实现不会变成不受控的 ABI。

2.3 配置独立消费工程

example/shared 通过本地 Maven 消费适配后的库:

kotlin {
    jvm()
    ohosArm64()
    sourceSets {
        commonMain.dependencies {
            api("org.jetbrains.kotlinx:kotlinx-collections-immutable:${property("immutableCollectionsVersion")}")
        }
    }
}

根工程先执行 publishToMavenLocal,示例工程再解析相同版本,确保示例不是偷偷依赖根项目源码。

2.4 通过构建产物消费共享库

prepareOhos 在 Native 链接成功后复制:

from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
    include("libimmutablecollections.so")
    into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
    include("libimmutablecollections_api.h")
    into("src/main/cpp/include")
}

动态库和头文件属于构建产物,源码仓库不提交 DevEco/CMake 中间目录;开发者执行脚本即可重新生成。

第 3 阶段:功能总览模型

3.1 为什么需要统一 JSON 契约

ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin 泛型集合对象,因此边界使用 UTF-8 JSON:

PersistentList/Set/Map 实例
        ↓ Kotlin catalog()
ImmutableCollectionsExample
        ↓ Kotlin/Native toJson()
C ABI char*
        ↓ C++ napi_create_string_utf8()
ArkTS JSON.parse()
        ↓
功能总览卡片

页面只消费 JSON,不复制集合创建和更新逻辑。JVM 测试与设备页面使用同一份 catalog 和 runChecks。

3.2 示例数据结构
public data class ImmutableCollectionsExample(
    val id: String,
    val kind: String,
    val value: String,
    val size: Int,
)

kind 表示功能类别,value 保存可读的 API 示例和运行结果,size 用于页面右侧的数量提示。

3.3 真实功能目录
public fun catalog(): List<ImmutableCollectionsExample> = listOf(
    example("list", "PersistentList", "persistentListOf(1, 2, 3).adding(4)", 4),
    example("set", "PersistentSet", "persistentSetOf(\"a\", \"a\")", 1),
    example("map", "PersistentMap", "persistentMapOf(...).putting(...)", 1),
    example("hash-set", "PersistentHashSet", "persistentHashSetOf(...) ", 2),
    example("hash-map", "PersistentHashMap", "persistentHashMapOf(...) ", 1),
    // 转换、运算符、Builder、mutate、Adapter 和接口继续列出
)

功能目录的展示文字和自检结果由共享 Kotlin 生成,ArkTS 不对这些字符串做业务判断。

3.4 自检和错误边界

示例自检覆盖:

  1. persistent list 的原值保持不变;
  2. persistent set 会去重;
  3. persistent map 能按 key 返回新值;
  4. hash set/map 工厂可用;
  5. 标准集合可以转换为 persistent 集合;
  6. + 和 - 返回持久化结果;
  7. Builder 可以生成快照;
  8. mutate 可以批量修改;
  9. 目录覆盖全部主要功能;
  10. N-API 可以返回 JSON。

Native 异常统一转换为 {"error":"..."},C++ 在创建 ArkTS 字符串后立即调用 ImmutableCollectionsFree。

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

4.1 Kotlin/Native 对象不能直接交给 ArkTS

PersistentList、PersistentSet 和 PersistentMap 属于 Kotlin 运行时对象,不能把对象地址直接当成 JavaScript 对象使用。最终采用三层桥接:

ArkTS
  │ JSON string
  ▼
C++ N-API entry
  │ const char*
  ▼
Kotlin/Native C ABI
  │ persistent collection implementation
  ▼
UTF-8 JSON + explicit free
4.2 方案对比
方案优点缺点选用
直接导出 Kotlin 集合对象代码少ABI、泛型和生命周期不可控❌
只导出集合数量实现简单看不到真实 API 结果❌
C ABI + JSON边界清晰、易调试、易扩展有一次序列化开销✅
在 ArkTS 重写集合逻辑页面简单KMP 与 ArkTS 容易产生两套实现❌
4.3 Kotlin/Native 导出函数
@CName("ImmutableCollectionsCatalog")
public fun catalogNative(): CPointer<ByteVar> = response {
    ImmutableCollectionsExamples.catalog()
        .joinToString(prefix = "[", postfix = "]") { it.toJson() }
}

@CName("ImmutableCollectionsGet")
public fun getNative(index: Int): CPointer<ByteVar> = response {
    ImmutableCollectionsExamples.get(index).toJson()
}

@CName("ImmutableCollectionsRunChecks")
public fun checksNative(): CPointer<ByteVar> = response { ... }

@CName("ImmutableCollectionsFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
    if (pointer != null) nativeHeap.free(pointer.rawValue)
}

返回值使用 Native heap 分配的、以 0 结尾的 C 字符串。每一块缓冲区都由 Free 函数释放。

4.4 C++ N-API 方法分发

C++ 注册目录、读取、随机读取和自检方法:

napi_property_descriptor methods[] = {
    {"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
        napi_default, nullptr},
    {"getImmutableCollections", nullptr, GetImmutableCollections,
        nullptr, nullptr, nullptr, napi_default, nullptr},
    {"randomImmutableCollections", nullptr, RandomImmutableCollections,
        nullptr, nullptr, nullptr, napi_default, nullptr},
    {"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
        napi_default, nullptr},
};

getImmutableCollections 会检查参数数量、整数类型和非负范围,然后调用 ImmutableCollectionsGet。C++ 完成字符串转换后立即释放 Native 缓冲区。

4.5 CMake 加载动态库
add_library(immutablecollections SHARED IMPORTED)
set_target_properties(immutablecollections PROPERTIES
  IMPORTED_LOCATION
  "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libimmutablecollections.so")

add_library(entry SHARED napi_init.cpp)
 target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
 target_link_libraries(entry PRIVATE immutablecollections libace_napi.z.so)

第 5 阶段:ArkUI 页面

5.1 ArkTS 客户端
export interface ImmutableCollectionsExample {
  id: string;
  kind: string;
  value: string;
  size: number;
}

export function catalog(): ImmutableCollectionsExample[] {
  return JSON.parse(immutablecollectionsNative.getCatalog()) as ImmutableCollectionsExample[];
}

ArkTS 页面只把 JSON 映射为界面模型,不创建 Kotlin 集合,也不重新实现 adding、putting 或 mutate。

5.2 功能总览布局
标题区          immutablecollections / NATIVE READY
说明区          完整功能总览 · KMP / OpenHarmony
卡片区          12 类公开 API 和真实示例结果
状态区          10/10 公共检查通过
底部说明        Kotlin/Native → N-API → ArkTS

每张卡片显示功能名称、调用方式、结果和数量;用户打开应用即可直观看到这个三方库的功能边界。

5.3 页面与库的职责分离

页面负责:

  • 调用 getCatalog;
  • 解析 JSON;
  • 显示功能卡片;
  • 显示 Native 自检状态;
  • 处理错误文本和重新读取。

Kotlin 负责:

  • 创建真实的 persistent/immutable/hash 集合;
  • 执行转换、运算符、Builder 和 mutate;
  • 生成功能描述和 JSON;
  • 执行公共检查。

第 6 阶段:构建与验证

6.1 OpenHarmony Native 准备
./scripts/build-openharmony.sh

脚本会完成本地发布、示例测试、ohosArm64 编译、Native 动态库链接和头文件复制。

6.2 构建未签名 HAP
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  --mode module -p module=entry@default -p product=default \
  -p buildMode=debug clean assembleHap --no-daemon

未签名产物:

example/ohosApp/entry/build/default/outputs/default/entry-default-unsigned.hap
6.3 签名工程和调试包

源码不保存证书和私钥。复制独立签名工程:

./scripts/prepare-signing-project.py /absolute/path/to/signing-project

配置 DevEco 签名后构建:

./scripts/build-hap.sh /absolute/path/to/signing-project

没有签名文件的 HAP 不能安装到真实鸿蒙设备;开发调试可以使用 DevEco 默认调试签名,正式发布必须使用开发者自己的证书和 profile。

四、完整代码对照

4.1 整体架构

ArkUI 页面
    │ getCatalog / runChecks
    ▼
C++ N-API(libentry.so)
    │ const char* JSON
    ▼
Kotlin/Native C ABI
    │
libimmutablecollections.so
    │
commonMain persistent collections

4.2 文件清单

文件职责
core/commonMain/src/ImmutableList.ktimmutable/persistent list 接口
core/commonMain/src/ImmutableSet.ktimmutable/persistent set 接口
core/commonMain/src/ImmutableMap.ktimmutable/persistent map 接口
core/commonMain/src/extensions.kt工厂、转换、运算符和 mutate
example/shared/.../ImmutableCollectionsExamples.kt功能目录、真实示例和自检
example/nativeApp/.../NativeBridge.ktC ABI、JSON 返回和内存释放
example/ohosApp/.../ImmutableCollectionsClient.etsN-API JSON 解析
example/ohosApp/.../Index.ets功能总览页面
example/ohosApp/entry/src/main/cpp/napi_init.cppN-API 导出和参数检查
scripts/build-openharmony.sh测试、Native 链接和产物复制
scripts/build-hap.sh签名工程 HAP 构建
docs/openharmony/VALIDATION.md构建和设备验收记录

4.3 关键 API 对照

层次API作用
KotlinpersistentListOf创建持久化列表
KotlinpersistentSetOf创建有序持久化集合
KotlinpersistentMapOf创建有序持久化映射
KotlinpersistentHashSetOf创建哈希持久化集合
KotlinpersistentHashMapOf创建哈希持久化映射
Kotlinadding / putting返回修改后的新集合
KotlintoPersistent*转换标准集合
Kotlinbuilder / mutate批量修改并生成快照
NativeImmutableCollectionsCatalog返回功能目录 JSON
NativeImmutableCollectionsGet返回单项 JSON
NativeImmutableCollectionsFree释放 Native 字符串
N-APIgetCatalogArkTS 获取目录
ArkTSForEach展示 12 类功能卡片

五、关键决策说明

决策 1:把 ohosArm64 加入公共构建约定

只有真正链接 ohosArm64 动态库,才能证明共享集合实现进入 OpenHarmony 运行时,而不是停留在页面字符串或 JVM 测试。

决策 2:示例通过 Maven 产物消费库

example/shared 解析本地发布产物,ohosApp 单独接收 .so 和头文件,避免根工程通过但 DevEco 无法打包。

决策 3:JSON 作为跨语言数据契约

JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清晰可调试,同时避免暴露 Kotlin 集合内部对象布局。

决策 4:页面展示真实 API 结果

功能总览中的每一项都对应 Kotlin 真实工厂函数或操作,页面只展示结果,避免把“演示 UI”误认为“库的实现”。

决策 5:构建产物和源码分离

.so、HAP、CMake 中间目录和 DevEco 缓存不提交 AtomGit;任何开发者都可以使用自己的 SDK 重新生成。

六、测试与验证

6.1 测试命令

# OpenHarmony 目标、Native 动态库和示例测试
./scripts/build-openharmony.sh

# 单独运行共享示例测试
(cd example && ./gradlew :shared:jvmTest -Pkotlin_Werror_override=disable)

# DevEco 未签名 HAP
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  --mode module -p module=entry@default -p product=default \
  -p buildMode=debug clean assembleHap --no-daemon

6.2 验证结果

本次适配已经验证:

shared:jvmTest                         PASSED
compileKotlinOhosArm64                PASSED
linkDebugSharedOhosArm64              PASSED
nativeApp:prepareOhos                  PASSED
DevEco CompileArkTS                    PASSED
DevEco PackageHap                     PASSED
entry-default-unsigned.hap             GENERATED

6.3 真机启动

hdc list targets
hdc -t <设备序列号> install -r \
  example/ohosApp/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b kotlinx.collections.immutable.sample

未签名包用于查看产物和 DevEco 构建结果;真机安装需要调试签名或正式签名包。

七、运行效果

7.1 功能总览真机效果图

在这里插入图片描述

截图中可以看到:

  • NATIVE READY 表示页面已经加载 Native 动态库;
  • 页面标题明确显示 Kotlin Multiplatform/OpenHarmony;
  • PersistentList、PersistentSet、PersistentMap 直接展示真实调用;
  • PersistentHashSet 和 PersistentHashMap 展示哈希实现;
  • Immutable factories 和 Conversions 展示工厂与转换能力;
  • 下方继续展示 +/- operators、Builder、mutate、Adapter 和接口体系;
  • 页面底部显示 Kotlin/Native、N-API 和 ArkTS 的完整调用链。

7.2 效果说明

页面不是一个普通的静态说明页。每个卡片由 Kotlin catalog() 生成,再由 Native C ABI 返回 JSON,最后由 ArkTS ForEach 渲染。重新读取会重新执行 Native 目录调用和公共自检。

7.3 命令速查

# 构建 Native 和生成头文件
./scripts/build-openharmony.sh

# 构建未签名 HAP
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw \
  --mode module -p module=entry@default -p product=default \
  -p buildMode=debug clean assembleHap --no-daemon

# 构建签名 HAP
./scripts/build-hap.sh /absolute/path/to/signing-project

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配:必须把 commonMain 集合实现真正编译成 ohosArm64 动态库。
  2. 不可变接口和持久化实现要分开说明:Immutable* 表达稳定契约,Persistent* 提供返回新集合的修改操作。
  3. N-API 不负责集合业务:C++ 只做参数检查、字符串转换和释放,集合语义放在 Kotlin。
  4. 示例数据不能替代功能目录:页面应该展示工厂、转换、运算符、Builder 和 mutate 等完整能力。
  5. JDK 版本必须匹配:OpenHarmony Kotlin/Native 构建使用 JDK 21。
  6. 未签名 HAP 不能直接安装:设备安装需要 DevEco 调试签名或开发者自己的正式签名。

8.2 已知问题

  • OpenHarmony ohosArm64 主要面向 ARM64 设备;
  • 老版本 Kotlin/Native 编译器可能提示上游返回值检查警告;
  • Adapter 不会冻结底层标准集合,调用方仍需保证底层集合没有被其他引用修改;
  • 功能总览用于验收主要 API,不替代完整 API 文档和契约测试;
  • HAP 签名证书和 profile 依赖开发者本机环境。

8.3 未来优化方向

  • 增加 Compose Multiplatform 业务页面,展示集合在状态管理中的实际用法;
  • 增加更多泛型、嵌套集合和大数据量性能样例;
  • 在持续集成中加入 ohosArm64 链接和未签名 HAP 构建;
  • 增加 OpenHarmony 设备上的自动化启动和截图验收;
  • 为 AtomGit 发布流程增加版本化 Native 产物和 API 兼容检查。

九、总结

9.1 核心难点回顾

本次适配真正需要处理的不是“在页面里显示一个列表”,而是一条完整跨端链路:

KMP commonMain 集合实现
    → ohosArm64 Kotlin/Native
    → libimmutablecollections.so
    → C ABI
    → C++ N-API
    → ArkTS JSON
    → ArkUI 功能总览

9.2 封装层次

  • KMP 层:定义 immutable/persistent 接口、工厂和结构共享实现;
  • Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态和功能卡片;
  • DevEco 层:完成 CMake、ArkTS、HAP、签名和设备启动。

9.3 三条经验

  1. 先让真实集合 API 在 JVM 和 Native 通过,再接入 ArkUI;
  2. 用 JSON 和少量 C ABI 代替跨语言对象传递;
  3. 把自动测试、HAP 构建和真机安装分别记录,避免把“页面显示成功”误认为“库适配完成”。

9.4 适配成果

当前 kotlinx.collections.immutable 已完成:

  • ohosArm64 Kotlin/Native 目标;
  • persistent/immutable/hash 集合在 OpenHarmony ARM64 上的编译;
  • Kotlin/Native + C ABI + N-API 桥接;
  • 12 类主要公开功能的 ArkUI 总览页面;
  • JVM 测试、Native 链接、未签名 HAP 构建;
  • DevEco 调试签名包的设备安装与启动;
  • AtomGit 项目文档、效果图和 OpenHarmony 验收记录。

适配过程 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐