kotlin-result OpenHarmony KMP_CMP 适配:从多平台源码到 ARM64 验收
本文记录
kotlin-result及kotlin-result-coroutines适配 OpenHarmony 的完整过程:从 Kotlin Multiplatform 目标配置、Kotlin/Native 工具链和依赖版本对齐,到独立消费工程、CMP runtime 验证、N-API 桥接、HAP 构建和签名交接。这次工作的重点不是把一套 ArkTS 业务代码机械翻译成 Kotlin,而是让一个已经稳定运行的多平台库获得
ohosArm64目标,并保持原有Result<V, E>API 和错误处理语义不变。文中的命令、版本和结论均以当前仓库实际内容为准。
项目地址:https://atomgit.com/oh-tpc/ohos_kotlin-result
开发工具: 华为云码道
一、背景
1.1 为什么 KMP/CMP 项目需要 OpenHarmony 目标
kotlin-result 是一个用 Result<V, E> 表示成功与失败的 Kotlin 多平台库。核心模块提供 Ok、Err、map、andThen、binding、recover、集合操作等 API;协程模块继续提供 coroutineBinding、取消感知的 runSuspendCatching、并行组合和 Flow 扩展。
原有多平台工程已经覆盖 JVM、JavaScript、Wasm 和多种 Native 平台,但 OpenHarmony 设备不能直接消费这些目标。要在 HarmonyOS/OpenHarmony 应用中复用同一套业务代码,需要同时解决几个问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | Gradle/Kotlin 约定中没有 ohosArm64(),库无法产出鸿蒙 ARM64 KLIB。 |
| 工具链不一致 | OpenHarmony Kotlin/Native 发行包、KMP 插件、sysroot 与普通 Kotlin/Native 版本必须对齐。 |
| 编译器版本差异 | 上游源码包含 Kotlin 2.4 相关返回值检查配置,指定的 Kotlin 2.2.21 编译器无法直接使用。 |
| 依赖版本差异 | OHOS source set 需要使用带 -1.0.0 后缀的协程发行包,普通平台仍要保持上游版本。 |
| 消费验证不足 | 仅编译库不能证明第三方工程能正确解析 Maven metadata、KLIB 和动态库。 |
| 应用边界不同 | Kotlin/Native 产物最终要被 ArkTS 应用通过 N-API 加载,涉及 C ABI、动态库和 HAP 打包。 |
因此,适配目标不是复制一个“鸿蒙专用库”,而是让公共源码继续复用,把平台差异收敛在构建约定、依赖声明和应用桥接层。
1.2 适配后提供的能力
当前仓库提供以下能力:
kotlin-result增加ohosArm64目标,可在 OpenHarmony ARM64 设备上使用核心 Result API。kotlin-result-coroutines增加对应的 OHOS 协程产物,支持挂起绑定、并行组合、取消传播和 Flow 扩展。- 通过
2.3.2-ohos.1版本号区分本次 OpenHarmony 社区发行版本。 - 提供
-PopenharmonyOnly=true,只配置 JVM/OHOS 目标,避免在 focused build 中下载无关平台工具链。 - 提供独立的
example/消费工程。它通过mavenLocal()引用两个已发布的库,而不是直接依赖根工程源码。 - 通过 Kotlin/Native 生成
libkn.so,再经 C++ N-API 桥接给 ArkTS 页面。 - 在同一条验收链路中检查 Result 行为、协程行为、CMP 1.9.2 snapshot/derived state 和原生动态库依赖。
核心库不依赖 CMP UI。示例只引入 CMP runtime,用于验证 OpenHarmony 目标下的快照和派生状态;应用页面由 ArkTS 编写。
1.3 实现目标
| 维度 | 要求 |
|---|---|
| API 稳定性 | 复用公共 commonMain 实现,不改变 Result<V, E> 的公开语义。 |
| 平台支持 | 新增 ohosArm64 / arm64-v8a,面向 HarmonyOS API 20 目标。 |
| 依赖隔离 | 仅在 ohosArm64Main、ohosArm64Test 中覆盖鸿蒙协程版本。 |
| 消费体验 | 库先发布到本地 Maven,独立 example/ 按普通消费者方式解析坐标。 |
| 可验证性 | JVM 测试、OHOS 编译、动态库链接、HAP 构建分别验证,不把一个结果当成全部通过。 |
| 交付安全 | 仓库只保留签名模板,不提交本机证书、私钥、密码或生成的动态库。 |
| 工程协作 | 文档、验证记录、构建脚本和 AtomGit 地址保持一致。 |
二、实现路线图
整个适配分为六个阶段:
第 1 阶段:基线盘点 ── 确认模块、目标、版本和上游 API 范围
第 2 阶段:目标适配 ── 在共享构建约定中加入 ohosArm64 和 focused build
第 3 阶段:编译器兼容 ── 对齐 Kotlin 2.2.21,处理返回值检查和协程依赖
第 4 阶段:消费工程 ── 通过 Maven/KLIB 产物构建独立 shared/nativeApp
第 5 阶段:鸿蒙桥接 ── Kotlin/Native 动态库、C ABI、N-API 和 ArkTS 页面
第 6 阶段:构建与验收 ── JVM/OHOS 检查、HAP、ELF 审计和签名交接
前两个阶段决定库能否编译;第三、四阶段决定产物能否被真实消费者解析;第五、六阶段决定它能否进入 DevEco 和设备验收。
三、逐步实现过程
第 1 阶段:盘点原工程和边界
1.1 先区分两个示例
上游工程原来的 example/ 是 JVM 服务端示例。为了避免把 JVM 示例误当成鸿蒙应用,本次适配将它移动到 jvm-example/,新增的 example/ 专门作为 OpenHarmony 消费工程:
kotlin-result/ Result 核心库
kotlin-result-coroutines/ 协程扩展库
jvm-example/ 原上游 JVM 服务端示例
example/
shared/ 共享验收逻辑和 JVM 测试
nativeApp/ ohosArm64 动态库和 CMP runtime 检查
ohosApp/ DevEco Stage 应用和 ArkTS 页面
依赖方向为:
ohosApp -> nativeApp -> shared -> kotlin-result
-> kotlin-result-coroutines
example/ 通过发布坐标消费库,库模块本身不反向依赖示例。这样可以验证“第三方工程如何接入”,而不是只验证工程内部的项目依赖。
1.2 固定版本矩阵
当前适配使用的关键版本如下:
| 组件 | 版本 |
|---|---|
| 适配库 | 2.3.2-ohos.1 |
| Kotlin Gradle plugin / compiler / Native | 2.2.21-1.0.0 |
| Kotlin Multiplatform 鸿蒙分支 | main-2.2.21-OH |
| Compose Multiplatform runtime | 1.9.2-1.0.0 |
| Compose Multiplatform 鸿蒙分支 | main-1.9.2-OH |
| Kotlin Coroutines 普通平台 | 1.10.2 |
| Kotlin Coroutines OHOS | 1.10.2-1.0.0 |
| HarmonyOS target / compatible API | 6.0.0(20) |
| Gradle | 8.14.3 |
| JDK | 21 |
这里要区分 target API、compatible API 和 compile SDK:示例的 target/compatible API 固定为 20;本机 DevEco 自带的 compile SDK 可以更高,不能把两者混写。
第 2 阶段:在共享约定中加入 ohosArm64
2.1 目标配置集中在 buildSrc
两个库都使用 id("kotlin-conventions")。因此不在每个模块重复配置,而是在 buildSrc/src/main/kotlin/kotlin-conventions.gradle.kts 中加入鸿蒙目标:
kotlin {
jvm()
jvmToolchain(21)
targets.withType<org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget>().configureEach {
compilerOptions.jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8)
}
ohosArm64()
if (!providers.gradleProperty("openharmonyOnly")
.map(String::toBoolean)
.getOrElse(false)) {
js {
browser()
nodejs()
}
// 其他 JVM/JS/Wasm/Native 目标继续保留
}
}
这段配置有两个要点:
ohosArm64()是两个库共享的正式目标,不在示例中临时拼接。openharmonyOnly只影响配置阶段,普通构建仍保留上游目标,避免为了鸿蒙适配破坏原有多平台工程。
2.2 为什么需要 focused build
Kotlin/Native 会根据目标下载对应的编译器、LLVM 和 sysroot。开发阶段如果每次都配置全部平台,构建会浪费时间,也更容易被无关目标的工具链问题阻塞。因此提供:
./gradlew -PopenharmonyOnly=true :kotlin-result:tasks
根工程在此模式下只 include kotlin-result 和 kotlin-result-coroutines;普通模式仍 include benchmarks 和 jvm-example,并保留上游的其他目标。
2.3 目标与 ABI 的关系
Kotlin 目标使用 ohosArm64,DevEco/CMake/HAP 中对应的 ABI 是 arm64-v8a:
ohosArm64 (Kotlin/Native)
│
▼
libkn.so (AArch64 ELF)
│
▼
example/ohosApp/entry/libs/arm64-v8a/
│
▼
HAP package -> ArkTS N-API module
目标名称和 HAP ABI 必须同时正确,否则库可能编译成功,但 DevEco 在打包或设备安装阶段找不到可加载的动态库。
第 3 阶段:处理 Kotlin 和协程兼容性
3.1 移除 Kotlin 2.4 专用返回值检查配置
上游源码包含 Kotlin 2.4 引入的 @IgnorableReturnValue 以及对应的 return-value-checker 配置。当前 OpenHarmony KMP 发行包固定在 Kotlin 2.2.21,因此这些声明会导致编译器找不到符号或无法识别参数。
处理方式是:
- 删除
@IgnorableReturnValue相关注解。 - 移除
-Xreturn-value-checker=full。 - 保留函数契约、
inline行为和 Result 的返回值语义。
这不是把错误吞掉,而是将编译器版本差异限制在兼容层;公共 API 的运行逻辑没有被改写。
3.2 只在 OHOS source set 覆盖协程版本
普通平台继续使用上游 1.10.2,鸿蒙源集严格使用社区发行号:
sourceSets {
ohosArm64Main.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
version { strictly(libs.versions.kotlin.coroutines.ohos.get()) }
}
}
ohosArm64Test.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test") {
version { strictly(libs.versions.kotlin.coroutines.ohos.get()) }
}
}
commonMain.dependencies {
implementation(libs.kotlin.coroutines.core)
api(project(":kotlin-result"))
}
}
这样做可以避免把 OHOS 专属发行号泄漏到 JVM、JS 或其他 Native 目标,同时保证 kotlin-result-coroutines 在鸿蒙上的二进制依赖可解析。
3.3 发布版本和签名开关
适配版本通过根目录 gradle.properties 统一声明:
group=com.michael-bull.kotlin-result
version=2.3.2-ohos.1
kotlin.native.distribution.downloadFromMaven=true
发布约定允许本地验证时关闭签名:
mavenPublishing {
publishToMavenCentral()
if (providers.gradleProperty("signPublications")
.map(String::toBoolean)
.getOrElse(true)) {
signAllPublications()
}
}
本地验证命令:
./gradlew -PopenharmonyOnly=true -PsignPublications=false \
:kotlin-result:publishToMavenLocal \
:kotlin-result-coroutines:publishToMavenLocal
当前版本尚未发布到公共 Maven,独立示例通过 mavenLocal() 消费上述坐标。
第 4 阶段:先让独立消费者跑起来
4.1 消费工程的仓库配置
example/settings.gradle.kts 同时配置插件仓库和依赖仓库:
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()
}
}
两个库的消费声明放在 shared 的 commonMain:
kotlin {
jvm()
ohosArm64()
sourceSets {
commonMain.dependencies {
implementation("com.michael-bull.kotlin-result:kotlin-result:2.3.2-ohos.1")
implementation("com.michael-bull.kotlin-result:kotlin-result-coroutines:2.3.2-ohos.1")
implementation(libs.kotlin.coroutines.core)
}
ohosArm64Main.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") {
version { strictly(libs.versions.kotlin.coroutines.ohos.get()) }
}
}
}
}
这个结构可以证明库发布的 root metadata、JVM 产物和 OHOS ARM64 产物都能被独立工程识别。
4.2 用真实库 API 编写共享验收场景
共享验收代码不调用内部实现,而是直接使用发布坐标解析到的 API:
suspend fun runAcceptanceChecks(): List<CheckResult> {
val results = mutableListOf<CheckResult>()
verify(results, "Ok / Err / nullable values") {
check(Ok(42).isOk)
check(Err("invalid").isErr)
check(Ok(null).isOk)
check(Err(null).isErr)
}
verify(results, "map / mapError / andThen") {
check(Ok(21).map { it * 2 } == Ok(42))
check(Err("bad").mapError { it.uppercase() } == Err("BAD"))
check(Ok(21).andThen { Ok(it * 2) } == Ok(42))
}
verify(results, "binding short-circuits on Err") {
var reached = false
val result: Result<Int, String> = binding {
val failed: Result<Int, String> = Err("stop")
failed.bind()
reached = true
42
}
check(result == Err("stop"))
check(!reached)
}
return results
}
同一套场景在 JVM 测试中运行;在 nativeApp 中通过 runBlocking 在 OHOS 原生目标执行,避免“JVM 通过但 Native 代码从未实际调用”的假通过。
4.3 验收范围
当前共享场景覆盖:
Ok、Err和可空值。map、mapError、andThen。- 成功绑定。
Err短路。runCatching和recover。combine和partition。coroutineBinding并发执行。- 协程错误传播。
runSuspendCatching保留取消异常。- Flow 的
filterOk/filterErr。
原生示例再增加 CMP 1.9.2 的 snapshot / derivedStateOf 检查,因此 ArkTS 首页预期显示 11 / 11 PASS。
第 5 阶段:Kotlin/Native 与 ArkTS 的桥接
5.1 为什么需要 N-API
OpenHarmony 应用页面使用 ArkTS,而验收逻辑运行在 Kotlin/Native。为了让 ArkTS 触发 Kotlin 代码,需要一条清晰的 ABI 边界:
ArkTS Index.ets
│ import runChecks from libentry.so
▼
C++ N-API module (entry)
│ ResultSampleRunChecks / ResultSampleFree
▼
Kotlin/Native libkn.so
│ runBlocking { runAcceptanceChecks() }
▼
JSON string -> ArkTS JSON.parse -> page state
库本身不暴露 ArkTS API;只有 example/nativeApp 的验收动态库暴露两个 C 入口,用于运行检查和释放返回字符串。
5.2 Kotlin/Native 导出函数
@CName("ResultSampleRunChecks")
fun runNativeChecks(): CPointer<ByteVar> {
val checks = try {
runBlocking { runAcceptanceChecks() }.toMutableList()
} catch (error: Throwable) {
mutableListOf(
CheckResult("Native acceptance runner", false, error.toString())
)
}
val json = checks.joinToString(prefix = "[", postfix = "]") {
"{\"name\":${quote(it.name)}," +
"\"passed\":${it.passed}," +
"\"detail\":${quote(it.detail)}}"
}
val bytes = json.encodeToByteArray()
val buffer = nativeHeap.allocArray<ByteVar>(bytes.size + 1)
bytes.forEachIndexed { index, byte -> buffer[index] = byte }
buffer[bytes.size] = 0
return buffer
}
@CName("ResultSampleFree")
fun freeNativeChecks(result: CPointer<ByteVar>?) {
if (result != null) nativeHeap.free(result)
}
这里没有把 Kotlin 对象直接暴露给 C++,而是把验收结果序列化为 UTF-8 JSON。这样 C ABI 只有字符串指针和释放函数,ArkTS 侧不需要理解 Kotlin/Native 的对象布局。
5.3 C++ N-API 包装
static napi_value RunChecks(napi_env env, napi_callback_info) {
auto text = ResultSampleRunChecks();
napi_value result = nullptr;
const auto status = napi_create_string_utf8(
env,
reinterpret_cast<const char*>(text),
NAPI_AUTO_LENGTH,
&result);
ResultSampleFree(text);
if (status != napi_ok) {
napi_throw_error(env, nullptr, "Unable to create acceptance report");
return nullptr;
}
return result;
}
N-API 在复制 JSON 字符串后立即调用 ResultSampleFree,避免 Kotlin/Native 堆内存泄漏。
5.4 ArkTS 页面只负责显示结果
import { runChecks } from 'libentry.so';
interface CheckResult {
name: string;
passed: boolean;
detail: string;
}
@Entry
@Component
struct Index {
@State private results: CheckResult[] = [];
@State private summary: string = '准备运行';
aboutToAppear(): void {
this.runAcceptance();
}
private runAcceptance(): void {
const checks = JSON.parse(runChecks()) as CheckResult[];
this.results = checks;
this.summary = `${checks.filter((item: CheckResult) => item.passed).length} / ` +
`${checks.length} PASS(通过)`;
}
}
ArkTS 页面不重新实现任何 Result 逻辑,只负责调用动态库、解析 JSON 和渲染状态。这让验收结果来自真实 Kotlin 库,而不是一套重复的 ArkTS 模拟代码。
5.5 共享库导出表
指定的 Kotlin/Native 链接器可能把可执行程序启动对象带入共享库。示例使用显式 export map,只开放两个 C 入口:
{
global:
ResultSampleRunChecks;
ResultSampleFree;
local: *;
};
配合零 ELF 入口和 section garbage collection,可以移除不需要的 main() 依赖,最终动态库只保留验收桥接所需符号。
第 6 阶段:构建、签名和交付
6.1 一键构建
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本依次完成:
- 两个库发布到
mavenLocal()。 - 两个库的 JVM 测试。
- 两个库的
ohosArm64主代码编译。 - 独立示例的 JVM 测试。
nativeApp:prepareOhos,将libkn.so和头文件复制到 ArkTS 工程。
核心 KLIB 位于:
kotlin-result/build/classes/kotlin/ohosArm64/main/klib/
kotlin-result-coroutines/build/classes/kotlin/ohosArm64/main/klib/
示例生成文件位于:
example/ohosApp/entry/libs/arm64-v8a/libkn.so
example/ohosApp/entry/src/main/cpp/include/libkn_api.h
动态库和生成头文件已加入忽略规则,不纳入源码提交。
6.2 DevEco 路径和签名工程
DevEco 对包含 &、非 ASCII 等字符的路径比较敏感。仓库提供复制脚本:
python3 scripts/prepare-signing-project.py \
"$HOME/ohos_kotlin_result_signing"
./scripts/build-hap.sh "$HOME/ohos_kotlin_result_signing"
复制脚本会:
- 复制 ArkTS、C++、资源和 HAP 配置。
- 排除
build、.hvigor、oh_modules、node_modules和本机证书文件。 - 如果目标目录已经有
build-profile.json5,保留目标配置,避免覆盖签名设置。
仓库中的 build-profile.json5 只保留安全模板,不包含 p12、p7b、证书路径、私钥或密码。签名必须在 DevEco 的 File > Project Structure > Project > Signing Configs 中由使用者配置。
6.3 HAP 构建
DEVECO_HOME="/path/to/DevEco-Studio.app/Contents" \
DEVECO_SDK_HOME="/path/to/sdk" \
./scripts/build-hap.sh "$HOME/ohos_kotlin_result_signing"
脚本实际执行 ohpm install --all 和 hvigorw ... assembleHap。目标包名为 com.example.kotlinresult,目标和兼容 SDK 为 6.0.0(20),ABI 为 arm64-v8a。
四、完整架构对照
4.1 工程级架构
┌─────────────────────────────────────────────────────────┐
│ kotlin-result 根工程 │
│ │
│ kotlin-result │
│ ├─ commonMain / JVM / OHOS │
│ └─ Result<V, E>、binding、集合和转换 │
│ │
│ kotlin-result-coroutines │
│ ├─ commonMain / JVM / OHOS │
│ └─ coroutineBinding、取消、并行和 Flow │
│ │
│ publishToMavenLocal │
└──────────────────────┬──────────────────────────────────┘
│ Maven coordinates
▼
┌─────────────────────────────────────────────────────────┐
│ example 独立消费工程 │
│ │
│ shared │
│ └─ 10 个共享验收场景 + JVM 测试 │
│ │
│ nativeApp │
│ └─ ohosArm64 + CMP runtime + C ABI │
│ │
│ ohosApp │
│ └─ C++ N-API + ArkTS 页面 + HAP │
└─────────────────────────────────────────────────────────┘
4.2 运行时架构
ArkTS Index.ets
│ runChecks()
▼
C++ N-API entry
│ ResultSampleRunChecks()
▼
Kotlin/Native libkn.so
│ runBlocking { runAcceptanceChecks() }
├─ Result core checks
├─ Coroutine checks
└─ CMP snapshot / derivedStateOf check
│ JSON + ResultSampleFree()
▼
ArkTS State -> 11 / 11 PASS
4.3 关键文件清单
| 文件 | 职责 |
|---|---|
buildSrc/src/main/kotlin/kotlin-conventions.gradle.kts | 统一 KMP 目标、JDK、focused build 和测试约定。 |
buildSrc/src/main/kotlin/publish-conventions.gradle.kts | KMP Maven 发布、sources/Javadoc 和签名开关。 |
kotlin-result-coroutines/build.gradle.kts | OHOS 协程版本及核心库依赖。 |
example/shared/src/commonMain/.../AcceptanceChecks.kt | 共享 Result/协程/Flow 验收场景。 |
example/nativeApp/src/ohosArm64Main/.../NativeChecks.kt | 原生入口、CMP runtime 检查和 JSON 报告。 |
example/nativeApp/src/ohosArm64Main/linker/shared-library.map | 控制动态库导出符号。 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | C++ N-API 模块和内存释放。 |
example/ohosApp/entry/src/main/ets/pages/Index.ets | ArkTS 验收页面和结果展示。 |
scripts/build-openharmony.sh | 发布、测试、编译和准备动态库。 |
scripts/prepare-signing-project.py | 生成路径合规的 DevEco 签名工程。 |
scripts/build-hap.sh | 安装 OHPM 依赖并构建 HAP。 |
scripts/check-native-deps.py | 检查 ARM64 ELF 和未解析强符号。 |
4.4 KMP/CMP 与传统平台的对应关系
| 能力 | 本项目 OpenHarmony 实现 | 常见平台对应物 |
|---|---|---|
| 多平台业务代码 | commonMain + ohosArm64 | Android/iOS Native source set |
| 原生二进制 | Kotlin/Native libkn.so | .so / .framework |
| 应用桥接 | C++ N-API | JNI / Objective-C bridge |
| UI 验收页面 | ArkTS Stage application | Android View/Compose / iOS UIKit/SwiftUI |
| 异步错误处理 | coroutineBinding + runSuspendCatching | Kotlin coroutine common API |
| 共享状态验证 | CMP Snapshot / derivedStateOf | Compose runtime 状态系统 |
五、关键决策说明
决策 1:在公共构建约定中添加目标
背景:如果只在 example/nativeApp 中声明 ohosArm64(),库模块本身不会产出可消费的 OHOS KLIB。
决策:把目标放到 buildSrc 的共享约定中,两个发布模块自然获得同一套目标和编译规则。
结果:库、协程扩展和独立消费工程都使用同一个目标名、同一个 JDK 和同一个 focused build 开关。
决策 2:OHOS 依赖只在 OHOS source set 覆盖
背景:社区鸿蒙协程包使用不同发行号,直接替换 version catalog 会污染 JVM 和其他平台。
决策:普通平台保持 1.10.2,只在 ohosArm64Main/ohosArm64Test 使用严格版本 1.10.2-1.0.0。
结果:跨平台模块仍保持上游依赖图,鸿蒙目标拥有匹配的 Native 产物。
决策 3:独立消费工程必须通过 Maven 坐标
背景:项目依赖会绕开发布 metadata、版本号和 KLIB 解析,无法证明实际接入体验。
决策:先 publishToMavenLocal,再让 example/shared 通过坐标引用两个库。
结果:JVM、OHOS 编译和动态库准备都经过真实的发布产物链路。
决策 4:将 C ABI 限制为两个入口
背景:直接把 Kotlin/Native 对象暴露给 C++ 会引入复杂的生命周期和类型布局问题。
决策:只导出 ResultSampleRunChecks 和 ResultSampleFree,用 JSON 作为稳定边界。
结果:ArkTS 不需要理解 Kotlin 对象,只处理字符串和页面状态;Native 内存由桥接层明确释放。
决策 5:把“编译通过”和“设备通过”分开记录
背景:Kotlin/Native 主代码编译、动态库链接、HAP 打包和签名安装验证的是不同层次。
决策:验证记录分别登记 JVM 测试、OHOS 编译、Maven 消费、ELF 审计、HAP 构建和设备执行。
结果:当前记录可以明确说明哪些已完成、哪些仍等待签名和设备。
决策 6:签名工程与源码工程分离
背景:DevEco 会生成本机证书路径和密码,直接提交会泄露凭据,也会让其他机器无法打开工程。
决策:源码保存无凭据模板,使用 prepare-signing-project.py 复制到独立目录,再在 DevEco 中配置签名。
结果:仓库可复用,签名材料只存在于本机签名目录。
六、测试与验证
6.1 验证环境
| 项目 | 版本或状态 |
|---|---|
| 主机 | macOS ARM64 |
| JDK | Android Studio bundled JBR 21.0.8 |
| Kotlin | 2.2.21-1.0.0 |
| Compose Multiplatform | 1.9.2-1.0.0 |
| Gradle | 8.14.3 |
| OHOS target/compatible API | 6.0.0(20) |
| DevEco Hvigor 插件 | 6.26.4 |
| 本机 compile SDK | 26.0.0.105 |
| Kotlin/Native sysroot | 社区发行包自带 sysroot 6.0.2.640 |
6.2 JVM 和独立消费测试
export JAVA_HOME="/path/to/jdk-21"
./gradlew -PopenharmonyOnly=true \
:kotlin-result:jvmTest \
:kotlin-result-coroutines:jvmTest
(cd example && ./gradlew -PopenharmonyOnly=true :shared:jvmTest)
当前验证记录:
| 检查 | 结果 |
|---|---|
| 核心库 JVM 测试 | 288 passed,0 failed,0 skipped |
| 协程库 JVM 测试 | 99 passed,0 failed,0 skipped |
| 独立示例 JVM 测试 | 1 个测试通过,内部覆盖 10 个验收场景 |
6.3 OHOS 编译和发布
./gradlew -PopenharmonyOnly=true -PsignPublications=false \
:kotlin-result:compileKotlinOhosArm64 \
:kotlin-result-coroutines:compileKotlinOhosArm64 \
:kotlin-result:compileTestKotlinOhosArm64 \
:kotlin-result-coroutines:compileTestKotlinOhosArm64 \
:kotlin-result:publishToMavenLocal \
:kotlin-result-coroutines:publishToMavenLocal
两个库的主代码和测试代码均完成 OHOS 编译;测试代码是编译检查,不能等同于设备执行。
6.4 HAP 和原生依赖检查
独立示例可以准备动态库并构建 HAP:
(cd example && ./gradlew -PopenharmonyOnly=true \
:shared:jvmTest :nativeApp:prepareOhos)
python3 scripts/prepare-signing-project.py \
"$HOME/ohos_kotlin_result_signing"
./scripts/build-hap.sh "$HOME/ohos_kotlin_result_signing"
对解压后的 HAP 做 ELF 审计:
python3 scripts/check-native-deps.py \
/path/to/sdk/20/native \
/path/to/extracted-hap/libs/arm64-v8a
当前验证记录显示:3 个 ARM64 动态库的强符号依赖均可解析。这个静态检查不能替代签名后的设备运行。
6.5 当前结论
已完成:
- 核心库和协程库 JVM 测试。
- 两个库的 OHOS 主代码和测试代码编译。
- 本地 Maven 发布和独立消费者解析。
- CMP runtime 动态库构建。
- 未签名 HAP 构建。
- ARM64 ELF 和原生依赖审计。
待完成:
- 使用真实签名配置生成签名 HAP。
- 安装到 API 20 设备并运行 ArkTS 页面。
- 确认首页显示
11 / 11 PASS,点击“重新运行”及前后台切换结果一致。
不能把 JVM 测试通过或未签名 HAP 构建写成真机已经通过。
七、运行效果和交付物
7.1 ArkTS 验收页面
页面启动后调用 N-API,显示:
kotlin-result(结果处理库)
OpenHarmony acceptance example / OpenHarmony 验收示例
KMP 2.2.21 · CMP 1.9.2
HarmonyOS 6.0.0 · API 20
11 / 11 PASS(通过)
实际运行效果如下,页面顶部显示 KMP 2.2.21、CMP 1.9.2 和 HarmonyOS API 20,随后列出每个 Result、协程和 CMP runtime 检查项:

截图中的 11 / 11 PASS(通过) 表示共享库的 10 个验收场景与额外的 CMP snapshot / derivedStateOf 场景全部通过。点击“Run again / 重新运行”会重新调用同一个 Native 入口并刷新页面结果。
每一项检查会列出名称和详情;失败时详情会显示异常消息,方便区分 Result 逻辑、协程、CMP runtime 或 Native bridge 问题。
7.2 交付物位置
| 交付物 | 位置 |
|---|---|
| 核心 KLIB | kotlin-result/build/classes/kotlin/ohosArm64/main/klib/ |
| 协程 KLIB | kotlin-result-coroutines/build/classes/kotlin/ohosArm64/main/klib/ |
| Native 动态库 | example/ohosApp/entry/libs/arm64-v8a/libkn.so |
| Native 头文件 | example/ohosApp/entry/src/main/cpp/include/libkn_api.h |
| 未签名 HAP | 签名工程的 entry/build/default/outputs/default/ |
| JVM 测试报告 | 各模块 build/reports/tests/ |
| 验证记录 | docs/openharmony/VALIDATION.md |
7.3 迁移后的仓库地址
源码、问题反馈、克隆和推送统一使用:
https://atomgit.com/oh-tpc/ohos_kotlin-result
已有本地副本可执行:
git remote set-url origin https://atomgit.com/oh-tpc/ohos_kotlin-result.git
git fetch origin
八、遗留问题与改进方向
8.1 实际踩坑复盘
| # | 踩坑点 | 现象 | 根因与解决方式 |
|---|---|---|---|
| 1 | 把 OHOS 当作普通 Native target | 库能配置但找不到兼容的 Native 编译器或 sysroot | OpenHarmony 需要指定社区发行包和 Maven 下载开关;固定 KMP、Native、Gradle 版本并配置 kotlin.native.distribution.downloadFromMaven=true。 |
| 2 | 把 Kotlin 2.4 配置直接带入 Kotlin 2.2.21 | @IgnorableReturnValue 或 return-value-checker 参数无法识别 | 移除版本专属注解和编译参数,保留 API 语义。 |
| 3 | 把 OHOS 协程版本写进公共依赖 | JVM/其他平台解析到不存在或不匹配的发行包 | 只在 ohosArm64Main 和 ohosArm64Test 严格覆盖 1.10.2-1.0.0。 |
| 4 | 用项目依赖代替发布坐标 | 根工程通过,但真实消费者无法解析 metadata/KLIB | example/ 独立构建,必须先 publishToMavenLocal 再消费。 |
| 5 | 只看 JVM 测试 | 业务逻辑通过,但 Native ABI 或 HAP 仍可能失败 | 增加 compileKotlinOhosArm64、动态库构建、N-API 和 ELF 审计。 |
| 6 | 直接提交 DevEco 签名配置 | 配置包含本机证书路径、私钥材料和密码 | 仓库只保留安全模板,签名使用独立目录和 DevEco 配置。 |
| 7 | 把 compile SDK 当成 target API | 文档声称使用 SDK 20 编译,与本机 DevEco 实际不符 | 明确 target/compatible API 为 20,compile SDK 使用本机 DevEco 版本。 |
| 8 | 直接把 Kotlin 对象传给 ArkTS | C ABI 类型和生命周期难以控制 | 只导出两个 C 函数,使用 JSON 字符串和显式 free 函数。 |
| 9 | 动态库导出符号过多 | 链接器带入启动对象或暴露不必要符号 | 使用 zero entry 和 shared-library.map,只导出验收入口。 |
| 10 | 把未签名 HAP 当成设备通过 | 构建成功但无法安装或启动 | 签名、安装和设备运行单独记录,最终以页面 11 / 11 PASS 为准。 |
| 11 | 使用 JDK 25 运行旧 Kotlin 编译器 | Kotlin/IntelliJ JavaVersion 解析 25.0.2 失败 | 使用 JDK 21 运行 Gradle;当前验证采用 Android Studio JBR 21.0.8。 |
8.2 已知边界
- 尚未发布公共 Maven 版本:当前接入需要先发布到
mavenLocal(),后续可再提供公共仓库坐标。 - 测试代码未在设备执行:OHOS test compilation 已通过,设备侧当前通过独立 nativeApp 验收逻辑进行验证。
- 设备验收依赖签名:签名证书和设备不随仓库提供,必须由使用者在 DevEco 中配置。
- 只支持 ARM64:当前目标为
ohosArm64,没有提供 OHOS x86 或其他 ABI。 - CMP 只验证 runtime 状态:示例页面使用 ArkTS,不覆盖 CMP/Skiko UI 渲染能力。
8.3 后续方向
- 发布可供消费的公共 Maven 版本并补充版本迁移说明。
- 在真实 API 20 设备完成签名、安装、运行和前后台切换记录。
- 将 Native bridge 验收扩展为可复用的第三方集成示例。
- 根据 OpenHarmony Kotlin/Native 发行包变化更新 Kotlin、协程和 CMP 版本矩阵。
- 在不改变公共 Result API 的前提下补充更多 OHOS 目标和 ABI。
- 将构建、ELF 审计和签名交接纳入持续集成的分层检查。
九、总结
9.1 三个核心难点
| 难点 | 本质 | 解法 |
|---|---|---|
| 目标与工具链对齐 | OpenHarmony 目标、KMP 插件、Native sysroot 和 Gradle 版本必须匹配 | ohosArm64() + 社区发行包 + 固定版本矩阵 |
| 发布产物可消费 | 根工程通过不代表独立工程能解析 KLIB 和 metadata | publishToMavenLocal + 独立 example/ 消费验证 |
| Kotlin/Native 到 ArkTS | 两种运行时之间没有可直接共享的 Kotlin 对象 | C ABI + N-API + JSON + 显式内存释放 |
9.2 适配层次
应用侧
└─ ArkTS 页面:调用 runChecks,展示 11 项状态
桥接侧
└─ C++ N-API:加载 libkn.so,复制 JSON,释放 Native 内存
原生侧
└─ Kotlin/Native:执行共享 Result/协程/CMP 验收逻辑
库侧
└─ commonMain:保留原有 Result API 和实现
构建侧
└─ buildSrc:目标、版本、发布、focused build 和签名交接
9.3 三条经验
- 先固定版本矩阵,再修改目标配置:Kotlin、KMP、协程、CMP 和 DevEco 的版本关系比单个编译错误更重要。
- 用独立消费者验证发布边界:只有从
mavenLocal()解析坐标,才能发现 metadata、KLIB 和目标依赖问题。 - 把构建、签名、设备运行分层记录:每一层都能通过不代表下一层一定能通过,尤其是 Native ABI 和设备安装。
9.4 适配成果
当前仓库已经完成:
kotlin-result和kotlin-result-coroutines的 OpenHarmony ARM64 目标适配。- Kotlin 2.2.21、CMP 1.9.2 和 OHOS 协程发行包的版本对齐。
- 独立 Maven 消费工程和 Kotlin/Native 验收动态库。
- ArkTS + C++ N-API 示例应用。
- JVM、OHOS 编译、HAP、ELF 依赖审计和双语文档。
- AtomGit 仓库迁移后的 clone、remote、issue 和协作地址统一。
项目地址:https://atomgit.com/oh-tpc/ohos_kotlin-result
参考文档
- kotlin-result OpenHarmony 仓库
- OpenHarmony Kotlin Multiplatform 分支
- OpenHarmony Compose Multiplatform 分支
- OpenHarmony 应用开发文档
- OpenHarmony N-API 文档
- OpenHarmony ArkTS 文档
环境:Kotlin 2.2.21-1.0.0 · Compose Multiplatform 1.9.2-1.0.0 · Kotlin Coroutines 1.10.2-1.0.0(OHOS)· HarmonyOS API 20 · Gradle 8.14.3 · JDK 21
更多推荐

所有评论(0)