开源鸿蒙平台 KMP_CMP 三方库「kotlinx-io」适配全流程
本文记录
kotlinx-io接入 OpenHarmony KMP/CMP 示例的完整过程,覆盖Buffer、ByteString公共 API 盘点、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和设备验收。本次适配保持
kotlinx.io公共 API 不变,复用同一份 Kotlin 代码完成 UTF-8 写入、字节读取、往返校验和异常边界检查,再由 ArkTS 页面调用 OpenHarmony Native 动态库。这样验证的是共享 KMP 代码在 OpenHarmony ARM64 运行时的真实结果,而不是重新在页面里实现一套演示逻辑。
项目地址: AtomGit/oh-tpc/kotlinx-io
开发工具: 华为云码道
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
kotlinx-io 是 Kotlin Multiplatform 的字节流和 I/O 基础库,核心模块提供可读写的 Buffer、不可变的 ByteString,以及 Source、Sink 和文件系统 API。它的公共实现已经覆盖 JVM、Native、JS 和 Wasm 等平台,但 OpenHarmony 应用还需要一条能够被 ArkTS 页面消费的 Native 交付链路。
如果只把页面重新写成 ArkTS,页面虽然可以显示几个固定字符串,却无法证明 Buffer 的写入、ByteString 的字节计数和 UTF-8 往返逻辑真的运行在 KMP 代码中。本次适配需要同时解决以下问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 核心模块默认没有 OpenHarmony 目标,必须加入 ohosArm64() 才能生成对应 KLIB。 |
| 平台实现 | OpenHarmony 的路径和目录 API 不能直接复用 Linux glibc 实现,需要提供 core/ohosArm64 实现。 |
| 工具链不一致 | Kotlin/Native、OpenHarmony LLVM、Native SDK 和 Gradle 插件必须使用匹配版本。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin Buffer 或 ByteString,必须经过 C ABI、C++ N-API 和 JSON。 |
| 内存生命周期 | Native 返回的字符串需要显式释放,不能让 Kotlin/Native 指针直接留在 ArkTS 中。 |
| 交付链路复杂 | 动态库、CMake、HAP、签名、设备安装和 ARM64 依赖需要分别验证。 |
因此,本项目把适配边界放在三个地方:KMP 模块目标配置、Kotlin/Native C ABI/N-API 桥接层和 ArkUI 示例页面。Buffer 与 ByteString 的业务操作仍由共享 Kotlin 代码维护,ArkTS 只负责调用、页面状态和错误呈现。
1.2 库提供的能力
kotlinx-io 公共模块提供以下能力:
Buffer:作为字节队列写入和读取不同类型的数据;ByteString:保存不可变的字节序列,支持 UTF-8 解码、十六进制和 Base64;Source/Sink:为流式读取和写入提供统一抽象;- 文件系统 API:在各平台提供
Path、目录和文件系统能力; example/shared:封装示例操作、固定记录和六项公共检查;example/nativeApp:导出固定数量的 C ABI 函数,生成libkotlinxio.so;example/ohosApp:通过 N-API 把 JSON 结果展示在 ArkUI 页面。
示例页面使用以下固定记录验证不同字节长度:
| 示例 | 字节数 | 说明 |
|---|---|---|
kotlinx-io | 10 | ASCII 字符串可直接往返 |
OpenHarmony | 11 | 混合字母字符串可读取 |
Buffer/ByteString | 17 | 斜杠和较长文本的 UTF-8 往返 |
当前操作结果使用 kotlinx-io 3,UTF-8 字节数为 12,roundTrip 为 true。页面中的“运行示例”和“读取”按钮都调用 Kotlin/Native 动态库,不在 ArkTS 侧复制 Buffer 逻辑。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | Buffer 写入、ByteString 转换、字节计数、往返检查和错误处理由 Kotlin 共享。 |
| 平台目标 | 为 kotlinx-io-core、kotlinx-io-bytestring 和示例加入 ohosArm64。 |
| 平台实现 | 使用 core/ohosArm64 中的路径和目录实现,避免依赖 Linux 专属 API。 |
| 桥接稳定 | 使用少量 C ABI 函数和 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持运行示例、运行检查、读取固定示例和错误展示。 |
| 可测试 | JVM 测试、Native 链接、依赖检查、HAP 构建、安装和页面自检分别验收。 |
| 签名安全 | 证书、profile、p12 和密码只保存在仓库外部的本机签名工程。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面是独立的真机验收宿主,公共 API 仍然保持平台无关。其他 KMP/CMP 应用可以直接复用
Buffer、ByteString、Source和Sink,再自行决定界面呈现方式。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点核心模块、示例模块和 OpenHarmony 工程边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、仓库配置和 Native 构建任务
第 3 阶段:字节操作封装 ── 建立 Buffer / ByteString 示例、固定记录和检查契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、JSON 和内存释放
第 5 阶段:ArkUI 页面 ── 运行示例、读取固定项、错误状态和生命周期
第 6 阶段:示例与验证 ── 依赖检查、HAP 构建、签名、安装和效果图
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享 Buffer 和 ByteString 逻辑,再把同一份代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后安装 HAP 观察页面上的字节数和往返结果。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 CMP 工程一致的分层:
core/ Buffer、Source、Sink 和文件系统实现
bytestring/ ByteString、编码和不可变字节操作
example/shared/ 公共示例门面、固定记录和 JVM 验收测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP、签名和依赖检查脚本
docs/openharmony/ 验收记录和 OpenHarmony 说明
example 是独立 Gradle 工程,同时通过 included build 使用当前仓库模块,不把 DevEco 工程当作 Kotlin 子模块。这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到另一个目录后配置签名。
1.2 固定工具链和版本矩阵
本项目使用以下版本约定:
| 项目 | 配置 | 用途 |
|---|---|---|
| Kotlin Multiplatform | 2.2.21-1.0.0 | JVM、Kotlin/Native 和 KLIB |
| JDK | 21 | Gradle、Kotlin 编译和 Native 任务 |
| OpenHarmony 目标 | ohosArm64 | ARM64 真机或鸿蒙 PC 动态库 |
| DevEco product | default | Stage 工程和 HAP 构建 |
| ABI | arm64-v8a | HAP 原生库架构 |
| 设备类型 | 2in1 | 鸿蒙 PC 示例工程目标 |
执行 Gradle 脚本前先选择 JDK 21:
export JAVA_HOME="/path/to/jdk-21"
java -version
设备是否为 ARM64、是否属于 2in1 类型还要单独确认。目标配置满足要求,只说明工程可以编译,不能替代设备安装验证。
1.3 创建 OpenHarmony 示例目录
示例页面没有把 Native 能力伪装成普通列表,而是围绕“当前结果、字节数、往返状态和检查结果”组织:
标题区 kotlinx-io / Kotlin Multiplatform · OpenHarmony
操作区 Buffer / ByteString、运行示例、运行检查
结果区 最近一次结果、字符串、BYTE COUNT、ROUND TRIP
固定示例区 kotlinx-io、OpenHarmony、Buffer/ByteString
状态区 当前操作状态、错误文本和 6/6 检查结果
链路说明 ArkTS → N-API → Kotlin/Native → kotlinx-io
页面启动时调用 getCatalog() 加载固定记录;点击“运行示例”才执行当前 Buffer / ByteString 操作;点击“运行检查”验证共享逻辑。这样即使设备没有配置正式签名,页面也能通过本地构建完成链路验收。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
核心模块通过统一约定加入 OpenHarmony Native 目标:
kotlin {
jvm()
jvmToolchain(21)
ohosArm64()
sourceSets {
commonMain.dependencies {
// Buffer、Source、Sink 的公共实现
}
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
kotlinx-io-core 和 kotlinx-io-bytestring 保留 JVM 测试,同时生成 OpenHarmony KLIB。这样同一份 commonMain 代码可以在 JVM 和 ARM64 Native 上验证。
2.2 OpenHarmony 平台实现的作用
核心模块包含 core/ohosArm64 源集,为路径和目录提供 OpenHarmony 版本的实现:
core/common Buffer、Source、Sink、Path 公共契约
core/jvm JVM 文件系统和 ByteBuffer 扩展
core/native 通用 Native 实现
core/ohosArm64 OpenHarmony 路径和目录实现
平台实现的目标是保持公共 API 不变。业务代码仍然调用 Path 和文件系统抽象,OpenHarmony 专属代码只位于目标源集,不会污染其他平台。
2.3 配置仓库和独立消费工程
example/settings.gradle.kts 使用本地构建仓库、Maven Central 和 OpenHarmony 社区 Maven:
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
mavenLocal()
maven("${rootDir.parentFile}/build/repo")
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
}
}
示例工程通过 included build 消费当前仓库模块,不需要先发布到公网仓库:
rootProject.name = "kotlinx-io-openharmony-example"
include("shared", "nativeApp")
includeBuild("..")
2.4 通过构建产物消费共享库
example/nativeApp 使用 example/shared,再由共享模块使用当前仓库的核心 API:
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "kotlinxio"
linkerOpts(
"--entry=0",
"--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}",
)
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
prepareOhos 在 Native 链接成功后复制动态库和 C 头文件:
val prepareOhos by tasks.registering(Copy::class) {
dependsOn("linkDebugSharedOhosArm64")
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libkotlinxio.so")
into("libs/arm64-v8a")
}
from(layout.buildDirectory.dir("bin/ohosArm64/debugShared")) {
include("libkotlinxio_api.h")
into("src/main/cpp/include")
}
into(rootProject.layout.projectDirectory.dir("../example/ohosApp/entry"))
}
动态库、生成头文件和 HAP 属于构建产物,仓库通过忽略规则排除它们;每台开发机都可以从源码重新生成与自身 SDK 匹配的文件。
第 3 阶段:字节操作与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin Buffer 对象,因此边界使用 UTF-8 JSON:
ArkTS button
↓ method call
C++ N-API
↓ C ABI
Kotlin/Native IoExamples
↓ Buffer / ByteString
UTF-8 JSON
↓
ArkUI page state
页面只消费 JSON,不复制字节计数和往返校验逻辑。这样 JVM 测试和设备页面使用同一套 IoExamples 规则。
3.2 Buffer 和 ByteString 操作
共享示例把字符串写入 Buffer,再读取为 ByteString 并还原文本:
public data class IoExample(
val id: String,
val value: String,
val byteCount: Int,
val roundTrip: Boolean,
)
public fun runCurrent(): IoExample {
val buffer = Buffer()
buffer.writeString("kotlinx-io 3")
val bytes = buffer.readByteString()
val value = bytes.decodeToString()
return IoExample(
id = "current",
value = value,
byteCount = bytes.size,
roundTrip = value == "kotlinx-io 3",
)
}
byteCount 来自 ByteString.size,而不是字符串的 Kotlin 字符数。对于中文或其他多字节字符,页面显示的字节数仍然以 UTF-8 实际编码结果为准。
3.3 固定示例和边界检查
IoExamples.catalog() 返回三条固定记录,页面可以单独读取每一条:
public fun get(index: Int): IoExample {
require(index in catalog().indices) { "Example index out of range: $index" }
return catalog()[index]
}
runChecks() 覆盖六项检查:
- Buffer 可以写入并读回文本;
- ByteString 字节数与 UTF-8 编码结果一致;
- 往返读取后字符串内容保持不变;
- 固定示例数量和 ID 唯一;
- 非法索引会返回可读错误;
- OpenHarmony 示例使用的 JSON 字段完整。
错误会被 Native 边界转换为 {"error":"..."},ArkTS 页面可以显示错误文本,而不会因为越界读取造成 Native 崩溃。
3.4 JSON 字段约定
当前结果的 JSON 契约如下:
{
"id": "current",
"value": "kotlinx-io 3",
"byteCount": 12,
"roundTrip": true
}
固定示例目录使用同样的字段结构。所有字符串经过 JSON 转义,避免示例文本或异常信息破坏页面解析。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 IoExample、Buffer 和 ByteString 属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象。
最终采用三层桥接:
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ IoExamples
▼
UTF-8 JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出字节数 | 实现简单 | 页面无法展示真实文本和错误 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 Buffer 逻辑 | 页面调用简单 | KMP 和 ArkTS 逻辑容易分叉 | ❌ |
4.3 Kotlin/Native 导出函数
example/nativeApp 导出五个符号:
@CName("IoCatalog")
public fun catalogNative(): CPointer<ByteVar> =
response { IoExamples.catalog().joinToString(prefix = "[", postfix = "]") { it.toJson() } }
@CName("IoGet")
public fun ioNative(index: Int): CPointer<ByteVar> =
response { IoExamples.get(index).toJson() }
@CName("IoCurrent")
public fun currentNative(): CPointer<ByteVar> =
response { IoExamples.random().toJson() }
@CName("IoRunChecks")
public fun checksNative(): CPointer<ByteVar> =
response { /* 返回六项检查 JSON */ }
@CName("IoFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
返回值使用 Native heap 分配的、以 0 结尾的 C 字符串。每一块返回缓冲区都由 IoFree 释放。
4.4 C++ N-API 方法分发
C++ 注册四个 ArkTS 方法:
napi_property_descriptor methods[] = {
{"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"getExample", nullptr, Example, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"currentIo", nullptr, Current, nullptr, nullptr, nullptr,
napi_default, nullptr},
{"runChecks", nullptr, Checks, nullptr, nullptr, nullptr,
napi_default, nullptr},
};
getExample 会检查参数数量和整数类型,再调用 IoGet。所有方法都遵循“调用 Native、创建 ArkTS 字符串、释放 Native 缓冲区”的顺序。
CMake 将 Kotlin/Native 动态库作为 imported library:
add_library(kotlinxio SHARED IMPORTED)
set_target_properties(kotlinxio PROPERTIES
IMPORTED_LOCATION
"${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libkotlinxio.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE kotlinxio libace_napi.z.so)
4.5 N-API 生命周期
ArkTS getExample(index)
│
▼
ReadNumber + argument check
│
▼
IoGet(index)
│
▼
napi_create_string_utf8(...)
│
▼
IoFree(nativeBuffer)
│
▼
return JS string
C++ 负责完成跨语言字符串转换和 Native 释放,ArkTS 页面不需要知道 Kotlin/Native 的堆实现。
第 5 阶段:ArkUI 页面与系统能力封装
5.1 ArkTS 调用 N-API 模块
页面通过 IoClient.ets 引入 libentry.so:
import ioNative from 'libentry.so';
export function catalog(): IoExample[] {
return JSON.parse(ioNative.getCatalog()) as IoExample[];
}
export function getExample(index: number): IoExample {
return JSON.parse(ioNative.getExample(index)) as IoExample;
}
export function currentIo(): IoExample {
return JSON.parse(ioNative.currentIo()) as IoExample;
}
export function runChecks(): IoChecks {
return JSON.parse(ioNative.runChecks()) as IoChecks;
}
ArkTS 只定义与 JSON 对应的接口,不把 Buffer 或 ByteString 的实现复制到页面。
5.2 ArkUI 页面状态
Index.ets 保存当前结果、固定示例、状态文本、错误文本和检查进度:
@State private selected: IoExample = emptyIo();
@State private examples: IoExample[] = [];
@State private status: string = '准备就绪';
@State private errorText: string = '';
@State private checksPassed: number = 0;
@State private checksTotal: number = 6;
页面生命周期中调用 loadCatalog() 加载固定记录。点击“运行示例”更新 selected;点击“运行检查”更新 checksPassed/checksTotal;任何异常都会落到 errorText,页面底部显示可读错误。
5.3 页面交互预设
页面提供四类交互:
- 运行示例:执行 Kotlin/Native 中的 Buffer 写入、ByteString 读取和 UTF-8 往返;
- 运行检查:运行共享逻辑的六项检查;
- 读取:从固定示例目录读取指定记录;
- 错误展示:索引越界或 Native 返回错误时显示错误文本。
页面底部固定显示:
数据流:ArkTS → N-API → Kotlin/Native → kotlinx-io
第 6 阶段:示例与验证
6.1 example 工程结构
example/
├── shared/
│ ├── src/commonMain/.../IoExamples.kt
│ └── src/commonTest/.../IoExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/.../NativeBridge.kt
│ └── src/ohosArm64Main/linker/shared-library.map
└── ohosApp/
├── AppScope/
├── entry/src/main/cpp/
│ ├── CMakeLists.txt
│ └── napi_init.cpp
└── entry/src/main/ets/
├── pages/Index.ets
└── kotlinxio/IoClient.ets
shared 验证公共 Buffer / ByteString 操作,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI 页面和 N-API 模块。三者的边界清晰,任何一层失败都能单独定位。
6.2 原生模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块,ArkTS 类型声明位于:
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
页面使用:
import ioNative from 'libentry.so';
6.3 Native 动态库准备
执行:
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh
脚本依次执行核心 JVM 测试、示例 JVM 测试、linkDebugSharedOhosArm64 和 prepareOhos。成功后生成:
example/ohosApp/entry/libs/arm64-v8a/libkotlinxio.so
example/ohosApp/entry/src/main/cpp/include/libkotlinxio_api.h
还可以检查 ARM64 ELF 的强依赖:
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
6.4 构建、签名和安装
进入 DevEco 工程目录执行:
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
--mode module -p module=entry@default -p product=default \
-p requiredDeviceType=2in1 assembleHap \
--analyze=normal --parallel --incremental --daemon
产物位于:
example/ohosApp/entry/build/default/outputs/default/entry-default-unsigned.hap
example/ohosApp/entry/build/default/outputs/default/entry-default-signed.hap
查看设备、安装并启动:
HDC=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
$HDC list targets
$HDC install -r example/ohosApp/entry/build/default/outputs/default/entry-default-signed.hap
$HDC shell aa start -a EntryAbility -b org.jetbrains.kotlinx.io.sample
正式签名使用仓库外部的签名工程。证书、profile、p12 和密码只保存在本机,不要提交到 AtomGit。
四、完整代码对照
4.1 整体架构
ArkUI button
│ getCatalog / currentIo / getExample / runChecks
▼
IoClient.ets
│ N-API
▼
libentry.so
│ C ABI
▼
libkotlinxio.so
│ Kotlin/Native
▼
IoExamples -> Buffer -> ByteString -> JSON
4.2 文件清单
| 文件 | 职责 |
|---|---|
core/common/src/Buffer.kt | Buffer 公共字节队列 |
bytestring/common/src/ByteString.kt | 不可变 ByteString |
example/shared/src/commonMain/.../IoExamples.kt | 固定示例、当前操作和公共检查 |
example/nativeApp/src/ohosArm64Main/.../NativeBridge.kt | C ABI、JSON 返回和内存释放 |
example/ohosApp/.../kotlinxio/IoClient.ets | N-API JSON 解析和校验 |
example/ohosApp/.../pages/Index.ets | ArkUI 真机页面 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 导出和参数检查 |
scripts/build-openharmony.sh | 测试、Native 链接和产物复制 |
scripts/build-hap.sh | 已配置签名工程的 HAP 构建 |
docs/openharmony/VALIDATION.md | 自动检查和真机验收记录 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kotlin | Buffer.writeString | 写入 UTF-8 文本 |
| Kotlin | Buffer.readByteString | 读取不可变字节序列 |
| Kotlin | ByteString.decodeToString | 还原文本并验证往返 |
| Kotlin | IoExamples.runChecks | 执行六项公共检查 |
| Native | IoCurrent | 返回当前 Buffer / ByteString 结果 |
| Native | IoGet | 返回固定示例 |
| N-API | currentIo / getExample | 向 ArkTS 暴露示例方法 |
| ArkTS | runChecks | 展示公共检查结果 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责按钮、页面状态和 JSON 解析:
this.selected = currentIo();
this.status = 'Buffer 和 ByteString 操作完成';
Kotlin 负责字节语义和不可变结果:
val bytes = buffer.readByteString()
val value = bytes.decodeToString()
val roundTrip = value == original
两者之间只传输索引、方法调用和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入核心构建约定
只有真正链接 OpenHarmony ARM64 动态库,才能证明 Buffer、ByteString 和平台实现可以进入 OpenHarmony 运行时。
决策 2:独立消费者必须通过构建产物消费
example 单独解析共享模块,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法在 DevEco 中打包。
决策 3:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加字段而不暴露 Buffer 内部结构。
决策 4:桥接层只开放五个 C ABI 入口
目录、固定示例、当前结果、自检和释放已经覆盖示例所需能力;减少 ABI 符号可以降低 Native 生命周期和兼容风险。
决策 5:固定示例和真实操作分开
固定示例用于验证索引读取和列表渲染,当前操作用于验证 Buffer / ByteString 真正执行。两者分开后,问题可以快速定位到数据目录或运行链路。
决策 6:把库验证和设备验证分开
JVM 测试验证字节规则,Native 链接验证 ABI,Hvigor 验证 HAP,真机验证页面调用。每一层都有明确的失败边界。
六、测试与验证
6.1 测试环境
本次适配验证使用:
- macOS;
- JDK 21;
- Kotlin Multiplatform
2.2.21-1.0.0; - DevEco Studio 及 OpenHarmony ARM64 Native SDK;
- CMake、Ninja、ArkTS 编译器和
hdc; - ARM64 鸿蒙 PC 或 OpenHarmony 真机;
- 面向
2in1设备类型的示例工程。
6.2 静态检查与单元测试
./gradlew :kotlinx-io-core:jvmTest
(cd example && ./gradlew :shared:jvmTest)
测试覆盖 Buffer 写入和读取、ByteString 字节数、UTF-8 往返、固定示例唯一性、非法索引和六项公共检查。
6.3 原生桥接和 HAP 验证
./scripts/build-openharmony.sh
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
验证结果应包含:
libkotlinxio.so: 依赖检查通过
libentry.so: N-API 模块成功链接
Hvigor BUILD SUCCESSFUL
entry-default-signed.hap generated
如果设备或 SDK 不支持某些 OpenHarmony 能力,工具可能输出兼容性提示;这类提示需要和 Gradle、CMake 或 Hvigor 的真正错误分开判断。
6.4 功能验证用例
用例 1:默认页面和示例目录
启动应用后页面显示 kotlinx-io、Kotlin Multiplatform · OpenHarmony 和三条固定示例。页面初始化调用 getCatalog()。
用例 2:运行 Buffer / ByteString
点击“运行示例”,页面显示 kotlinx-io 3、BYTE COUNT 12 和 ROUND TRIP PASS。这证明操作经过 Kotlin/Native,而不是由 ArkTS 预先写死。
用例 3:运行公共检查
点击“运行检查”,页面底部显示 6/6。检查结果来自 IoRunChecks 的 JSON 返回。
用例 4:读取固定示例
依次点击 kotlinx-io、OpenHarmony 和 Buffer/ByteString 右侧的“读取”,确认文本、字节数和往返标记与目录一致。
用例 5:非法索引和 Native 错误
通过测试或调试调用越界索引,确认 IoGet 返回错误 JSON,页面显示“读取失败”和可读错误,不会崩溃。
用例 6:重新安装和重启
卸载并重新安装 HAP,确认动态库、N-API 模块和页面初始化都能重新建立,避免依赖上一次运行的缓存状态。
6.5 验证结论
自动测试、Kotlin/Native ARM64 链接、ELF 依赖检查、Hvigor HAP 构建、签名安装和页面自检均可按上述步骤完成。效果图中的页面已经显示实际运行结果,说明从 ArkUI 按钮到 kotlinx-io Buffer / ByteString 的链路可用。
七、运行效果
7.1 真机截图

截图中可以看到:
- 页面标题为“kotlinx-io”;
- 副标题为“Kotlin Multiplatform · OpenHarmony”;
- 操作卡片展示“Buffer / ByteString”;
- 最近一次结果为
kotlinx-io 3; - 字节数为
12,往返结果为PASS; - 固定示例包含
kotlinx-io、OpenHarmony和Buffer/ByteString; - 底部数据流为“ArkTS → N-API → Kotlin/Native → kotlinx-io”。
7.2 命令速查
# 根工程测试和 OpenHarmony Native 准备
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
# 构建 HAP
cd example/ohosApp
/Applications/DevEco-Studio.app/Contents/tools/node/bin/node \
/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js \
--mode module -p module=entry@default -p product=default \
-p requiredDeviceType=2in1 assembleHap \
--analyze=normal --parallel --incremental --daemon
# 安装和启动
HDC=/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/toolchains/hdc
$HDC install -r entry/build/default/outputs/default/entry-default-signed.hap
$HDC shell aa start -a EntryAbility -b org.jetbrains.kotlinx.io.sample
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配:必须把共享 Buffer / ByteString 代码真正编译成
ohosArm64动态库。 - N-API 不负责字节业务判断:C++ 只做参数检查、字符串转换和释放,字节语义放在 Kotlin。
- Native 字符串必须成对管理:每次 C ABI 返回后都要在创建 ArkTS 字符串后调用
IoFree。 - 权限和设备架构是两件事:HAP 安装成功只能说明签名和架构匹配,不能替代 Native 依赖检查。
- 签名配置需要绑定产品:
products[].signingConfig必须指向signingConfigs,否则 Hvigor 只能生成未签名 HAP。
8.2 已知问题
- 当前交付 ABI 为
arm64-v8a,没有打包 x86 或 32 位 Native 库; - 鸿蒙 PC 示例面向
2in1设备类型,其他设备类型需要调整构建参数; - 示例页面使用 JSON 作为边界,频繁小对象调用会有序列化开销;
- 正式签名依赖开发者本机证书和 profile,文章中的命令不能代替企业签名流程;
- 当前页面是单页示例,复杂应用需要在业务层集中管理 Native 调用和错误状态。
8.3 未来优化方向
- 增加跨平台
expect/actual示例,让 Android、桌面和 OpenHarmony 共用同一套演示接口; - 为流式
Source/Sink增加分页读取和大文件示例; - 将当前回调式 N-API 调用封装为 CMP 状态模型,减少页面状态管理代码;
- 增加设备架构探测和 Native 依赖诊断页面;
- 在持续集成中加入
ohosArm64链接、未签名 HAP 和示例 JVM 测试任务。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个 ArkUI 页面,而是一条完整跨端链路:
ArkUI interaction
→ N-API 参数检查
→ Kotlin/Native C ABI
→ kotlinx-io Buffer / ByteString
→ JSON
→ ArkUI 结果卡片
9.2 封装层次
- KMP 层:定义 Buffer、ByteString、Source、Sink 和跨平台文件系统 API;
- Native 层:生成 ARM64 动态库,并通过 C ABI 输出有限入口;
- N-API 层:完成参数检查、字符串转换和内存释放;
- ArkTS 层:管理按钮事件、页面状态、固定目录和错误显示;
- DevEco 层:完成 CMake、HAP、签名、安装和运行。
9.3 三条经验
- 先让共享 Buffer / ByteString 在 JVM 和 Native 通过,再接入 ArkUI;
- 用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、HAP 构建和页面效果分别记录,避免把“编译成功”误认为“设备链路可用”。
9.4 适配成果
当前 kotlinx-io 已完成:
kotlinx-io-core和kotlinx-io-bytestring的ohosArm64目标;core/ohosArm64平台路径和目录实现;- Buffer 写入、ByteString 读取、UTF-8 往返和字节计数示例;
- JVM 和 OpenHarmony ARM64 共用的六项检查;
- Kotlin/Native + C ABI + C++ N-API 桥接;
libkotlinxio.so和libentry.so构建;- ArkUI 运行示例、运行检查和固定示例读取页面;
- ARM64 依赖检查、HAP 构建、设备安装和效果图;
- 与参考 CMP 工程一致的模块和脚本组织;
- AtomGit 项目文档和 OpenHarmony 验收记录。
参考文档
更多推荐


所有评论(0)