开源鸿蒙平台 KMP_CMP 三方库「AtomicFU」适配全流程
本文记录
kotlinx-atomicfu接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机宿主、HAP 构建和验收。本次适配复用 AtomicFU 的原子 Int、原子引用、原子 Boolean 以及 Native 锁实现,再由 ArkTS 调用 N-API 展示不可变快照。这样验证的是同一份 KMP 代码在 OpenHarmony ARM64 设备上的运行结果,而不是重新写一套只在页面里生效的计数器逻辑。
项目地址: AtomGit/oh-tpc/kotlinx-atomicfu
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
AtomicFU 是 Kotlin Multiplatform 原子操作库。它把原子引用、原子整数、原子长整数、原子布尔值以及多平台锁封装成统一 API,业务代码可以在 JVM、Native、JS、Wasm 等目标上复用。
如果只把页面重新写成 ArkTS,页面可能会显示一个会变化的数字,却无法证明共享 Kotlin 原子实现、Kotlin/Native 动态库和实际 OpenHarmony 宿主已经连通。适配需要解决下面几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块原先没有 OpenHarmony 目标,必须增加 ohosArm64() 才能生成 OpenHarmony KLIB 和动态库。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。 |
| 实现层级复杂 | AtomicFU 的 Native 代码包含原子类型、线程等待和 POSIX 同步实现,不能只复制一个页面示例。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin data class,必须经过 C ABI、C++ N-API 和 JSON。 |
| 内存生命周期 | Kotlin/Native 返回的字符串必须由明确的释放函数回收,不能把 Native 指针长期交给 ArkTS。 |
| 交付链路复杂 | Native 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:Kotlin/Native 目标配置、C ABI/N-API 桥接层和 ArkUI 示例层。原子语义仍由 AtomicFU 的 KMP 代码维护,ArkTS 只负责调用、状态展示和用户操作。
1.2 库提供的能力
kotlinx-atomicfu 公共模块提供以下能力:
atomic(initial):创建原子引用、原子 Int、原子 Long 或原子 Boolean;AtomicRef:提供value、compareAndSet、getAndSet和更新操作;AtomicInt:提供incrementAndGet、getAndIncrement、compareAndSet、addAndGet等操作;AtomicLong和AtomicBoolean:提供对应的数值和布尔原子操作;SynchronousMutex:在多平台上提供阻塞式互斥锁;- Native parking 和 POSIX 条件变量实现:支持 Native 端等待、唤醒和超时;
- JVM AtomicFU transformer:继续保留原有 JVM 字节码转换行为。
本次 OpenHarmony 示例使用三个公开原子值:
| 原子值 | 初始值 | 页面用途 |
|---|---|---|
AtomicInt | 0 | 统计原子递增次数。 |
AtomicRef<String> | "ready" | 展示引用更新是否经过共享代码。 |
AtomicBoolean | true | 展示原子布尔值读取。 |
页面通过 AtomicSnapshot 对外展示当前状态:
{"counter":1,"label":"updated","enabled":true}
共享检查包含八项:原子 Int 递增、原子引用更新、原子 Boolean 读取、compareAndSet、getAndSet、公共 API 平台无关、OpenHarmony 使用 Native 实现以及桥接快照不可变。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | AtomicFU 原有原子类型、Native 锁、KMP API 和测试继续复用。 |
| 平台目标 | 为 atomicfu 模块加入 ohosArm64,生成 libatomicfu.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持当前快照、原子递增、重置和共享检查展示。 |
| 可测试 | JVM 测试、Native 编译、HAP 构建和设备安装分别验收。 |
| 签名安全 | 源码只保留签名配置入口,证书和密钥材料由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的真机验收宿主,公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以直接复用 AtomicFU API,再自行决定如何把快照呈现到界面。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 KMP 模块、AtomicFU API 和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、独立 example 工程和 Native 构建任务
第 3 阶段:原子状态与快照 ── 建立 AtomicExamples、AtomicSnapshot 和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、内存释放和错误返回
第 5 阶段:ArkUI 宿主封装 ── ArkTS 调用 N-API、页面状态和操作生命周期
第 6 阶段:示例与验证 ── HAP 构建、签名、设备安装和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享原子模型,再把相同代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后构建 HAP 观察页面状态。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 CMP 工程一致的分层:
atomicfu/ KMP 原子类型、Native 锁和公共 API
atomicfu-transformer/ JVM 字节码转换器
atomicfu-gradle-plugin/ Gradle 插件
atomicfu-maven-plugin/ Maven 插件
example/shared/ 示例门面和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP 和签名工程辅助脚本
docs/openharmony/ 验收说明和效果图
example 是独立 Gradle 工程,不把 DevEco 工程当作 Kotlin 子模块。它通过 composite build 使用当前仓库的 atomicfu 项目,这样 Native 示例不会依赖尚未发布到 Maven 仓库的 OpenHarmony 变体。
1.2 固定工具链和版本矩阵
本项目使用以下版本约定:
| 项目 | 配置 | 用途 |
|---|---|---|
| Kotlin Multiplatform | 2.2.21-1.0.0 | JVM、Kotlin/Native 和 KLIB |
| JDK | 21 | Gradle、Kotlin 编译和 Native 任务 |
| OpenHarmony 目标 | ohosArm64 | ARM64 真机动态库 |
| DevEco product | 6.0.0(20) | 工程兼容和目标版本 |
| ABI | arm64-v8a | HAP 原生库架构 |
| 原子示例版本 | 0.33.0-SNAPSHOT | 当前仓库开发版本 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
设备是否支持某个 OpenHarmony API 要单独验证。API 版本满足要求,只说明工程可以编译和安装,不能替代硬件能力检查。
1.3 创建 OpenHarmony 示例目录
示例页面围绕“当前快照、原子操作和共享检查”组织:
标题区 AtomicFU 工作台 / Kotlin Multiplatform / OpenHarmony
状态区 counter、label、enabled
操作区 原子递增 / 重置并检查
自检区 8/8 原子操作检查通过
“原子递增”按钮调用 Kotlin/Native 动态库;“重置并检查”恢复初始状态并执行共享检查。页面不在 ArkTS 中重新实现 compareAndSet 或计数逻辑。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
公共模块同时保留 JVM 测试和 OpenHarmony Native 目标:
plugins {
id("kotlin-multiplatform-conventions")
id("kotlin-multiplatform-publish-conventions")
}
kotlin {
jvmToolchain(8)
jvm()
ohosArm64()
}
OpenHarmony 目标复用 commonMain 和 Native 源集。由于 AtomicFU 已有 nativeMain、nativeUnixLikeMain 和平台实际实现,本次补充了 ohosArm64Main 的 POSIX 条件变量、互斥锁和线程标识 actual,让原有 Native 锁逻辑可以在 OHOS 编译。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "atomicfu"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出五个 C ABI 符号:
AtomicSnapshot
AtomicIncrement
AtomicReset
AtomicRunChecks
AtomicFree
2.3 配置仓库和独立消费工程
example/settings.gradle.kts 使用 composite build 把 org.jetbrains.kotlinx:atomicfu 替换为当前仓库的 :atomicfu 项目:
includeBuild("..") {
dependencySubstitution {
substitute(module("org.jetbrains.kotlinx:atomicfu"))
.using(project(":atomicfu"))
}
}
这一步的作用是让 example/shared 的公共源码和 example/nativeApp 的 ohosArm64 编译都使用当前适配代码。正式发布时,业务项目仍然可以使用 Maven 制品;本地示例则优先验证源码和目标变体。
2.4 通过构建产物消费共享库
prepareOhos 依赖 linkDebugSharedOhosArm64,把 Native 生成物复制到 DevEco entry 模块:
example/ohosApp/entry/libs/arm64-v8a/libatomicfu.so
example/ohosApp/entry/src/main/cpp/include/libatomicfu_api.h
这两个文件属于本地生成物,源码仓库通过 .gitignore 排除。重新构建 Native 后必须同时更新动态库和头文件,不能只替换其中一个。
第 3 阶段:原子状态与序列化
3.1 为什么需要统一 JSON 契约
Kotlin/Native 的 AtomicFU 对象不能直接作为 ArkTS 值返回。示例定义一个只包含基础类型的快照:
public data class AtomicSnapshot(
val counter: Int,
val label: String,
val enabled: Boolean,
)
JSON 字段固定为 counter、label 和 enabled。ArkTS 只解析这三个字段,不依赖 Kotlin 类名、包名或内部字段布局。
3.2 AtomicExamples 和 AtomicSnapshot
public object AtomicExamples {
private val counter = atomic(0)
private val label = atomic("ready")
private val enabled = atomic(true)
public fun snapshot(): AtomicSnapshot =
AtomicSnapshot(counter.value, label.value, enabled.value)
public fun increment(): AtomicSnapshot {
counter.incrementAndGet()
label.value = "updated"
return snapshot()
}
public fun reset(): AtomicSnapshot {
counter.value = 0
label.value = "ready"
enabled.value = true
return snapshot()
}
}
AtomicExamples 只使用公共 AtomicFU API。页面显示的状态来自同一个对象,不在 ArkTS 中维护第二份计数器。
3.3 原子操作和 JSON
increment() 通过 AtomicInt.incrementAndGet() 更新计数器;label.value = "updated" 通过原子引用写入;enabled.value 通过原子 Boolean 读取。Native 桥接再把快照编码为:
{
"counter": 6,
"label": "updated",
"enabled": true
}
返回数据只包含不可变基础值。即使 ArkTS 保存了快照,也不会保留 Kotlin/Native 对象或指针。
3.4 自检和错误边界
AtomicExamples.runChecks() 覆盖八项检查:
- 原子 Int 可以递增;
- 原子引用可以更新;
- 原子 Boolean 可以读取;
compareAndSet成功更新;getAndSet返回旧值;- 公共 API 不依赖平台类型;
- OpenHarmony 使用 Native AtomicFU 实现;
- 桥接返回不可变快照。
Native 异常会转换为 {"error":"..."},C++ 在创建 ArkTS 字符串后立即调用 AtomicFree。这样页面能显示可读错误,同时不会泄漏 Kotlin/Native 堆内存。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 AtomicSnapshot 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ AtomicExamples
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出整数 | 实现简单 | 页面会重新实现原子状态 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 AtomicFU 逻辑 | 页面调用简单 | KMP 和 ArkTS 逻辑容易分叉 | ❌ |
4.3 Kotlin/Native 导出函数
@CName("AtomicSnapshot")
public fun snapshotNative(): CPointer<ByteVar> =
response { AtomicExamples.snapshot().toJson() }
@CName("AtomicIncrement")
public fun incrementNative(): CPointer<ByteVar> =
response { AtomicExamples.increment().toJson() }
@CName("AtomicReset")
public fun resetNative(): CPointer<ByteVar> =
response { AtomicExamples.reset().toJson() }
@CName("AtomicRunChecks")
public fun checksNative(): CPointer<ByteVar> = response { /* JSON */ }
@CName("AtomicFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
返回值使用 Native heap 分配的、以 0 结尾的 C 字符串。每一块返回缓冲区都由 AtomicFree 释放。
4.4 C++ N-API 方法分发
C++ 注册四个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"snapshot", nullptr, CallSnapshot, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"increment", nullptr, CallIncrement, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"reset", nullptr, CallReset, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, CallChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
每个方法都遵循“调用 Native、创建 ArkTS 字符串、释放 Native 缓冲区”的顺序。示例没有参数型 API,因此 C++ 不需要把动态参数传给 Kotlin;后续新增带参数方法时,应在 C++ 层先检查数量、范围和类型。
4.5 N-API 生命周期
ArkTS increment()
│
▼
AtomicIncrement()
│
▼
napi_create_string_utf8(...)
│
▼
AtomicFree(nativeBuffer)
│
▼
return JS string
C++ 负责完成跨语言字符串转换和 Native 释放,ArkTS 页面不需要知道 Kotlin/Native 的堆实现。
第 5 阶段:ArkUI 宿主封装
5.1 ArkTS 调用 N-API 模块
AtomicFuClient.ets 从 libentry.so 导入原生模块,并把 JSON 解析为显式类型:
import atomicfuNative from 'libentry.so';
export interface AtomicSnapshot {
counter: number;
label: string;
enabled: boolean;
}
function parse<T>(text: string): T {
return JSON.parse(text) as T;
}
export function increment(): AtomicSnapshot {
return parse<AtomicSnapshot>(atomicfuNative.increment());
}
ArkTS 不创建第二个 AtomicFU 实现,只负责把 Native 返回值转换为页面可用的基础类型。
5.2 权限声明
AtomicFU 示例只访问自己的 Native 动态库和内存,不调用传感器、相机、位置或多模态感知 API,因此不需要新增运行时权限。宿主仍然需要在 module.json5 中正确声明 Ability、页面和 ARM64 product 配置。
这与系统能力型插件不同:编译器可以构建 AtomicFU 页面,设备也可以运行页面,不需要额外的权限弹窗。若业务应用在 AtomicFU 之外加入系统能力,权限应由业务宿主单独声明。
5.3 ArkUI 页面状态
Index.ets 保存快照、检查状态和错误文本:
@State private current: AtomicSnapshot = emptySnapshot();
@State private status: string = '正在加载';
@State private errorText: string = '';
页面首次出现时调用 reset() 和 runChecks();点击“原子递增”调用 increment();点击“重置并检查”恢复初始快照并重新运行检查。页面只展示 Native 结果,不在 UI 线程复制原子逻辑。
5.4 页面交互预设
页面提供两个操作按钮:
- 原子递增:调用
AtomicIncrement,计数器递增,label 变为updated; - 重置并检查:调用
AtomicReset,随后调用AtomicRunChecks,状态显示8/8 原子操作检查通过。
操作不依赖设备传感器。这样即使只使用模拟器或暂时没有真机,也可以验证 KMP、Native 和 N-API 链路。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../AtomicExamples.kt
│ └── src/commonTest/.../AtomicExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/.../NativeBridge.kt
│ └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
├── AppScope/
├── entry/src/main/cpp/
│ ├── CMakeLists.txt
│ └── napi_init.cpp
└── entry/src/main/ets/
├── pages/Index.ets
└── atomicfu/AtomicFuClient.ets
shared 验证公共模型,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI 页面和 N-API 模块。三者的边界清晰,任何一层失败都能单独定位。
6.2 原生模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块。ArkTS 使用:
import atomicfuNative from 'libentry.so';
CMake 将 Kotlin/Native 动态库作为 imported library:
add_library(atomicfu SHARED IMPORTED)
set_target_properties(atomicfu PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libatomicfu.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE atomicfu libace_napi.z.so)
6.3 Native 动态库准备
执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本依次执行根模块 JVM 测试、示例测试、linkDebugSharedOhosArm64 和 prepareOhos。成功后生成:
example/ohosApp/entry/libs/arm64-v8a/libatomicfu.so
example/ohosApp/entry/src/main/cpp/include/libatomicfu_api.h
还可以检查 ARM64 ELF 的强依赖:
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
6.4 构建、签名和安装
未配置签名时,可以直接构建未签名 HAP:
DEVECO_HOME=/Applications/DevEco-Studio.app/Contents
export PATH="$DEVECO_HOME/tools/node/bin:$DEVECO_HOME/tools/ohpm/bin:$PATH"
export DEVECO_SDK_HOME="$DEVECO_HOME/sdk"
cd example/ohosApp
"$DEVECO_HOME/tools/ohpm/bin/ohpm" install --all
"$DEVECO_HOME/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
真机安装需要签名 HAP。配置签名后执行:
python3 scripts/prepare-signing-project.py /absolute/path/atomicfu-signing
DEVECO_HOME=/Applications/DevEco-Studio.app/Contents \
scripts/build-hap.sh /absolute/path/atomicfu-signing
签名证书、profile、p12 和密码只保存在本机,不要提交到 AtomGit。
四、完整代码对照
4.1 整体架构
ArkTS AtomicFuClient
│ N-API method
▼
C++ libentry.so
│ C ABI
▼
libatomicfu.so
│ Kotlin/Native
▼
AtomicExamples -> AtomicSnapshot -> JSON
4.2 文件清单
| 文件 | 职责 |
|---|---|
atomicfu/src/nativeMain/... | 原有 Native 原子类型与锁实现 |
atomicfu/src/ohosArm64Main/... | OpenHarmony POSIX actual 实现 |
example/shared/.../AtomicExamples.kt | 公共快照、原子操作和自检 |
example/nativeApp/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/.../AtomicFuClient.ets | N-API JSON 解析和显式类型 |
example/ohosApp/.../Index.ets | ArkUI 运行页面 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 导出和字符串复制 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
scripts/build-hap.sh | 已配置签名工程的 HAP 构建 |
docs/openharmony/VALIDATION.md | 自动检查和真机验收说明 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | atomic(0) | 创建原子 Int |
| Kotlin | AtomicExamples.snapshot | 创建不可变快照 |
| Kotlin | AtomicExamples.increment | 原子递增并更新引用 |
| Native | AtomicIncrement | 返回一个 JSON 快照 |
| N-API | increment | 向 ArkTS 暴露递增方法 |
| ArkTS | runChecks | 展示共享检查结果 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责操作和界面生命周期:
this.current = increment();
Kotlin 负责原子语义和不可变数据:
counter.incrementAndGet()
label.value = "updated"
return snapshot()
两者之间只传输 JSON 字符串,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
只有真正链接 ohosArm64 动态库,才能证明 AtomicFU 的 Native 实现可以进入 OpenHarmony 运行时。只在 ArkTS 页面中使用普通变量不能证明 KMP 适配成立。
决策 2:独立消费者必须通过构建产物消费
example 单独解析 AtomicFU,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加字段而不暴露内部对象布局。
决策 4:桥接层只开放五个 C ABI 入口
快照、递增、重置、自检和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期和兼容风险。
决策 5:页面不重复实现原子逻辑
ArkTS 只调用 increment 和 reset,所有并发语义由 AtomicFU 公共 API 决定。这样示例验证的是库本身,而不是页面变量的加一操作。
决策 6:把库验证和设备验证分开
JVM 测试验证原子规则,Native 编译验证目标实现,CMake 验证 N-API 动态库,Hvigor 验证 HAP,真机验证最终页面。每一层都有明确的失败边界。
六、测试与验证
6.1 测试环境
本次本地构建使用:
- macOS;
- JDK 21;
- Kotlin Multiplatform
2.2.21-1.0.0; - DevEco Studio 及 OpenHarmony ARM64 Native SDK;
arm64-v8a目标架构;- AtomGit
oh-tpc/kotlinx-atomicfu仓库; - 未签名 HAP 构建链路。
文章中的效果图用于展示 ArkUI 页面布局和 Native 数据状态。真机是否可以安装和运行,仍需在配置签名并连接 ARM64 设备后验证。
6.2 静态检查与单元测试
./gradlew :atomicfu:jvmTest
(cd example && ./gradlew :shared:jvmTest)
测试覆盖 AtomicFU 原有 JVM 行为、原子递增、引用更新、Boolean 读取、CAS、get-and-set 和示例自检。公共测试通过后再执行 Native 链接,避免把模型问题带到 DevEco 阶段。
6.3 原生桥接和 HAP 验证
./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
验证重点:
compileKotlinOhosArm64成功;linkDebugSharedOhosArm64成功;libatomicfu.so和头文件成对复制;- Native 依赖检查没有未解析的强符号;
- Hvigor 能完成 CMake、ArkTS 和 HAP 打包;
- 未配置签名时生成
entry-default-unsigned.hap。
6.4 功能验证用例
用例 1:默认页面和自检
启动应用后页面显示 AtomicFU 工作台、计数器初始值、ready label 和 enabled 状态。页面调用 runChecks() 后显示 8/8 原子操作检查通过。
用例 2:原子递增
点击“原子递增”,页面计数器增加 1,label 变为 updated,状态提示变为“已执行原子递增”。
用例 3:重复递增
连续点击“原子递增”,计数器应连续增加。每次点击都经过 ArkTS → N-API → C ABI → AtomicFU → JSON 的完整链路。
用例 4:重置和检查
点击“重置并检查”,计数器恢复为 0,label 恢复为 ready,enabled 恢复为 true,并重新显示共享检查结果。
用例 5:错误返回
如果 Native 模块加载失败或返回错误 JSON,页面显示错误文本,不应出现 ArkTS 未捕获异常或 Native 崩溃。
用例 6:未签名包和签名包区别
未签名 HAP 可以用于检查构建产物;要安装到实际设备,需要在独立工程中配置匹配的证书和 profile,再生成 signed HAP。
6.5 验证结论
当前适配已完成 JVM 测试、OpenHarmony Native 编译、C ABI/N-API 构建和未签名 HAP 构建。截图展示了页面已经接收到 Native 快照并呈现 counter、label 和 enabled。签名安装和真机长时间运行应在配置设备证书后继续验收。
七、运行效果
7.1 页面效果图

图中可以看到:
- 页面标题为“AtomicFU 工作台”;
- 副标题说明 Kotlin Multiplatform / OpenHarmony;
- 当前计数器为
6; - label 为
updated; - enabled 为
true; - 页面展示“原子递增”和“重置并检查”两个操作;
- 底部说明数据由 Kotlin/Native 原子实现,经 N-API 提供给 ArkTS。
7.2 命令速查
# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
# 单独运行共享测试
(cd example && ./gradlew :shared:jvmTest)
# 设备依赖检查
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 构建未签名 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/atomicfu-signing
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把 AtomicFU Native 实现真正编译成
ohosArm64动态库。 - N-API 不负责业务判断:C++ 只做字符串转换和释放,原子语义放在 Kotlin。
- 生成头文件和动态库必须同步:只更新其中一个会导致符号或 ABI 不匹配。
- 权限和 Native 能力是两件事:AtomicFU 示例不需要系统权限,但仍需正确打包 ARM64 动态库。
- 签名配置需要绑定 product:未配置签名时 Hvigor 只能生成 unsigned HAP。
8.2 已知问题
- 当前示例只打包
arm64-v8a,不包含 32 位或 x86 模拟器 Native 库; - HAP 的签名证书和 profile 需要开发者本机配置,仓库不提供通用签名材料;
- ArkTS 导入
libentry.so时,DevEco 可能提示 Native 模块声明尚未验证,这是 N-API 类型声明和 SDK 版本相关提示; - 当前示例验证的是单页面原子操作,复杂业务仍需自行设计共享状态模型和并发访问策略;
- Native 依赖、HAP 打包和真机安装属于不同阶段,不能用其中一个结果替代其他阶段。
8.3 未来优化方向
- 增加多线程 Native 压力测试和设备侧性能数据;
- 为 CMP 页面提供可复用的 AtomicFU 状态卡片组件;
- 增加
AtomicLong、SynchronousMutex和超时等待的 ArkTS 示例; - 在持续集成中加入
ohosArm64Native 链接和未签名 HAP 构建任务; - 增加设备能力、ABI 和签名配置的自动检查。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个计数器按钮,而是一条完整跨端链路:
KMP AtomicFU API
→ Kotlin/Native ohosArm64
→ C ABI
→ C++ N-API
→ ArkTS 页面
→ OpenHarmony HAP
9.2 封装层次
- KMP 层:定义稳定的原子类型、锁和更新操作;
- Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成字符串转换、模块注册和内存释放;
- ArkTS 层:管理页面状态、按钮操作和错误展示;
- DevEco 层:完成 CMake、HAP、签名、安装和运行。
9.3 三条经验
- 先让共享模型在 JVM 和 Native 通过,再接入 ArkUI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、Native 链接、HAP 构建和真机安装分别记录,避免把“编译成功”误认为“设备能力可用”。
9.4 适配成果
当前 kotlinx-atomicfu 已完成:
ohosArm64Kotlin/Native 目标;- OpenHarmony POSIX 原子锁和线程实际实现;
- Kotlin/Native + C ABI + N-API 桥接;
- Atomic Int、Atomic Ref 和 Atomic Boolean 共享快照;
AtomicSnapshotJSON 数据契约;- JVM、Native 和示例共享的 8 项自检;
- DevEco Stage ArkUI 宿主页面;
- ARM64 Native 动态库和未签名 HAP 构建;
- AtomGit 项目文档、验收说明和运行效果图。
参考文档
- AtomGit/oh-tpc/kotlinx-atomicfu
- AtomGit/oh-tpc/uuid OpenHarmony 适配参考
- Kotlin Multiplatform
- OpenHarmony N-API
- DevEco Studio
- 华为云码道
开源鸿蒙平台 KMP_CMP 三方库「AtomicFU」适配全流程
更多推荐




所有评论(0)