本文记录 kotlinx.coroutines 接入 OpenHarmony 的完整过程,覆盖 KMP 公共协程库盘点、ohosArm64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。

kotlinx.coroutines 是与 UI 无关的 KMP 基础库。本次适配让同一份公共实现进入 OpenHarmony ARM64 运行时,ArkUI 页面作为真机验收宿主,Compose Multiplatform(CMP)应用可以通过同一个 KMP 依赖使用这些协程 API。

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

开发工具: DevEco Studio

一、背景

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

kotlinx.coroutines 为 Kotlin 提供 CoroutineScope、Job、Deferred、Flow、Dispatchers 和结构化并发能力。KMP 项目通常把这些能力放在 commonMain,由 Android、iOS、桌面、Web 或其他 Native 宿主共享。OpenHarmony 应用同样需要这套公共能力,但默认构建并不会生成 OpenHarmony 的 Kotlin/Native 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明公共协程实现已经在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证 KMP 核心、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() 才能生成 OpenHarmony KLIB。
工具链不一致Kotlin/Native、Native SDK、LLVM 和 Gradle 插件需要匹配。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
调度器差异Dispatchers.Default 的实际执行线程由目标平台实现。
事件顺序验证启动、挂起、恢复、Flow 收集和取消清理需要可观察。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在 KMP 目标配置、C ABI/N-API 桥接和 ArkUI 验收层。协程语义仍由公共实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

示例提供四个可观察场景:

  • launch:启动子协程并等待完成;
  • async/await:跨越挂起点取得 Deferred 结果;
  • Flow:收集冷流并展示发射顺序;
  • 取消:取消子任务并验证 finally 清理。

CoroutineSample 包含:

字段作用
idlaunch、async、flow、cancel 四个稳定标识。
title页面标题。
description能力说明。
dispatcher实际 Dispatcher 字符串。
events本次运行事件序列。
revision运行版本,避免复用旧结果。

共享层还提供八项检查:launch/join、async/await、delay、Flow 收集、结构化并发、取消清理、默认 Dispatcher 和不可变 revision。

1.3 实现适配

维度要求
代码复用协程和 Flow 逻辑全部位于 KMP commonMain。
平台目标核心库和示例增加 ohosArm64,生成 ARM64 动态库。
桥接稳定使用少量 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址。
CMP 兼容CMP 应用复用同一 KMP 依赖;ArkUI 只负责真机验收。
UI 完整支持选择、运行、清空、前后切换和错误展示。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。CMP 工程可以直接依赖 OpenHarmony KMP 变体,在自己的 Compose 页面中使用标准协程 API。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点源集、示例边界和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、focused build 和独立 example
第 3 阶段:协程场景建模   ── 建立四个示例、事件序列和八项检查
第 4 阶段:原生桥接       ── C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── JSON 目录、运行结果、按钮状态和错误边界
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
kotlinx-coroutines-core/       公共协程实现和 ohosArm64 目标
example/shared/                KMP 示例、JSON 契约和 JVM 测试
example/nativeApp/             Kotlin/Native 动态库
example/ohosApp/               DevEco Stage、N-API 和 ArkTS 页面
scripts/                       focused build、签名工程和依赖检查
docs/openharmony/              验收记录、文章和效果图

example 是独立 Gradle 工程,不把 DevEco 工程作为根项目的 Kotlin 子模块,Gradle 和 Hvigor 可以分开执行。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformOpenHarmony focused build 使用 2.2.21-1.0.0
JDK21
OpenHarmony 目标ohosArm64
DevEco product6.0.0(20)
ABIarm64-v8a
Bundleorg.jetbrains.kotlinx.coroutines.sample
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 页面边界
标题区          KOTLINX COROUTINES / 协程能力工作台
能力区          launch / async/await / Flow / 取消
结果区          标题、说明、Dispatcher、运行次数、状态和事件序列
操作区          运行当前示例 / 清空事件
导航区          上一个能力 / 下一个能力

页面启动时通过 getCatalog() 读取目录,再自动运行首个 launch 示例。

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

2.1 加入 ohosArm64
kotlin {
    if (openHarmonyOnly) {
        ohosArm64()
    }

    sourceSets {
        groupSourceSets("concurrent", listOf("jvm", "native"), listOf("common"))
        if (project.nativeTargetsAreEnabled) {
            if (openHarmonyOnly) {
                groupSourceSets("nativeOther", listOf("ohosArm64"), listOf("native"))
            }
        }
    }
}

focused build 通过 openHarmonyOnly 缩小目标集合,避免加载不相关的 JS、Wasm 和其他 Native 任务。

2.2 构建核心库和 Native 产物
./gradlew -PopenharmonyOnly=true \
  -PsignPublications=false \
  -PDeployVersion=1.11.0-ohos.1 \
  :kotlinx-coroutines-core:compileKotlinOhosArm64 \
  :kotlinx-coroutines-core:publishKotlinMultiplatformPublicationToMavenLocal \
  :kotlinx-coroutines-core:publishOhosArm64PublicationToMavenLocal \
  :kotlinx-coroutines-bom:publishToMavenLocal

scripts/build-openharmony.sh 将核心库发布、示例 Native 编译和 prepareOhos 串起来,准备:

example/ohosApp/entry/libs/arm64-v8a/libkotlinx_coroutines.so
example/ohosApp/entry/src/main/cpp/include/libkotlinx_coroutines_api.h
2.3 独立消费工程
kotlin {
    jvm()
    jvmToolchain(21)
    ohosArm64()

    sourceSets {
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${rootProject.version}")
        }
    }
}

示例通过 mavenLocal() 消费 focused build 产物;发布后只需替换版本和仓库地址。

第 3 阶段:协程场景建模

3.1 JSON 契约
CoroutineSamples.kt
        ↓ CoroutineSample
runSample(index, revision)
        ↓ events + dispatcher + revision
sampleJson(index, revision)
        ↓ UTF-8 JSON
N-API getSample()
        ↓
ArkUI Index.ets

页面不复制协程业务判断,每个事件由公共 Kotlin 代码生成。

3.2 结果数据类
public data class CoroutineSample(
    val id: String,
    val title: String,
    val description: String,
    val dispatcher: String,
    val events: List<String>,
    val revision: Int,
)

目录包含四项能力:

private val catalog = listOf(
    "launch" to ("Launch and join" to "A child coroutine completes before its parent scope exits."),
    "async" to ("Async and await" to "A deferred value crosses a suspend boundary."),
    "flow" to ("Collect a Flow" to "A cold Flow is collected in a structured scope."),
    "cancel" to ("Cancellation" to "A child observes cancellation and releases its work."),
)
3.3 运行逻辑
coroutineScope {
    val job = launch(Dispatchers.Default) {
        result += "launch-start"
        yield()
        result += "launch-end"
    }
    job.join()
    result += "parent-complete"
}
val value = async(Dispatchers.Default) {
    delay(1)
    "async-result"
}.await()
listOf("deferred-start", value, "await-complete")
val values = flowOf("flow-1", "flow-2", "flow-3").toList()
listOf("collect-start") + values + "collect-complete"
val job: Job = launch {
    try {
        result += "work-start"
        awaitCancellation()
    } finally {
        result += "cleanup"
    }
}
yield()
job.cancelAndJoin()
result + "parent-complete"

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

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry
  │ const char*
  ▼
Kotlin/Native C ABI
  │ CoroutineSamples
  ▼
UTF-8 JSON + explicit free

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制状态语义,所以选择 C ABI + JSON。

4.2 Kotlin/Native 导出函数
@CName("CoroutinesCatalog")
public fun coroutinesCatalog(): CPointer<ByteVar> = textBuffer(catalogJson())

@CName("CoroutinesGet")
public fun coroutinesGet(index: Int, revision: Int): CPointer<ByteVar> =
    textBuffer(sampleJson(index, revision))

@CName("CoroutinesRunChecks")
public fun coroutinesRunChecks(): CPointer<ByteVar> = textBuffer(checksJson())

@CName("CoroutinesFree")
public fun coroutinesFree(value: CPointer<ByteVar>?) {
    if (value != null) nativeHeap.free(value.rawValue)
}

每个返回值都是 Native heap 分配的 C 字符串。C++ 创建 ArkTS 字符串后立即调用 CoroutinesFree。

4.3 C++ N-API 方法分发
napi_property_descriptor methods[] = {
    {"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr, napi_default, nullptr},
    {"getSample", nullptr, Sample, nullptr, nullptr, nullptr, napi_default, nullptr},
    {"runChecks", nullptr, Checks, nullptr, nullptr, nullptr, napi_default, nullptr},
};

getSample 检查下标和 revision 是否为非负整数。N-API 只负责参数类型、字符串创建和释放,不实现协程语义。

4.4 CMake 和类型声明
add_library(kotlinx_coroutines SHARED IMPORTED)
set_target_properties(kotlinx_coroutines PROPERTIES
  IMPORTED_LOCATION
  "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libkotlinx_coroutines.so")

add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE kotlinx_coroutines libace_napi.z.so)
export const getCatalog: () => string;
export const getSample: (index: number, revision: number) => string;
export const runChecks: () => string;

第 5 阶段:ArkUI 宿主

5.1 加载目录和运行结果
import { getCatalog, getSample } from 'libentry.so';

this.catalogItems = JSON.parse(getCatalog()) as CoroutineSample[];
this.showSelection();
this.runSelected();

运行当前能力:

this.sample = JSON.parse(getSample(this.sampleIndex, currentRevision)) as CoroutineSample;
this.runCount += 1;
this.revision = currentRevision === 2147483647 ? 0 : currentRevision + 1;
5.2 页面交互

页面支持 launch、async/await、Flow 和“取消”,另有运行、清空、前一个和后一个按钮。所有事件来自共享 Kotlin 结果,ArkTS 不重新实现事件顺序。

5.3 对比度和错误边界

选中能力使用深蓝色和白字,浅色按钮使用深色文字,正文和辅助文字使用更深的灰色,运行中使用蓝色,完成状态使用绿色。运行期间按钮禁用,Native 异常显示在页面底部。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── shared/src/commonMain/.../CoroutineSamples.kt
├── shared/src/commonTest/.../CoroutineSamplesTest.kt
├── nativeApp/src/ohosArm64Main/.../NativeBridge.kt
└── ohosApp/
    ├── entry/src/main/cpp/CMakeLists.txt
    ├── entry/src/main/cpp/napi_init.cpp
    └── entry/src/main/ets/pages/Index.ets
6.2 构建、签名和安装
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh

python3 scripts/prepare-signing-project.py \
  "$HOME/kotlinx_coroutines_openharmony_signing"

./scripts/build-hap.sh "$HOME/kotlinx_coroutines_openharmony_signing"
hdc list targets -v
hdc -t <设备序列号> install -r \
  entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b org.jetbrains.kotlinx.coroutines.sample

四、完整代码对照

4.1 整体架构

CoroutineSamples.kt
    │ launch / async / Flow / cancellation
    ▼
NativeBridge.kt
    │ CoroutinesCatalog / CoroutinesGet / CoroutinesRunChecks / CoroutinesFree
    ▼
libentry.so
    │ getCatalog / getSample / runChecks
    ▼
Index.ets
    │ JSON parse + state update
    ▼
ArkUI 真机页面

4.2 文件清单

文件职责
kotlinx-coroutines-core/build.gradle.ktsohosArm64 目标和 focused source set
buildSrc/src/main/kotlin/Projects.ktOpenHarmony 版本和属性读取
example/shared/.../CoroutineSamples.kt四个协程场景、JSON 和八项检查
example/nativeApp/.../NativeBridge.ktC ABI、JSON 返回和内存释放
example/ohosApp/.../napi_init.cppN-API 导出和参数检查
example/ohosApp/.../Index.etsArkUI 运行页面
scripts/build-openharmony.sh核心库、Native 链接和产物准备
scripts/build-hap.sh签名工程的 HAP 构建
docs/openharmony/VALIDATION.md构建和真机验收记录

4.3 关键 API 对照

层次API作用
KMPsampleCatalog生成四项能力目录
KMPrunSample执行场景并返回事件序列
KMPrunAcceptanceChecks执行八项检查
NativeCoroutinesCatalog返回目录 JSON
NativeCoroutinesGet返回运行结果 JSON
NativeCoroutinesRunChecks返回检查结果 JSON
NativeCoroutinesFree释放 Native 字符串
N-APIgetCatalog / getSample向 ArkTS 暴露数据
ArkTSrunSelected触发示例并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

this.sample = JSON.parse(getSample(this.sampleIndex, currentRevision)) as CoroutineSample;
public fun sampleJson(index: Int, revision: Int): String = runSample(index, revision).toJson()

两者之间只传输整数和 JSON,不传输 Kotlin 对象或未校验的动态结构。

五、关键决策说明

决策 1:把 ohosArm64 加入核心构建约定

只有真正链接 OpenHarmony 动态库,才能证明共享协程实现进入 ARM64 运行时。

决策 2:focused build 只加载所需目标

完整仓库包含 JVM、JS、Wasm 和其他 Native 任务,通过 openHarmonyOnly 缩小目标集合,可以减少工具链冲突。

决策 3:独立消费者通过构建产物消费

example/shared 消费本地 Maven 产物,ohosApp 接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 打包。

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

JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加字段而不暴露内部对象布局。

决策 5:ArkUI 作为验收宿主,CMP 复用 KMP API

当前页面验证 OpenHarmony Native 链路,CMP 应用则直接依赖相同 KMP API,在自己的 Compose UI 中启动协程。

六、测试与验证

6.1 测试环境

本次真机验证使用 macOS、JDK 21、DevEco Studio、OpenHarmony ARM64 Native SDK 和 HUAWEI Mate 60 Pro,设备序列号为 FMR0223825079397。

6.2 共享样例测试

cd example
./gradlew :shared:jvmTest

测试验证目录数量、revision、JSON 内容和八项 runAcceptanceChecks。

6.3 HAP 验证

CompileArkTS: finished
PackageHap: finished
SignHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

6.4 功能用例

  1. 启动后自动加载目录并运行 launch;
  2. 切换四种协程能力,确认标题、说明和事件列表更新;
  3. 清空事件后重新运行;
  4. 使用前后导航循环切换能力;
  5. 运行期间按钮显示“运行中…”并禁用;
  6. Native 异常显示在页面错误区域。

6.5 验证结论

Native 动态库、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在 HUAWEI Mate 60 Pro 上成功安装并进入前台,页面展示了真实协程事件顺序、Dispatcher、revision 和运行状态。

七、运行效果

7.1 真机截图

在这里插入图片描述

截图中可以看到:

  • 页面标题为“协程能力工作台”;
  • 顶部能力选择包含 launch、async/await、Flow 和“取消”;
  • 当前示例为 Launch and join,右侧显示 1/4;
  • Dispatcher 区域显示实际信息;
  • 页面展示“已完成第 1 次运行”和真实事件顺序;
  • 主按钮、清空按钮和前后导航按钮可直接操作;
  • 浅色按钮和辅助文字保持较高对比度。

7.2 命令速查

git clone https://atomgit.com/oh-tpc/kotlinx.coroutines.git
cd kotlinx.coroutines
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
python3 scripts/prepare-signing-project.py \
  "$HOME/kotlinx_coroutines_openharmony_signing"
./scripts/build-hap.sh "$HOME/kotlinx_coroutines_openharmony_signing"

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须真正编译 ohosArm64 动态库;
  2. N-API 不负责业务判断,C++ 只做类型检查、字符串转换和释放;
  3. Native 返回值交给 N-API 后必须调用 CoroutinesFree;
  4. 运行结果要来自真实 Native 调用,而不是页面静态文字;
  5. 签名工程必须与源码分离,证书不能进入 AtomGit。

8.2 已知问题

  • Kotlin/Native 工具链和 DevEco SDK 需要匹配;
  • Dispatchers.Default 线程名称和日志格式由运行时决定;
  • 当前 ArkUI 示例是单页面宿主,不包含 CMP UI;
  • HAP 安装必须使用与 bundle name 匹配的签名;
  • JSON 目前由示例手动生成,生产业务可替换为序列化库。

8.3 未来优化方向

  • 将结果封装为 Flow<CoroutineSample>,演示 CMP 状态层消费;
  • 增加 Compose Multiplatform 示例宿主;
  • 增加 Native bridge 版本字段和 ABI 兼容检查;
  • 在 CI 中加入 Native 链接、ELF 检查和 HAP 构建;
  • 补充不同设备 ABI、API 版本和 Dispatcher 行为的验收矩阵。

九、总结

9.1 核心难点回顾

KMP commonMain 协程实现
    → ohosArm64 Kotlin/Native
    → C ABI
    → C++ N-API
    → ArkTS JSON 解析
    → ArkUI 真机页面

9.2 封装层次

  • KMP 层:提供 CoroutineScope、Job、Deferred、Flow、Dispatcher 和取消语义;
  • Native 层:生成 ARM64 动态库并通过有限 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理目录、运行状态、错误文本和页面交互;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 先让共享协程场景在 KMP 和 Native 通过,再接入 ArkUI 或 CMP UI;
  2. 用 JSON 和少量 C ABI 代替跨语言对象传递,并明确字符串释放责任;
  3. 分别记录自动测试、Native 链接、HAP 构建和真机运行,避免把编译成功误认为设备链路可用。

9.4 适配成果

当前适配已完成:

  • kotlinx-coroutines-core 的 ohosArm64 focused build;
  • JVM 和 OpenHarmony ARM64 共用的四个协程场景;
  • 四个 C ABI 符号和 C++ N-API;
  • DevEco Stage ArkUI 验收页面;
  • HAP 构建、签名工程准备、设备安装和 Mate 60 Pro 真机验证;
  • AtomGit 项目文档、OpenHarmony README 和本地效果图。

参考文档

Logo

一站式 AI 云服务平台

更多推荐