开源鸿蒙平台 KMP_CMP 三方库「kotlinx.coroutines」适配全流程
本文记录
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 包含:
| 字段 | 作用 |
|---|---|
id | launch、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 Multiplatform | OpenHarmony focused build 使用 2.2.21-1.0.0 |
| JDK | 21 |
| OpenHarmony 目标 | ohosArm64 |
| DevEco product | 6.0.0(20) |
| ABI | arm64-v8a |
| Bundle | org.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.kts | ohosArm64 目标和 focused source set |
buildSrc/src/main/kotlin/Projects.kt | OpenHarmony 版本和属性读取 |
example/shared/.../CoroutineSamples.kt | 四个协程场景、JSON 和八项检查 |
example/nativeApp/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/.../napi_init.cpp | N-API 导出和参数检查 |
example/ohosApp/.../Index.ets | ArkUI 运行页面 |
scripts/build-openharmony.sh | 核心库、Native 链接和产物准备 |
scripts/build-hap.sh | 签名工程的 HAP 构建 |
docs/openharmony/VALIDATION.md | 构建和真机验收记录 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| KMP | sampleCatalog | 生成四项能力目录 |
| KMP | runSample | 执行场景并返回事件序列 |
| KMP | runAcceptanceChecks | 执行八项检查 |
| Native | CoroutinesCatalog | 返回目录 JSON |
| Native | CoroutinesGet | 返回运行结果 JSON |
| Native | CoroutinesRunChecks | 返回检查结果 JSON |
| Native | CoroutinesFree | 释放 Native 字符串 |
| N-API | getCatalog / getSample | 向 ArkTS 暴露数据 |
| ArkTS | runSelected | 触发示例并刷新 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 功能用例
- 启动后自动加载目录并运行
launch; - 切换四种协程能力,确认标题、说明和事件列表更新;
- 清空事件后重新运行;
- 使用前后导航循环切换能力;
- 运行期间按钮显示“运行中…”并禁用;
- 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 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须真正编译
ohosArm64动态库; - N-API 不负责业务判断,C++ 只做类型检查、字符串转换和释放;
- Native 返回值交给 N-API 后必须调用
CoroutinesFree; - 运行结果要来自真实 Native 调用,而不是页面静态文字;
- 签名工程必须与源码分离,证书不能进入 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 三条经验
- 先让共享协程场景在 KMP 和 Native 通过,再接入 ArkUI 或 CMP UI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递,并明确字符串释放责任;
- 分别记录自动测试、Native 链接、HAP 构建和真机运行,避免把编译成功误认为设备链路可用。
9.4 适配成果
当前适配已完成:
kotlinx-coroutines-core的ohosArm64focused build;- JVM 和 OpenHarmony ARM64 共用的四个协程场景;
- 四个 C ABI 符号和 C++ N-API;
- DevEco Stage ArkUI 验收页面;
- HAP 构建、签名工程准备、设备安装和 Mate 60 Pro 真机验证;
- AtomGit 项目文档、OpenHarmony README 和本地效果图。
参考文档
更多推荐

所有评论(0)