本文记录 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-io10ASCII 字符串可直接往返
OpenHarmony11混合字母字符串可读取
Buffer/ByteString17斜杠和较长文本的 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 Multiplatform2.2.21-1.0.0JVM、Kotlin/Native 和 KLIB
JDK21Gradle、Kotlin 编译和 Native 任务
OpenHarmony 目标ohosArm64ARM64 真机或鸿蒙 PC 动态库
DevEco productdefaultStage 工程和 HAP 构建
ABIarm64-v8aHAP 原生库架构
设备类型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() 覆盖六项检查:

  1. Buffer 可以写入并读回文本;
  2. ByteString 字节数与 UTF-8 编码结果一致;
  3. 往返读取后字符串内容保持不变;
  4. 固定示例数量和 ID 唯一;
  5. 非法索引会返回可读错误;
  6. 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.ktBuffer 公共字节队列
bytestring/common/src/ByteString.kt不可变 ByteString
example/shared/src/commonMain/.../IoExamples.kt固定示例、当前操作和公共检查
example/nativeApp/src/ohosArm64Main/.../NativeBridge.ktC ABI、JSON 返回和内存释放
example/ohosApp/.../kotlinxio/IoClient.etsN-API JSON 解析和校验
example/ohosApp/.../pages/Index.etsArkUI 真机页面
example/ohosApp/entry/src/main/cpp/napi_init.cppN-API 导出和参数检查
scripts/build-openharmony.sh测试、Native 链接和产物复制
scripts/build-hap.sh已配置签名工程的 HAP 构建
docs/openharmony/VALIDATION.md自动检查和真机验收记录

4.3 关键 API 对照

层次API作用
KotlinBuffer.writeString写入 UTF-8 文本
KotlinBuffer.readByteString读取不可变字节序列
KotlinByteString.decodeToString还原文本并验证往返
KotlinIoExamples.runChecks执行六项公共检查
NativeIoCurrent返回当前 Buffer / ByteString 结果
NativeIoGet返回固定示例
N-APIcurrentIo / getExample向 ArkTS 暴露示例方法
ArkTSrunChecks展示公共检查结果

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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配:必须把共享 Buffer / ByteString 代码真正编译成 ohosArm64 动态库。
  2. N-API 不负责字节业务判断:C++ 只做参数检查、字符串转换和释放,字节语义放在 Kotlin。
  3. Native 字符串必须成对管理:每次 C ABI 返回后都要在创建 ArkTS 字符串后调用 IoFree。
  4. 权限和设备架构是两件事:HAP 安装成功只能说明签名和架构匹配,不能替代 Native 依赖检查。
  5. 签名配置需要绑定产品: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 三条经验

  1. 先让共享 Buffer / ByteString 在 JVM 和 Native 通过,再接入 ArkUI;
  2. 用 JSON 和少量 C ABI 代替跨语言对象传递;
  3. 把自动测试、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 验收记录。

参考文档

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐