本文记录 kotlin-resultkotlin-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 多平台库。核心模块提供 OkErrmapandThenbindingrecover、集合操作等 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 目标。
依赖隔离仅在 ohosArm64MainohosArm64Test 中覆盖鸿蒙协程版本。
消费体验库先发布到本地 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 / Native2.2.21-1.0.0
Kotlin Multiplatform 鸿蒙分支main-2.2.21-OH
Compose Multiplatform runtime1.9.2-1.0.0
Compose Multiplatform 鸿蒙分支main-1.9.2-OH
Kotlin Coroutines 普通平台1.10.2
Kotlin Coroutines OHOS1.10.2-1.0.0
HarmonyOS target / compatible API6.0.0(20)
Gradle8.14.3
JDK21

这里要区分 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 目标继续保留
    }
}

这段配置有两个要点:

  1. ohosArm64() 是两个库共享的正式目标,不在示例中临时拼接。
  2. openharmonyOnly 只影响配置阶段,普通构建仍保留上游目标,避免为了鸿蒙适配破坏原有多平台工程。
2.2 为什么需要 focused build

Kotlin/Native 会根据目标下载对应的编译器、LLVM 和 sysroot。开发阶段如果每次都配置全部平台,构建会浪费时间,也更容易被无关目标的工具链问题阻塞。因此提供:

./gradlew -PopenharmonyOnly=true :kotlin-result:tasks

根工程在此模式下只 include kotlin-resultkotlin-result-coroutines;普通模式仍 include benchmarksjvm-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()
    }
}

两个库的消费声明放在 sharedcommonMain

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 验收范围

当前共享场景覆盖:

  1. OkErr 和可空值。
  2. mapmapErrorandThen
  3. 成功绑定。
  4. Err 短路。
  5. runCatchingrecover
  6. combinepartition
  7. coroutineBinding 并发执行。
  8. 协程错误传播。
  9. runSuspendCatching 保留取消异常。
  10. 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

脚本依次完成:

  1. 两个库发布到 mavenLocal()
  2. 两个库的 JVM 测试。
  3. 两个库的 ohosArm64 主代码编译。
  4. 独立示例的 JVM 测试。
  5. 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.hvigoroh_modulesnode_modules 和本机证书文件。
  • 如果目标目录已经有 build-profile.json5,保留目标配置,避免覆盖签名设置。

仓库中的 build-profile.json5 只保留安全模板,不包含 p12p7b、证书路径、私钥或密码。签名必须在 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 --allhvigorw ... 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.ktsKMP Maven 发布、sources/Javadoc 和签名开关。
kotlin-result-coroutines/build.gradle.ktsOHOS 协程版本及核心库依赖。
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.cppC++ N-API 模块和内存释放。
example/ohosApp/entry/src/main/ets/pages/Index.etsArkTS 验收页面和结果展示。
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 + ohosArm64Android/iOS Native source set
原生二进制Kotlin/Native libkn.so.so / .framework
应用桥接C++ N-APIJNI / Objective-C bridge
UI 验收页面ArkTS Stage applicationAndroid View/Compose / iOS UIKit/SwiftUI
异步错误处理coroutineBinding + runSuspendCatchingKotlin coroutine common API
共享状态验证CMP Snapshot / derivedStateOfCompose 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++ 会引入复杂的生命周期和类型布局问题。

决策:只导出 ResultSampleRunChecksResultSampleFree,用 JSON 作为稳定边界。

结果:ArkTS 不需要理解 Kotlin 对象,只处理字符串和页面状态;Native 内存由桥接层明确释放。

决策 5:把“编译通过”和“设备通过”分开记录

背景:Kotlin/Native 主代码编译、动态库链接、HAP 打包和签名安装验证的是不同层次。

决策:验证记录分别登记 JVM 测试、OHOS 编译、Maven 消费、ELF 审计、HAP 构建和设备执行。

结果:当前记录可以明确说明哪些已完成、哪些仍等待签名和设备。

决策 6:签名工程与源码工程分离

背景:DevEco 会生成本机证书路径和密码,直接提交会泄露凭据,也会让其他机器无法打开工程。

决策:源码保存无凭据模板,使用 prepare-signing-project.py 复制到独立目录,再在 DevEco 中配置签名。

结果:仓库可复用,签名材料只存在于本机签名目录。


六、测试与验证

6.1 验证环境

项目版本或状态
主机macOS ARM64
JDKAndroid Studio bundled JBR 21.0.8
Kotlin2.2.21-1.0.0
Compose Multiplatform1.9.2-1.0.0
Gradle8.14.3
OHOS target/compatible API6.0.0(20)
DevEco Hvigor 插件6.26.4
本机 compile SDK26.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 交付物位置

交付物位置
核心 KLIBkotlin-result/build/classes/kotlin/ohosArm64/main/klib/
协程 KLIBkotlin-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 编译器或 sysrootOpenHarmony 需要指定社区发行包和 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/其他平台解析到不存在或不匹配的发行包只在 ohosArm64MainohosArm64Test 严格覆盖 1.10.2-1.0.0
4用项目依赖代替发布坐标根工程通过,但真实消费者无法解析 metadata/KLIBexample/ 独立构建,必须先 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 对象传给 ArkTSC 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 已知边界

  1. 尚未发布公共 Maven 版本:当前接入需要先发布到 mavenLocal(),后续可再提供公共仓库坐标。
  2. 测试代码未在设备执行:OHOS test compilation 已通过,设备侧当前通过独立 nativeApp 验收逻辑进行验证。
  3. 设备验收依赖签名:签名证书和设备不随仓库提供,必须由使用者在 DevEco 中配置。
  4. 只支持 ARM64:当前目标为 ohosArm64,没有提供 OHOS x86 或其他 ABI。
  5. 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 和 metadatapublishToMavenLocal + 独立 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 三条经验

  1. 先固定版本矩阵,再修改目标配置:Kotlin、KMP、协程、CMP 和 DevEco 的版本关系比单个编译错误更重要。
  2. 用独立消费者验证发布边界:只有从 mavenLocal() 解析坐标,才能发现 metadata、KLIB 和目标依赖问题。
  3. 把构建、签名、设备运行分层记录:每一层都能通过不代表下一层一定能通过,尤其是 Native ABI 和设备安装。

9.4 适配成果

当前仓库已经完成:

  • kotlin-resultkotlin-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 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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐