开源鸿蒙平台 KMP_CMP 三方库「Atrium」适配全流程
本文记录
Atrium接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 交互页面、HAP 构建、签名入口和验证流程。本次适配复用 Atrium 的断言语义、失败报告和 Kotlin 多平台实现,再由 ArkTS 调用 Kotlin/Native 中的真实断言函数。示例页面不是单纯的固定检查页面,而是允许用户输入实际值和期望值、切换断言类型、执行断言并查看最近结果的交互式工作台。
项目地址: AtomGit/oh-tpc/atrium
上游仓库: robstoll/atrium
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
Atrium 是一个 Kotlin Multiplatform 断言库,提供 fluent 和 infix 两套 API,支持相等、不相等、集合、字符序列包含、类型和结构等断言,并能生成结构化失败报告。原项目的核心代码主要在 commonMain 中运行,原生目标通过各平台实现补齐运行时差异。
如果只把示例页面重新写成 ArkTS,再在页面中使用 === 或 String.includes,只能证明 ArkUI 可以显示结果,不能证明 Atrium 的公共 API、Kotlin/Native 目标、失败报告和跨语言桥接已经连通。因此本次适配需要同时解决以下问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | KMP 模块原本没有 OpenHarmony ohosArm64 目标,必须让核心、logic、API 和 bundle 共同生成 KLIB。 |
| Native 兼容 | Kotlin/Native 没有 JVM 的部分标准库和反射行为,需要补充 nativeMain actual 实现。 |
| 依赖变体 | Atrium 的部分构建依赖没有现成的 OpenHarmony variant,需要在本地多平台模块中提供兼容实现。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin data class 或 Expect 对象,必须经过 C ABI、C++ N-API 和 JSON。 |
| 报告生命周期 | Kotlin/Native 返回的字符串由 Native heap 分配,C++ 复制到 ArkTS 后必须显式释放。 |
| 示例真实性 | 固定显示“检查通过”无法体现断言库功能,页面必须把用户输入传给 Atrium 并展示真实诊断消息。 |
| 交付链路复杂 | Native 动态库、CMake、ArkTS、HAP、签名、设备安装和 ARM64 依赖需要分开验收。 |
因此,本项目把适配边界放在四个地方:Kotlin Multiplatform 目标配置、Native 兼容实现、C ABI/N-API 桥接层和 ArkUI 交互层。断言语义与失败报告仍由 Atrium 维护,ArkTS 只负责输入、模式选择、页面状态和结果展示。
1.2 Atrium 提供的能力
Atrium 公共模块提供以下能力:
expect(actual).toEqual(expected):使用Any.equals判断相等;expect(actual).notToEqual(expected):判断实际值与期望值不相等;expect(text).toContain.atLeast(1).value(expected):判断字符序列至少包含一次期望文本;- fluent 和 infix 两种调用风格,满足不同测试代码的表达习惯;
- 统一的
AssertionError失败报告,包含主题、期望条件和实际值; - 多语言翻译模块和报告格式化模块;
- 公共测试工厂、verbs 和 bundle 模块,供库内部测试和业务项目复用。
本次交互示例把最容易理解的三种文本断言映射为页面模式:
| 页面模式 | mode | Atrium 调用 | 示例 |
|---|---|---|---|
| 相等 | 0 | expect(actual).toEqual(expected) | atrium 与 atrium 相等 |
| 不相等 | 1 | expect(actual).notToEqual(expected) | left 与 right 不相等 |
| 包含文本 | 2 | expect(actual).toContain.atLeast(1).value(expected) | OpenHarmony 包含 Harmony |
未知 mode 在共享门面中回退到相等分支,ArkTS 页面只提供上述三个合法选项。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 断言调用、失败捕获、结果模型和自检逻辑由 Kotlin 共享。 |
| 平台目标 | 为 Atrium 核心模块和示例加入 ohosArm64,生成 libatrium.so。 |
| 桥接稳定 | 只使用少量 C ABI 函数和 UTF-8 JSON,不把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持输入、模式切换、成功/失败示例、执行历史和错误反馈。 |
| 可测试 | JVM 测试、Native 编译、CMake/N-API、ArkTS 编译、HAP 和设备安装分别验收。 |
| 签名安全 | 源码只保留签名配置入口,证书、Profile、keystore 和密码由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的 OpenHarmony 验收宿主。其他 KMP/CMP 应用可以直接复用 Atrium 的公共断言 API,也可以只复用
example/shared中的门面模式,按业务需要设计自己的 UI。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点 Atrium 模块、公共 API 和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、Native actual 和独立 example 工程
第 3 阶段:断言与序列化 ── 建立三种断言模式、结果模型和 JSON 契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、参数检查和内存释放
第 5 阶段:交互页面 ── ArkUI 输入控件、模式切换、结果卡片和执行历史
第 6 阶段:构建与验证 ── CMake、ArkTS、HAP 构建、签名入口和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享断言,再把相同代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后由 ArkTS 页面发起真实断言调用。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 UUID CMP 工程一致的分层:
atrium-core/ 核心断言、报告和公共类型
logic/atrium-logic/ 断言逻辑、搜索器和 Native actual
apis/fluent/atrium-api-fluent/ fluent 断言 API
apis/infix/atrium-api-infix/ infix 断言 API
bundles/ 可消费的 Atrium bundle
misc/ specs、test-factory、verbs 和支撑模块
example/shared/ 示例门面和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native 构建和 HAP 签名入口脚本
docs/openharmony/ 验收记录和效果图
example 是独立 Gradle 工程,不把 DevEco 工程当作根工程的 Kotlin 子模块。这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到另一个目录后由开发者在 DevEco Studio 中配置签名。
1.2 固定工具链和版本矩阵
本项目使用以下工具链约定:
| 项目 | 配置 | 用途 |
|---|---|---|
| Kotlin Multiplatform | 以 gradle/buildLibs.versions.toml 为准 | JVM、Kotlin/Native 和 KLIB |
| JDK | 21 | Gradle、Kotlin 编译和 Native 任务 |
| OpenHarmony 目标 | ohosArm64 | ARM64 真机动态库 |
| DevEco product | 6.0.0(20) | 工程兼容和目标 SDK |
| DevEco Studio | 6.0.0 Release | IDE 版本,负责工程同步、HAP 构建与签名 |
| ABI | arm64-v8a | HAP 原生库架构 |
| N-API 模块 | libentry.so | ArkTS 到 Kotlin/Native 的桥接 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
本次验证使用了本机 JDK 21。Kotlin/Native 任务不要使用 JDK 25,否则可能出现 class-file 版本不兼容。
1.3 创建 OpenHarmony 示例目录
示例页面围绕“输入、选择、执行、反馈、历史”组织,而不是把共享检查结果直接平铺成验证清单:
标题区 Atrium 三方库 / OpenHarmony 交互式断言工作台
输入区 实际值 / 期望值
模式区 相等 / 不相等 / 包含文本
操作区 运行断言 / 载入成功示例 / 载入失败示例
结果区 通过或未通过、断言类型、Atrium 诊断消息
历史区 最近五次执行记录
页面默认使用“包含文本”示例,用户可以直接修改任意输入后再次执行。失败结果保留原始 Atrium 消息,便于观察断言库的报告能力。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
示例共享模块同时保留 JVM 测试和 OpenHarmony Native 目标:
plugins {
kotlin("multiplatform")
}
kotlin {
explicitApi()
jvm()
ohosArm64()
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
根工程中的 Atrium 核心、logic、fluent、infix、translations、verbs、test-factory 和 bundle 模块也加入对应的 ohosArm64 配置。JVM 目标让断言行为可以快速测试,ohosArm64 则把同一份 commonMain 代码编译为 OpenHarmony KLIB。
2.2 Native focused build 的作用
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "atrium"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
链接器只导出三个 C ABI 符号:
AtriumRunChecks
AtriumEvaluate
AtriumFree
这样 ArkTS 只能通过明确的边界获取检查或断言结果,Atrium 内部的 Expect、报告对象和实现细节不会变成不受控的 ABI。
2.3 配置仓库和独立消费工程
示例工程的 settings.gradle.kts 保留 Maven Central、Gradle Plugin Portal 和项目使用的公共 Maven 仓库:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
gradlePluginPortal()
mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
mavenLocal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
mavenCentral()
}
}
当前 example/shared 通过项目依赖消费根工程的 Atrium bundle:
sourceSets {
commonMain.dependencies {
api("ch.tutteli.atrium:atrium-fluent:1.3.0-SNAPSHOT")
}
}
这让页面示例始终使用当前工作树中的真实 Atrium API,而不是在 ArkTS 中复制一套行为相似的判断函数。
2.4 通过构建产物消费共享库
prepareOhos 在 Native 链接成功后复制动态库和 Kotlin/Native 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libatrium.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libatrium_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("../example/ohosApp/entry"))
}
生成的 .so 和头文件只作为本机 DevEco 构建输入,按照参考工程的规则写入 .gitignore。这样可以避免把与本机 SDK、ABI 和签名环境绑定的二进制产物提交到 AtomGit。
第 3 阶段:断言与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin data class,因此边界使用稳定的 UTF-8 JSON:
ArkTS 输入 actual / expected / mode
↓
N-API evaluate()
↓
C ABI AtriumEvaluate()
↓
AtriumExamples.evaluate()
↓
AssertionResult.toJson()
↓
ArkUI 结果卡片和历史记录
页面只消费 JSON,不重新实现 Atrium 的相等和包含语义。这样 JVM 测试、Kotlin/Native 和 ArkTS 页面使用同一套断言规则。
3.2 AtriumAssertionResult
公共示例门面定义不可变结果模型:
public data class AtriumAssertionResult(
val type: String,
val passed: Boolean,
val message: String,
)
三种模式统一由 AtriumExamples.evaluate 分发:
public fun evaluate(actual: String, expected: String, mode: Int): AtriumAssertionResult {
val type = when (mode) {
NOT_EQUAL -> "不相等"
CONTAINS -> "包含文本"
else -> "相等"
}
return try {
when (mode) {
NOT_EQUAL -> expect(actual).notToEqual(expected)
CONTAINS -> expect(actual).toContain.atLeast(1).value(expected)
else -> expect(actual).toEqual(expected)
}
AtriumAssertionResult(type, true, "断言通过")
} catch (error: AssertionError) {
AtriumAssertionResult(type, false, error.message ?: "断言失败")
}
}
关键点是 try/catch 只包住 Atrium 断言调用。通过时返回简洁状态,失败时保留 Atrium 生成的详细 AssertionError.message,页面无需猜测失败原因。
3.3 JSON 结构和字符串转义
Native bridge 返回的成功结果如下:
{
"type": "包含文本",
"passed": true,
"message": "断言通过"
}
失败结果保留诊断消息:
{
"type": "相等",
"passed": false,
"message": "expected: ... actual: ..."
}
Kotlin/Native 侧统一使用 quote 对反斜杠、引号、换行和制表符进行转义。任何异常都会转换成:
{"error":"Native Atrium error"}
这样 ArkTS 能显示错误文本,C ABI 仍然只有字符串和指针两种边界类型。
3.4 自检和错误边界
AtriumExamples.runChecks() 保留四项共享冒烟检查:
- 整数相等断言通过;
- 字符串相等断言通过;
- 集合相等断言通过;
- 故意失败的断言能产生包含
to equal的报告。
这些检查用于确认 Atrium 核心和 Native 加载成功,但示例首页的主要功能仍然是用户驱动的 evaluate 工作流。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 AtriumAssertionResult 和 Atrium 内部 Expect 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript 值,C++ 也不能把 Kotlin 对象地址直接当成 JavaScript 对象。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ char* / int32
▼
Kotlin/Native C ABI
│ AtriumExamples.evaluate
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出布尔值 | 实现简单 | 页面看不到 Atrium 失败报告 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写断言逻辑 | 页面调用简单 | KMP 和 ArkTS 逻辑会分叉 | ❌ |
4.3 Kotlin/Native 导出函数
Native bridge 的结果缓冲区由 nativeHeap 分配:
@CName("AtriumEvaluate")
public fun evaluateNative(
actual: CPointer<ByteVar>?,
expected: CPointer<ByteVar>?,
mode: Int,
): CPointer<ByteVar> = response {
val result = AtriumExamples.evaluate(
actual?.toKString().orEmpty(),
expected?.toKString().orEmpty(),
mode,
)
"{\"type\":${quote(result.type)},\"passed\":${result.passed},\"message\":${quote(result.message)}}"
}
@CName("AtriumFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
返回指针不跨越一次调用生命周期。C++ 复制字符串后立即调用 AtriumFree,避免 Native heap 泄漏。
4.4 C++ N-API 方法分发
C++ 注册两个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"evaluate", nullptr, Evaluate, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
evaluate 会检查参数数量、两个字符串类型和整数 mode,再调用 AtriumEvaluate。字符串参数先查询 UTF-8 长度,再分配 std::vector<char>,避免固定缓冲区截断中文或长文本。
CMake 将 Kotlin/Native 动态库作为 imported library:
add_library(atrium SHARED IMPORTED)
set_target_properties(atrium PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libatrium.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE atrium libace_napi.z.so)
4.5 N-API 生命周期
ArkTS evaluate(actual, expected, mode)
│
▼
Read UTF-8 strings + int32 mode
│
▼
AtriumEvaluate(actual, expected, mode)
│
▼
napi_create_string_utf8(...)
│
▼
AtriumFree(nativeBuffer)
│
▼
return JS string
C++ 负责参数检查、跨语言字符串复制和 Native 释放,ArkTS 页面不需要知道 Kotlin/Native 的堆实现。
第 5 阶段:交互页面封装
5.1 ArkTS 调用 Native Atrium
ArkTS 客户端只做类型声明和 JSON 解析:
import atriumNative from 'libentry.so';
export interface AtriumAssertionResult {
type: string;
passed: boolean;
message: string;
}
export function evaluate(actual: string, expected: string, mode: number): AtriumAssertionResult {
return JSON.parse(atriumNative.evaluate(actual, expected, mode)) as AtriumAssertionResult;
}
页面不在客户端中实现 includes、=== 或失败文案,所有断言判断都通过 Native bridge 进入 Atrium。
5.2 输入和模式状态
Index.ets 使用 ArkUI @State 保存用户输入、模式、最新结果和历史数组:
@State private actual: string = 'OpenHarmony 让跨平台断言更易用';
@State private expected: string = '跨平台断言';
@State private mode: number = 2;
@State private latest: AssertionHistory | null = null;
@State private history: AssertionHistory[] = [];
三个按钮只修改 mode,不执行本地判断。点击“运行断言”时,当前输入和模式一次性传入 evaluate,返回结果再写入页面状态。
5.3 结果卡片和历史记录
每次执行生成一条记录:
const record: AssertionHistory = {
id: Date.now(),
type: result.type,
actual: this.actual,
expected: this.expected,
passed: result.passed,
message: result.message
};
this.latest = record;
this.history = [record, ...this.history].slice(0, 5);
成功结果显示“通过”和“断言通过”;失败结果额外显示实际值和期望值。最近执行区域用 PASS / FAIL 标记,帮助用户比较多次输入的差异。
5.4 页面交互预设
页面提供两个数据预设:
- 载入成功示例:加载中文包含文本场景,默认选择“包含文本”;
- 载入失败示例:加载不同字符串的相等场景,默认选择“相等”。
预设按钮只写入输入框和模式,不直接伪造结果。用户仍需点击“运行断言”,页面才会调用 Kotlin/Native。
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../AtriumExamples.kt
│ └── src/commonTest/.../AtriumExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/.../NativeBridge.kt
│ └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
├── AppScope/
├── entry/src/main/cpp/
│ ├── CMakeLists.txt
│ ├── napi_init.cpp
│ └── types/libentry/
└── entry/src/main/ets/
├── atrium/AtriumClient.ets
└── pages/Index.ets
shared 验证公共断言,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI 页面。三者的边界清晰,任何一层失败都能单独定位。
6.2 原生模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块,类型声明位于:
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
ArkTS 使用:
import atriumNative from 'libentry.so';
模块导出 runChecks 和 evaluate 两个方法,方法返回值均为 JSON 字符串,再由 ArkTS 客户端解析为类型化对象。
6.3 Native 动态库准备
执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本依次编译 Atrium 的核心 ohosArm64 目标、运行示例 JVM 测试、链接 nativeApp 并执行 prepareOhos。成功后生成:
example/ohosApp/entry/libs/arm64-v8a/libatrium.so
example/ohosApp/entry/src/main/cpp/include/libatrium_api.h
这些文件属于本地构建输入,默认被 .gitignore 排除。重新构建时必须同时更新 .so 和头文件,避免 C ABI 不一致。
6.4 构建、签名和安装
可以直接使用 DevEco Hvigor 编译 ArkTS、C++ 和 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
仓库脚本在签名阶段增加明确停点:
./scripts/build-hap.sh /absolute/path/to/atrium/example/ohosApp
未设置 OHOS_SIGNING_CONFIG 时,脚本会提示用户配置签名路径并退出,不会索取或保存密码、私钥:
签名需要用户配置 OHOS_SIGNING_CONFIG
配置本机签名工程后再重新执行脚本。源码工程不应提交 .cer、.p7b、.p12、Profile、密码或私钥。
四、完整代码对照
4.1 整体架构
ArkUI 输入框和模式按钮
│ actual / expected / mode
▼
AtriumClient.ets -> libentry.so
│ N-API
▼
AtriumEvaluate / AtriumRunChecks
│ C ABI
▼
libatrium.so
│ Kotlin/Native
▼
AtriumExamples -> Atrium API -> JSON
4.2 文件清单
| 文件 | 职责 |
|---|---|
atrium-core/src/nativeMain/ | Atrium 核心 Native actual 和兼容实现 |
logic/atrium-logic/src/nativeMain/ | 断言 logic 的 OpenHarmony Native 实现 |
example/shared/.../AtriumExamples.kt | 三种断言门面、自检和结果模型 |
example/nativeApp/.../NativeBridge.kt | C ABI、JSON 返回和 Native 内存释放 |
example/nativeApp/.../shared-library.map | C ABI 导出符号白名单 |
example/ohosApp/.../napi_init.cpp | N-API 导出、参数读取和字符串转换 |
example/ohosApp/.../AtriumClient.ets | ArkTS 类型定义和 JSON 解析 |
example/ohosApp/.../Index.ets | 交互式断言工作台页面 |
scripts/build-openharmony.sh | 模块测试、Native 链接和产物复制 |
scripts/build-hap.sh | 已配置签名工程的 HAP 构建入口 |
docs/openharmony/VALIDATION.md | 自动构建门和真机验证记录 |
docs/openharmony/atrium-workbench.jpg | 交互页面效果图 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Atrium | expect(actual).toEqual(expected) | 相等断言 |
| Atrium | expect(actual).notToEqual(expected) | 不相等断言 |
| Atrium | expect(text).toContain.atLeast(1).value(expected) | 包含文本断言 |
| Shared | AtriumExamples.evaluate | 统一模式分发和失败捕获 |
| Native | AtriumEvaluate | C ABI 入口,返回结果 JSON |
| N-API | evaluate | 向 ArkTS 暴露 Native 断言 |
| ArkTS | runAssertion | 读取页面状态、调用 N-API、记录历史 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责收集输入和呈现状态:
const result: AtriumAssertionResult = evaluate(
this.actual,
this.expected,
this.mode
);
Kotlin 负责断言含义和失败报告:
when (mode) {
NOT_EQUAL -> expect(actual).notToEqual(expected)
CONTAINS -> expect(actual).toContain.atLeast(1).value(expected)
else -> expect(actual).toEqual(expected)
}
两者之间只传输字符串、整数和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
只有真正链接 ohosArm64 动态库,才能证明 Atrium 的共享断言和报告逻辑可以进入 OpenHarmony 运行时。只编译 JVM 示例不足以证明 Native 兼容。
决策 2:独立消费者必须通过构建产物消费
example 单独解析 Atrium,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并能携带 Atrium 失败报告。新增字段时也不需要暴露 Kotlin 对象布局。
决策 4:桥接层只开放三个 C ABI 入口
执行检查、执行交互断言和释放字符串已经覆盖示例所需能力。减少 ABI 符号可以降低 Native 生命周期和兼容风险。
决策 5:页面展示原始 Atrium 诊断
如果页面只显示“成功/失败”,用户无法理解断言库的价值。失败时保留 AssertionError.message,才能展示 Atrium 对主题、期望和实际值的结构化描述。
决策 6:把自动构建和设备验证分开
JVM 测试验证断言规则,Native 编译验证目标,CMake 验证 N-API,Hvigor 验证 HAP,真机验证安装和运行。每一层都有明确的失败边界。
六、测试与验证
6.1 测试环境
本次工程验证使用:
- macOS;
- JDK 21;
- Kotlin Multiplatform 版本以仓库版本目录为准;
- DevEco Studio 及 OpenHarmony ARM64 Native SDK;
- OpenHarmony Stage 工程;
hdc设备工具用于可选的真机安装。
当前验证环境没有授权的 hdc 设备,因此本文不把设备安装结果写成已完成的真机验收。效果图用于展示交互页面设计和页面信息层级,Native、ArkTS 和 HAP 构建结果仍按命令独立验证。
6.2 静态检查与单元测试
export JAVA_HOME="/path/to/jdk-21"
./gradlew -p example :shared:jvmTest :nativeApp:prepareOhos --no-daemon
测试覆盖:
- 相等断言成功;
- 不相等断言成功;
- 包含文本断言成功;
- 相等断言失败时返回非空 Atrium 消息;
- 原有四项共享冒烟检查全部通过。
6.3 原生桥接和 HAP 验证
JAVA_HOME=/path/to/jdk-21 ./scripts/build-openharmony.sh
脚本已验证以下任务可以完成:
:atrium-core:compileKotlinOhosArm64
:atrium-fluent:compileKotlinOhosArm64
:atrium-infix:compileKotlinOhosArm64
:shared:jvmTest
:nativeApp:prepareOhos
CMake/Ninja 还可以单独检查 napi_init.cpp:
cmake -S example/ohosApp/entry/src/main/cpp \
-B /tmp/atrium-ohos-cmake \
-G Ninja \
-DCMAKE_TOOLCHAIN_FILE=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake \
-DOHOS_ARCH=arm64-v8a \
-DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/atrium-ohos-cmake -v
DevEco Hvigor 已验证能够完成 CMake、ArkTS、HAP 打包流程。仓库签名脚本在没有 OHOS_SIGNING_CONFIG 时会按设计停在签名提示处。
6.4 功能验证用例
用例 1:默认页面和包含文本断言
打开页面后,实际值为 OpenHarmony 让跨平台断言更易用,期望值为 跨平台断言,默认模式为“包含文本”。点击“运行断言”应进入 Kotlin/Native 并显示“断言通过”。
用例 2:相等断言
选择“相等”,输入两个相同字符串,执行后结果类型显示“相等”,状态为通过。输入不同字符串后再次执行,页面显示失败并展示 Atrium 诊断。
用例 3:不相等断言
选择“不相等”,输入 left 和 right,执行后应通过;把期望值改成 left,再次执行应失败。
用例 4:包含文本断言
选择“包含文本”,输入长文本和子文本,执行后验证字符序列搜索实际来自 Atrium,而不是 ArkTS includes。
用例 5:成功/失败预设
点击“载入成功示例”或“载入失败示例”,确认输入框和模式变化;点击“运行断言”后,页面结果才改变,预设按钮不会伪造执行结果。
用例 6:连续执行和历史记录
连续执行至少六次,确认“最近执行”最多保留五条,最新一条位于列表顶部,PASS/FAIL 与执行结果一致。
6.5 验证结论
共享 JVM 测试、Atrium ohosArm64 编译、Kotlin/Native 动态库链接、CMake/N-API 编译和 DevEco ArkTS/HAP 构建均可独立完成。真机安装与设备交互需要在连接授权的 ARM64 OpenHarmony 设备后继续执行,文章不以无设备状态代替真机结论。
七、运行效果
7.1 交互页面效果图

效果图展示了当前 ArkUI 页面:
- 页面标题为“Atrium 三方库”;
- 副标题为“OpenHarmony 交互式断言工作台”;
- 顶部显示
NATIVE,说明结果通过 Native bridge 产生; - 输入区同时展示实际值和期望值;
- 三个模式按钮中“包含文本”处于选中状态;
- “运行断言”是实际执行入口;
- “载入成功示例”和“载入失败示例”用于快速切换输入;
- 结果区显示“断言通过 · 包含文本”和 Atrium 返回的成功消息;
- 最近执行区域保留实际运行过的输入组合。
图片文件位于 docs/openharmony/atrium-workbench.jpg,文章与效果图均随项目源码使用 AtomGit 管理。
7.2 命令速查
# 根工程测试和 OpenHarmony Native 准备
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
# 单独运行共享测试
./gradlew -p example :shared:jvmTest --no-daemon
# CMake/N-API 编译
cmake -S example/ohosApp/entry/src/main/cpp \
-B /tmp/atrium-ohos-cmake -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native/build/cmake/ohos.toolchain.cmake \
-DOHOS_ARCH=arm64-v8a
cmake --build /tmp/atrium-ohos-cmake
# 在已配置签名的工程中构建 HAP
./scripts/build-hap.sh /absolute/path/to/atrium/example/ohosApp
# 查看设备并安装签名 HAP
hdc list targets
hdc -t <设备序列号> install -r /absolute/path/to/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b ch.tutteli.atrium.example
7.3 签名说明
签名是 HAP 交付的最后一步。执行 scripts/build-hap.sh 时,如果没有配置 OHOS_SIGNING_CONFIG,脚本会输出:
签名需要用户配置 OHOS_SIGNING_CONFIG(仅接受签名配置文件路径,不要在终端输入密码或私钥)。
配置应由开发者在本机完成。证书、Profile、p12、别名和密码不进入 AtomGit,也不写入文章或示例源码。
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把 Atrium 共享断言真正编译为
ohosArm64动态库。 - N-API 不负责业务判断:C++ 只做参数检查、字符串转换和释放,断言语义必须放在 Kotlin。
- 返回值不能只保留布尔值:如果丢弃
AssertionError.message,示例无法体现 Atrium 的报告能力。 - C ABI 必须配 linker map:头文件生成成功不代表符号会被导出,新增函数后必须同步更新版本脚本。
- 生成文件不应进入仓库:
.so、生成头文件、.cxx和.hvigor应在本地准备,源码仓库只保留可复现的构建入口。 - 签名与编译分开处理:构建成功不等于 HAP 已签名,签名材料必须由本地 DevEco 工程提供。
8.2 已知问题
- 当前 Native 示例只提供 ARM64(
arm64-v8a),没有 32 位或 x86 模拟器产物; - 当前验证环境没有授权真机,设备安装和
hdc启动需要后续补做; - DevEco 对
libentry.so的 N-API 类型声明可能输出 SDK 版本相关的提示,项目已提供index.d.ts; - 当前页面只提供三种文本断言,Atrium 其他集合、类型和结构断言仍需按业务增加交互控件;
- HAP 签名依赖开发者本机配置,仓库脚本只负责在缺少配置时提示并停止。
8.3 未来优化方向
- 增加集合断言、正则断言和自定义消息的交互示例;
- 为 ArkTS 客户端增加更严格的 JSON schema 校验和错误分类;
- 发布带 OpenHarmony variant 的 Atrium bundle,减少示例对本地构建的依赖;
- 增加 x86_64 模拟器或更多 ARM64 设备的兼容性验证;
- 在持续集成中加入
compileKotlinOhosArm64、CMake 和未签名 HAP 构建任务; - 为 CMP/Compose Multiplatform 宿主提供与当前 ArkUI 工作台等价的交互示例。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个页面按钮,而是一条完整跨端链路:
ArkUI 输入
→ N-API 参数检查
→ Kotlin/Native C ABI
→ Atrium 断言 API
→ AssertionError / 成功结果
→ JSON
→ ArkUI 结果卡片和执行历史
9.2 封装层次
- Atrium 层:定义稳定的 expectation、报告和翻译能力;
- Shared 层:定义三种页面模式、结果模型和共享自检;
- Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成参数检查、字符串转换和内存释放;
- ArkTS 层:管理输入控件、模式状态、结果展示和执行历史;
- DevEco 层:完成 CMake、ArkTS、HAP、签名和设备安装。
9.3 三条经验
- 先让共享断言在 JVM 和 Native 通过,再接入 ArkUI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递,同时保留失败报告;
- 把自动测试、HAP 构建、签名和真机验收分别记录,避免把“编译成功”误认为“设备可用”。
9.4 适配成果
当前 Atrium OpenHarmony 适配已完成:
- 核心模块和 API 的
ohosArm64Kotlin/Native 目标; - Native actual、KMP 依赖变体和本地 kbox 兼容模块;
- JVM 与 OpenHarmony 共用的相等、不相等、包含文本断言;
- Kotlin/Native + C ABI + C++ N-API JSON 桥接;
- ArkUI 交互式断言工作台和最近执行记录;
- CMake、Hvigor、HAP 构建和签名提示脚本;
- AtomGit 项目文档、中文/英文 OpenHarmony README 和效果图;
- 可复现的 JVM、Native、CMake 和 ArkTS 验证命令。
适配过程 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)