本文记录 Kaml 接入 OpenHarmony 的完整过程,覆盖 KMP/CMP 工程盘点、
ohosArm64目标、OHOS 依赖替换、SnakeYAML 源码适配、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 交互页面、未签名 HAP 构建和原生依赖验收。本次适配复用 Kaml 的 YAML 节点解析和序列化能力,再由 ArkTS 编辑器调用 Kotlin/Native。页面不是静态验证页,而是可以编辑 YAML、载入模板、解析结构并把普通文本编码成 YAML 标量的工作台。
项目地址: AtomGit/oh-tpc/kaml
上游仓库: charleskorn/kaml
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做开源鸿蒙平台 KMP/CMP 适配
Kaml 是一个基于 kotlinx.serialization 的 Kotlin Multiplatform YAML 库,核心能力包括 YAML 文档读取、YAML 节点树访问和类型安全的 YAML 序列化。原项目已经覆盖 JVM、JS、Wasm、Apple 和 Linux 等目标,但标准发布物没有 OpenHarmony ohosArm64 变体。
如果只重新写一个 ArkTS 页面,页面可以显示几段硬编码 YAML,却无法证明同一份 KMP 解析逻辑已经运行在 OpenHarmony ARM64 设备上。因此适配需要同时解决以下问题:
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 根 KMP 工程默认没有 ohosArm64(),不能生成 OpenHarmony KLIB。 |
| 依赖缺少 OHOS 变体 | kotlinx-serialization-core、Okio 和 SnakeYAML KMP 的标准坐标没有完整的 OHOS 变体。 |
| Native ABI 边界 | ArkTS 不能直接持有 Kotlin YamlNode,需要 C ABI、C++ N-API 和 JSON。 |
| 示例工程隔离 | Gradle/Kotlin 工程和 DevEco/Hvigor 工程需要分别构建,再通过 .so 和头文件连接。 |
| 页面需要体现能力 | 仅显示测试结果不能体现 YAML 库的使用场景,需要编辑、解析、模板和编码交互。 |
| 签名材料安全 | HAP 签名依赖本机证书和 profile,源码仓库必须保持未签名。 |
| 依赖可验证 | 不能把 Linux ARM64 klib 冒充 OHOS ABI,必须检查 ELF 和 SDK 符号。 |
因此,本项目把适配边界放在四个地方:Kotlin/Native 目标配置、OHOS 依赖供应、C ABI/N-API 桥接层和 ArkUI YAML 工作台。Kaml 的 YAML 解析语义仍由共享 Kotlin 代码维护,ArkTS 只负责输入、交互和结果展示。
1.2 Kaml 提供的能力
Kaml 公共 API 保持原有使用方式,OpenHarmony 示例围绕以下能力组织:
Yaml.default.parseToYamlNode:把 YAML 文本解析为节点树;YamlMap:访问映射字段和标量值;YamlList:访问序列节点和列表项目;YamlScalar:读取标量内容;Yaml.encodeToString:使用kotlinx.serialization把 Kotlin 值编码为 YAML;YamlNode.contentToString:在示例中展示结构化节点内容;KamlExamples.inspect:计算根节点类型、摘要和节点数量;KamlExamples.encodeValue:把页面输入的普通文本编码为 YAML 标量。
示例把一份 YAML 文档的根节点归类为映射、列表或标量:
| 根节点 | 页面展示 | Kaml API |
|---|---|---|
| Mapping | 字段数量和映射内容 | YamlMap.entries |
| Sequence | 项目数量和列表内容 | YamlList.items |
| Scalar | 标量内容 | YamlScalar.content |
| Null/Tagged | 通用节点摘要 | YamlNode.contentToString() |
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | YAML 解析、节点判断、节点计数、标量编码和错误处理由 Kotlin 共享层完成。 |
| 平台目标 | 根工程、共享示例和 Native 应用加入 ohosArm64,生成 libkaml.so。 |
| 桥接稳定 | 使用少量 C ABI 函数和 UTF-8 JSON,避免把 Kotlin 对象地址交给 ArkTS。 |
| UI 完整 | 页面支持 YAML 编辑、模板载入、解析结果、标量编码和错误展示。 |
| 可测试 | JVM 测试、Kotlin/Native 编译、C++ N-API 编译、HAP 构建和 ELF 检查分别验收。 |
| 签名安全 | 源码只保留未签名工程和签名准备脚本,证书与密钥材料由开发者本机配置。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
示例页面是独立的 OpenHarmony 消费者工程,公共 Kaml API 仍然保持平台无关。其他 KMP/CMP 应用可以直接复用 Kaml 的
Yaml、YamlMap和YamlList,再自行决定页面呈现方式。
二、实现路线图
text
第 1 阶段:项目初始化 ── 盘点 Kaml API、依赖和 OpenHarmony 示例边界
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64、OHOS 依赖替换和 SnakeYAML 源码模块
第 3 阶段:节点与序列化 ── 建立 YAML 模板、节点摘要、节点计数和错误契约
第 4 阶段:原生桥接 ── Kotlin/Native C ABI、C++ N-API、字符串参数和内存释放
第 5 阶段:ArkUI 工作台 ── YAML 编辑、模板载入、解析结果和标量编码交互
第 6 阶段:示例与验证 ── JVM 测试、Native 链接、HAP 构建、ELF 依赖和签名准备
每个阶段都使用真实产物作为下一阶段输入:先用 JVM 验证共享示例,再把同一份 Kaml 代码链接为 ARM64 动态库,随后由 CMake 和 N-API 加载到 Stage 工程,最后生成未签名 HAP。签名和真机安装属于本地发布步骤,不把证书写进仓库。
三、逐步实现过程
第 1 阶段:项目初始化
1.1 盘点公共 API 和工程边界
项目采用与参考 KMP/CMP 工程一致的分层:
text
kaml/ 根 KMP 库和原有平台目标
vendor/snakeyaml-engine-kmp/ OHOS SnakeYAML KMP 源码模块
example/shared/ 共享 Kaml 消费者和 JVM 测试
example/nativeApp/ ohosArm64 Kotlin/Native 动态库
example/ohosApp/ DevEco Stage 工程和 ArkUI 页面
scripts/ Native、HAP、依赖和签名工程脚本
docs/openharmony/ 验收记录和效果图
example 是独立 Gradle 消费者工程,不把 DevEco 工程当作根项目的 Kotlin 子模块。这样可以分别执行 Gradle 和 Hvigor,也可以把 ohosApp 复制到另一个目录后在 DevEco Studio 中配置签名。
1.2 固定工具链和版本矩阵
本项目使用以下版本约定:
| 项目 | 配置 | 用途 |
|---|---|---|
| Kotlin Multiplatform | 2.2.21-1.0.0 |
JVM、Kotlin/Native 和 KLIB |
| Kaml OHOS 版本 | 0.104.0-ohos.1 |
OpenHarmony 适配发布版本 |
| OpenHarmony 目标 | ohosArm64 |
ARM64 真机动态库 |
| DevEco product | 6.0.0(20) |
工程兼容和目标版本 |
| DevEco Studio | 6.0.0 Release |
IDE 版本,负责工程同步、HAP 构建与签名 |
| ABI | arm64-v8a |
HAP 原生库架构 |
| Gradle | 9.7.1 |
根工程和 example 构建 |
执行 Gradle 脚本前选择受支持的 JDK:
bash
export JAVA_HOME="/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home"
java -version
目标 SDK 满足构建条件,只能说明工程可以编译,并不能替代设备安装和运行能力检查。
1.3 创建 OpenHarmony 示例目录
示例页面围绕"输入 YAML、执行 Kaml、查看结果"组织:
text
标题区 Kaml 工作台 / 在设备上编辑、解析和编码 YAML
模式区 编辑 YAML / 查看结果
编辑区 多行 YAML 编辑器、字符数和恢复示例
操作区 解析 YAML、普通文本编码为 YAML 标量
模板区 服务配置、团队发布单
结果区 根节点类型、摘要、节点数和结构内容
状态区 Native 连接状态、解析错误和调用链
页面输入会经过完整链路;模板按钮只是快速填充文本,不绕过 Kaml。这样即使用户把 tags 改成嵌套列表,解析结果也来自实际的 YamlNode。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64 目标
根工程在原有目标集合中增加 OpenHarmony 目标:
kotlin
kotlin {
explicitApi()
jvm {}
js(IR) { browser(); nodejs(); binaries.executable() }
wasmJs { binaries.library(); browser(); nodejs() }
macosX64()
macosArm64()
iosSimulatorArm64()
iosX64()
linuxX64()
linuxArm64()
iosArm64()
mingwX64()
ohosArm64()
}
JVM 目标让原有测试保持不变;ohosArm64 则把相同 commonMain 代码编译成 OpenHarmony KLIB。
2.2 OHOS 依赖替换的作用
OpenHarmony Kotlin/Native 编译不能把 JVM artifact 或 Linux klib 当作 OHOS 目标依赖。本项目在 OHOS configuration 中替换为对应变体:
kotlin
configurations.matching { it.name.startsWith("ohosArm64") }.configureEach {
resolutionStrategy.dependencySubstitution {
substitute(module("org.jetbrains.kotlinx:kotlinx-serialization-core"))
.using(module("org.jetbrains.kotlinx:kotlinx-serialization-core-ohosarm64:1.9.1-1.0.0"))
substitute(module("com.squareup.okio:okio"))
.using(module("com.squareup.okio:okio-ohosarm64:3.16.4-1.0.0"))
}
}
SnakeYAML KMP 上游没有 OHOS 变体,因此没有伪造坐标指向其他 ABI,而是把 common/native 源码放进 vendor/snakeyaml-engine-kmp,由当前工程真正编译。
2.3 OHOS SnakeYAML 源码模块
vendor 模块只声明 OHOS 目标和本地出版坐标:
kotlin
group = "it.krzeminski"
version = "4.0.1-ohos.1"
kotlin {
ohosArm64 {
mavenPublication {
artifactId = "snakeyaml-engine-kmp-ohosarm64"
}
}
}
上游源码中 URI 编码实现依赖 JVM-only library,OHOS common/native 编译时改为纯 Kotlin percent encode/decode。其余 YAML scanner、parser、composer、emitter 和 node 代码保持上游结构。
2.4 配置仓库和独立消费工程
example/settings.gradle.kts 使用本地 Maven、OpenHarmony 社区 Maven 和 Maven Central:
kotlin
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()
}
}
共享示例通过本地发布的 Kaml 消费:
kotlin
sourceSets {
commonMain.dependencies {
implementation("com.charleskorn.kaml:kaml:${property("kamlVersion")}")
}
}
2.5 通过构建产物消费共享库
example/nativeApp 构建一个受 linker map 约束的 shared library:
kotlin
kotlin {
ohosArm64 {
binaries.sharedLib {
baseName = "kaml"
linkerOpts("--entry=0", "--version-script=${project.file("src/ohosArm64Main/linker/shared-library.map")}")
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
}
prepareOhos 在 Native 链接成功后把 libkaml.so 和 libkaml_api.h 复制进 DevEco entry 模块。动态库、头文件和 HAP 都是构建产物,不直接作为源码提交。
第 3 阶段:节点与序列化
3.1 为什么需要统一 JSON 契约
ArkTS、C++ 和 Kotlin/Native 不能直接共享 Kotlin YamlNode,因此边界使用 UTF-8 JSON:
text
ArkTS TextArea
↓ YAML string
libentry.so / N-API
↓ void* UTF-8 argument
KamlInspect / KamlEncode
↓ Kotlin YamlNode / Yaml.encodeToString
JSON result
↓
ArkUI result panel
页面只消费 JSON,不复制 Kaml 的 map/list 判断。这样 JVM、Native 和页面使用同一份解析语义。
3.2 KamlExample 和 KamlTransform
共享示例把模板和操作结果建模为不可变 data class:
kotlin
public data class KamlExample(
val id: String,
val title: String,
val detail: String,
val yaml: String,
)
public data class KamlTransform(
val kind: String,
val summary: String,
val content: String,
val nodeCount: Int,
)
yaml 供 ArkTS 模板区载入;KamlTransform 供结果页展示,不把 YamlNode 本身跨 ABI 返回。
3.3 inspect 和 encodeValue
kotlin
public fun inspect(yaml: String): KamlTransform {
require(yaml.isNotBlank()) { "请输入 YAML 内容" }
val node = Yaml.default.parseToYamlNode(yaml)
val kind = when (node) {
is YamlMap -> "映射"
is YamlList -> "列表"
is YamlScalar -> "标量"
else -> "节点"
}
return KamlTransform(
kind = kind,
summary = summaryFor(node),
content = node.contentToString(),
nodeCount = countNodes(node),
)
}
public fun encodeValue(value: String): KamlTransform {
require(value.isNotBlank()) { "请输入要编码的文本" }
val encoded = Yaml.default.encodeToString(String.serializer(), value)
return KamlTransform("编码", "普通文本已编码为 YAML 标量", encoded, 1)
}
3.4 自检和错误边界
KamlExamples.runChecks() 仍保留 JVM 和 Native 自检,用于验证基础能力:
- map 可以解析;
- boolean 内容保持不变;
- sequence 项目数量正确;
- scalar 可以编码;
- 可编辑模板可以再次解析。
Native 异常会转换为 {"error":"..."},C++ 创建 ArkTS 字符串后立即调用 KamlFree。页面统一检查 error 字段并显示错误,不把 Kotlin 异常对象传给 JavaScript。
第 4 阶段:原生桥接(技术难点)
4.1 Kotlin/Native 对象不能直接交给 ArkTS
Kotlin/Native 的 YamlMap、YamlList 和 KamlTransform 都属于 Kotlin 运行时对象。ArkTS 只能接收 JavaScript 值,C++ 不能把 Kotlin 对象地址直接当成 JavaScript 对象。
最终采用三层桥接:
text
ArkTS
│ YAML string / JSON string
▼
C++ N-API entry
│ void* UTF-8 pointer
▼
Kotlin/Native C ABI
│ Kaml parser and serializer
▼
JSON + explicit free
4.2 方案对比
| 方案 | 优点 | 缺点 | 选用 |
|---|---|---|---|
| 直接导出 Kotlin 节点对象 | 代码少 | ABI、生命周期和类型不可控 | ❌ |
| 只导出节点类型整数 | 实现简单 | 页面无法展示真实节点内容 | ❌ |
| C ABI + JSON | 边界清晰、易扩展、易调试 | 有一次序列化开销 | ✅ |
| 在 ArkTS 重写 YAML 解析 | 页面调用简单 | KMP 和 ArkTS 逻辑分叉 | ❌ |
4.3 Kotlin/Native 导出函数
kotlin
@CName("KamlEncode")
public fun encodeNative(value: CPointer<ByteVar>?): CPointer<ByteVar> = response {
KamlExamples.encodeValue(value?.toKString().orEmpty()).toJson()
}
@CName("KamlInspect")
public fun inspectNative(yaml: CPointer<ByteVar>?): CPointer<ByteVar> = response {
KamlExamples.inspect(yaml?.toKString().orEmpty()).toJson()
}
@CName("KamlFree")
public fun freeNative(pointer: CPointer<ByteVar>?) {
if (pointer != null) nativeHeap.free(pointer.rawValue)
}
Native 函数只收发以 0 结尾的 UTF-8 字符串。输入指针只在函数调用期间有效,返回缓冲区由 KamlFree 显式释放。
4.4 C++ N-API 方法分发
C++ 注册五个 ArkTS 方法,其中参数方法会先由 N-API 复制成临时 std::string:
cpp
napi_property_descriptor methods[] = {
{"getCatalog", nullptr, Catalog, nullptr, nullptr, nullptr, napi_default, nullptr},
{"parseSample", nullptr, ParseSample, nullptr, nullptr, nullptr, napi_default, nullptr},
{"encodeScalar", nullptr, EncodeScalar, nullptr, nullptr, nullptr, napi_default, nullptr},
{"parseYaml", nullptr, Inspect, nullptr, nullptr, nullptr, napi_default, nullptr},
{"runChecks", nullptr, RunChecks, nullptr, nullptr, nullptr, napi_default, nullptr},
};
encodeScalar(value) 和 parseYaml(yaml) 都校验参数数量和字符串类型;Kotlin/Native 返回错误 JSON 后,C++ 负责创建 ArkTS 字符串并释放 Native 缓冲区。
4.5 N-API 生命周期
text
ArkTS parseYaml(yaml)
│
▼
napi_get_value_string_utf8
│
▼
KamlInspect(void*)
│
▼
napi_create_string_utf8
│
▼
KamlFree(nativeBuffer)
│
▼
return JSON string
CMake 将 Kotlin/Native 动态库作为 imported library:
cmake
add_library(kaml SHARED IMPORTED)
set_target_properties(kaml PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a/libkaml.so")
add_library(entry SHARED napi_init.cpp)
target_include_directories(entry PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(entry PRIVATE kaml libace_napi.z.so)
第 5 阶段:ArkUI 工作台
5.1 ArkTS 调用 Kaml N-API
页面通过类型声明消费 libentry.so:
typescript
export const encodeScalar: (value: string) => string;
export const parseYaml: (yaml: string) => string;
客户端先解析 JSON,再把 Native 错误转换成页面异常:
typescript
export function parseYaml(yaml: string): KamlTransform {
return parse<KamlTransform>(kamlNative.parseYaml(yaml));
}
5.2 编辑 YAML 和模板载入
页面的核心输入是 TextArea,不是硬编码文本:
typescript
TextArea({ text: this.yamlText, placeholder: 'name: Kaml\\nenabled: true' })
.onChange((value: string) => this.yamlText = value)
.fontFamily('monospace').fontSize(13).height(230)
模板区使用 ForEach 展示共享层 catalog() 返回的模板。点击"载入"只更新编辑器内容,真正的解析仍由用户点击"解析 YAML"触发。
5.3 结果和标量编码
结果页展示 kind、summary、nodeCount 和 content。普通文本编码独立于 YAML 编辑器:
typescript
private encode(): void {
try {
this.result = encodeScalar(this.scalarText);
this.mode = '结果';
this.status = '已通过 Kaml 编码为 YAML 标量';
this.errorText = '';
} catch (error) {
this.fail(error);
}
}
因此页面同时体现"解析已有 YAML"和"从普通文本生成 YAML"两条实际使用路径。
5.4 页面交互预设
页面提供两个模式按钮:
- 编辑 YAML:显示输入区、模板和标量编码操作;
- 查看结果:显示 Kaml 返回的结构摘要和节点内容。
模板包括服务配置和团队发布单,分别覆盖映射、列表、嵌套映射、布尔值和字符串。输入非法 YAML 时,状态区保留编辑内容,并显示 Kaml 的错误文本,方便用户修正后再次解析。
第 6 阶段:示例与验证
6.1 example 工程结构
text
example/
├── shared/
│ ├── src/commonMain/kotlin/com/charleskorn/kaml/example/KamlExamples.kt
│ └── src/commonTest/kotlin/com/charleskorn/kaml/example/KamlExamplesTest.kt
├── nativeApp/
│ ├── src/ohosArm64Main/kotlin/.../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
└── kaml/KamlClient.ets
shared 验证公共 Kaml 调用,nativeApp 产生 Native 动态库,ohosApp 负责 ArkUI 页面和 C++ 模块。三者的边界清晰,任何一层失败都可以单独定位。
6.2 原生模块注册
napi_init.cpp 通过 napi_module_register 注册 entry 模块,类型声明位于:
text
example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts
ArkTS 页面使用:
typescript
import kamlNative from 'libentry.so';
6.3 Native 动态库准备
执行:
bash
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
成功后检查:
text
example/ohosApp/entry/libs/arm64-v8a/libkaml.so
example/ohosApp/entry/src/main/cpp/include/libkaml_api.h
6.4 构建、签名和安装
根目录一键准备:
bash
./scripts/build-openharmony.sh
未签名 HAP 构建:
bash
./scripts/build-hap.sh example/ohosApp
仓库不会自动写入证书和 profile。需要正式签名时,把工程复制到独立目录:
bash
python3 scripts/prepare-signing-project.py /absolute/path/to/kaml-signing
将证书、profile、私钥和密码配置在独立工程中,再由 DevEco Studio 或本机 Hvigor 构建签名 HAP。签名完成前不要执行面向正式设备的安装验收。
四、完整代码对照
4.1 整体架构
text
ArkUI TextArea / template buttons
│ YAML string or scalar string
▼
libentry.so / C++ N-API
│ KamlInspect / KamlEncode
▼
libkaml.so / Kotlin-Native
│ Yaml.parseToYamlNode / Yaml.encodeToString
▼
KamlTransform -> JSON
│
▼
ArkUI result panel
4.2 文件清单
| 文件 | 职责 |
|---|---|
build.gradle.kts |
根 KMP 目标、OHOS 依赖替换和 publication |
vendor/snakeyaml-engine-kmp |
OHOS SnakeYAML KMP 源码构建 |
example/shared/.../KamlExamples.kt |
模板、解析、编码和共享自检 |
example/nativeApp/.../NativeBridge.kt |
C ABI、JSON 返回和 Native 内存释放 |
example/ohosApp/.../KamlClient.ets |
N-API JSON 解析和错误校验 |
example/ohosApp/.../Index.ets |
YAML 编辑、模板和结果页面 |
example/ohosApp/entry/src/main/cpp/napi_init.cpp |
N-API 导出、参数读取和释放 |
scripts/build-openharmony.sh |
测试、Native 链接和产物复制 |
scripts/build-hap.sh |
未签名 HAP 构建 |
scripts/check-native-deps.py |
ELF 架构和 SDK 符号检查 |
scripts/prepare-signing-project.py |
复制独立签名工程 |
docs/openharmony/images/kaml-workbench.jpg |
示例页面效果图 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| Kaml | Yaml.default.parseToYamlNode |
解析 YAML 文本 |
| Kaml | Yaml.default.encodeToString |
编码 Kotlin 值为 YAML |
| Shared | KamlExamples.inspect |
返回节点类型、摘要和节点数 |
| Shared | KamlExamples.encodeValue |
编码页面输入的普通文本 |
| Native | KamlInspect |
C ABI 解析入口 |
| Native | KamlEncode |
C ABI 标量编码入口 |
| N-API | parseYaml |
向 ArkTS 暴露解析方法 |
| N-API | encodeScalar |
向 ArkTS 暴露编码方法 |
| ArkTS | Index.inspect |
处理编辑器输入并切换结果页 |
4.4 ArkTS 与 Kotlin 的边界
ArkTS 只负责输入和页面状态:
typescript
this.result = parseYaml(this.yamlText);
Kotlin 负责 YAML 语义和节点统计:
kotlin
val node = Yaml.default.parseToYamlNode(yaml)
return KamlTransform(kind, summary, node.contentToString(), countNodes(node))
两者之间只传输 UTF-8 字符串和 JSON,不传输 Kotlin 对象、ArkTS class 实例或未校验的动态结构。
五、关键决策说明
决策 1:把 ohosArm64 加入公共构建约定
只有真正链接 ohosArm64 动态库,才能证明共享 Kaml 代码可以进入 OpenHarmony ARM64 运行时。标准 Linux ARM64 klib 不能替代 OHOS ABI。
决策 2:SnakeYAML 使用源码模块而不是伪造 ABI
上游 SnakeYAML KMP 没有 OHOS variant,项目将 common/native 源码放进 vendor 模块,生成真实 OHOS klib,并用 OHOS publication metadata 指向它。
决策 3:独立消费者必须通过构建产物消费
example 单独解析 Kaml,ohosApp 单独接收 .so 和头文件,避免根工程编译通过却无法被 DevEco CMake 打包。
决策 4:JSON 作为跨语言数据契约
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,也可以在不暴露内部对象布局的情况下扩展字段。
决策 5:页面操作必须调用真实 Kaml
模板只负责填充输入,解析按钮才触发 Yaml.default.parseToYamlNode。结果页展示真实节点内容和计数,不在 ArkTS 重写 YAML 业务逻辑。
决策 6:签名和构建分离
源码工程只生成未签名 HAP;签名工程通过脚本复制到独立目录。这样本机证书、profile、私钥和密码不会进入 AtomGit。
六、测试与验证
6.1 测试环境
本次自动验证使用:
- macOS;
- DevEco Studio 自带 JBR;
- Kotlin Multiplatform
2.2.21-1.0.0; - Gradle
9.7.1; - OpenHarmony API 20 Native SDK;
ohosArm64/arm64-v8a构建链路。
当前验证重点是共享测试、Native 编译、C++ N-API 编译、HAP 打包和 ELF 依赖检查。由于仓库按要求保持未签名,未宣称完成正式设备安装验收。
6.2 静态检查与单元测试
bash
./gradlew jvmTest
(cd example && ./gradlew :shared:jvmTest)
共享示例测试覆盖服务模板解析、boolean 内容、列表数量、标量编码和可编辑 YAML 再解析。
6.3 原生桥接和 HAP 验证
bash
./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
./scripts/build-hap.sh example/ohosApp
已验证结果:
text
BUILD SUCCESSFUL
libkaml.so: 0 unresolved strong imports
entry-default-unsigned.hap generated
Hvigor 输出 No signingConfig found for product default 属于预期提示,说明构建没有读取本机签名配置。
6.4 功能验证用例
用例 1:默认页面和模板
启动应用后进入"编辑 YAML"模式,编辑器载入服务配置模板,模板区显示服务配置和团队发布单两个入口。
用例 2:编辑并解析映射
修改 name 或 enabled,点击"解析 YAML",结果页显示"映射"、字段摘要、节点数量和结构内容。
用例 3:解析嵌套列表
载入团队发布单模板,确认 maintainers 列表和 release 嵌套映射可以由同一份 Kaml 代码解析。
用例 4:普通文本编码
在标量输入框输入 OpenHarmony 或中文文本,点击"编码",结果页显示 Kaml 输出的 YAML 标量。
用例 5:非法 YAML 错误
输入缩进错误或空内容,点击"解析 YAML",页面保留原文并显示 Native 返回的错误信息。
用例 6:恢复和模式切换
点击"恢复示例"清除上一次结果,使用"编辑 YAML"和"查看结果"切换页面工作区。
6.5 验证结论
自动测试、Kotlin/Native ARM64 链接、C++ N-API 编译、ArkTS 编译、Hvigor HAP 构建和 ELF 依赖检查均已完成。效果图展示的是已构建页面的交互布局;正式设备安装仍需要用户提供签名材料并在目标设备上执行。
七、运行效果
7.1 示例页面效果图

效果图中可以看到:
- 页面标题为"Kaml 工作台";
- 副标题说明页面支持编辑、解析和编码 YAML;
- 顶部提供"编辑 YAML"和"查看结果"两种模式;
- YAML 编辑器展示实际服务配置文本;
- "解析 YAML"会调用 Kotlin/Native 解析;
- "编码"会把普通文本转换为 YAML 标量;
- 内置模板区可以载入服务配置和团队发布单。
7.2 命令速查
bash
# 根工程测试、OHOS KLIB 和示例 Native 动态库
./scripts/build-openharmony.sh
# 单独运行共享测试和准备 .so / C 头文件
(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)
# 检查 OpenHarmony 原生依赖
python3 scripts/check-native-deps.py \
/Applications/DevEco-Studio.app/Contents/sdk/default/openharmony/native \
example/ohosApp/entry/libs/arm64-v8a
# 构建未签名 HAP
./scripts/build-hap.sh example/ohosApp
# 准备独立签名工程
python3 scripts/prepare-signing-project.py /absolute/path/to/kaml-signing
签名完成后再由 DevEco Studio 或 hdc 安装对应的签名 HAP。源码工程不会自动保存签名配置。
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配 :必须把 Kaml 的
commonMain真正编译成ohosArm64动态库。 - 不能把 Linux klib 当 OHOS klib:目标平台、sysroot 和 ABI 必须一致。
- SnakeYAML 依赖需要源码供应:上游没有 OHOS variant 时,应编译源码而不是改名伪装。
- N-API 不负责 YAML 语义:C++ 只做类型检查、字符串转换和释放,节点判断放在 Kotlin。
- 签名配置必须隔离:自动生成的本机证书路径不能进入 AtomGit。
8.2 已知问题
- 当前示例验证了 ARM64 OHOS 构建,但没有在未提供签名材料的情况下宣称正式设备安装完成;
- Kaml YAML 节点内容展示使用
contentToString(),它适合调试和示例,不是通用 YAML 格式化器; - HAP 构建依赖本机 DevEco Hvigor 和 OpenHarmony SDK,其他机器需要配置自己的工具路径;
- 当前页面是单页工作台,多页面应用需要在业务层集中管理编辑内容和解析结果;
- 生成的
.so、HAP 和 DevEco 缓存不会提交到 AtomGit。
8.3 未来优化方向
- 增加 YAML 文本格式化、复制结果和历史记录能力;
- 将解析结果扩展为可展开的树形节点视图;
- 为 Compose Multiplatform 增加共享工作台页面示例;
- 在持续集成中加入
ohosArm64编译和 HAP 未签名打包任务; - 为不同 OpenHarmony SDK 版本维护更清晰的依赖矩阵;
- 在完成签名和设备授权后补充真机安装截图和性能数据。
九、总结
9.1 核心难点回顾
本次适配真正需要处理的不是一个 ArkUI 页面,而是一条完整跨端链路:
text
YAML TextArea
→ ArkTS 状态和交互
→ C++ N-API
→ Kotlin/Native C ABI
→ Kaml YamlNode / serializer
→ JSON
→ ArkUI 结果页
9.2 封装层次
- Kaml 层:负责 YAML scanner、parser、节点树和序列化;
- OHOS vendor 层:提供真实
ohosArm64SnakeYAML 编译输入; - Native 层:生成 ARM64 动态库并输出有限 C ABI;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理编辑器、模板、结果页和错误状态;
- DevEco 层:完成 CMake、HAP、签名、安装和运行。
9.3 三条经验
- 先让共享 Kaml API 在 JVM 和 Native 通过,再接入 ArkUI 页面;
- 使用 JSON 和少量 C ABI 代替跨语言对象传递;
- 把自动测试、HAP 构建、ELF 检查和签名安装分别记录,避免把"编译成功"误认为"设备验收完成"。
9.4 适配成果
当前 Kaml OpenHarmony 适配已完成:
ohosArm64Kotlin/Native 目标;- OHOS Kotlin Serialization、Okio 和 SnakeYAML 依赖替换;
- 真实 OHOS SnakeYAML source build;
- Kaml YAML map/list/scalar 解析和标量编码;
- Kotlin/Native + C ABI + N-API 桥接;
- 可编辑、可载入模板、可查看结果的 ArkUI 工作台;
- JVM 测试、Native 链接、C++/ArkTS 编译和 HAP 构建;
- OpenHarmony ELF 原生依赖检查;
- 未签名工程和独立签名准备脚本;
- AtomGit 项目文档、README、验收说明和效果图。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。