本文记录 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,再自行决定页面呈现方式。

二、实现路线图

第 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 工程一致的分层:

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 Multiplatform2.2.21-1.0.0JVM、Kotlin/Native 和 KLIB
Kaml OHOS 版本0.104.0-ohos.1OpenHarmony 适配发布版本
OpenHarmony 目标ohosArm64ARM64 真机动态库
DevEco product6.0.0(20)工程兼容和目标版本
DevEco Studio6.0.0 ReleaseIDE 版本,负责工程同步、HAP 构建与签名
ABIarm64-v8aHAP 原生库架构
Gradle9.7.1根工程和 example 构建

执行 Gradle 脚本前选择受支持的 JDK:

export JAVA_HOME="/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home"
java -version

目标 SDK 满足构建条件,只能说明工程可以编译,并不能替代设备安装和运行能力检查。

1.3 创建 OpenHarmony 示例目录

示例页面围绕“输入 YAML、执行 Kaml、查看结果”组织:

标题区          Kaml 工作台 / 在设备上编辑、解析和编码 YAML
模式区          编辑 YAML / 查看结果
编辑区          多行 YAML 编辑器、字符数和恢复示例
操作区          解析 YAML、普通文本编码为 YAML 标量
模板区          服务配置、团队发布单
结果区          根节点类型、摘要、节点数和结构内容
状态区          Native 连接状态、解析错误和调用链

页面输入会经过完整链路;模板按钮只是快速填充文本,不绕过 Kaml。这样即使用户把 tags 改成嵌套列表,解析结果也来自实际的 YamlNode。

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

2.1 加入 ohosArm64 目标

根工程在原有目标集合中增加 OpenHarmony 目标:

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 中替换为对应变体:

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 目标和本地出版坐标:

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:

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 消费:

sourceSets {
    commonMain.dependencies {
        implementation("com.charleskorn.kaml:kaml:${property("kamlVersion")}")
    }
}
2.5 通过构建产物消费共享库

example/nativeApp 构建一个受 linker map 约束的 shared library:

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:

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:

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
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 自检,用于验证基础能力:

  1. map 可以解析;
  2. boolean 内容保持不变;
  3. sequence 项目数量正确;
  4. scalar 可以编码;
  5. 可编辑模板可以再次解析。

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 对象。

最终采用三层桥接:

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 导出函数
@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:

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 生命周期
ArkTS parseYaml(yaml)
        │
        ▼
napi_get_value_string_utf8
        │
        ▼
KamlInspect(void*)
        │
        ▼
napi_create_string_utf8
        │
        ▼
KamlFree(nativeBuffer)
        │
        ▼
return JSON string

CMake 将 Kotlin/Native 动态库作为 imported library:

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:

export const encodeScalar: (value: string) => string;
export const parseYaml: (yaml: string) => string;

客户端先解析 JSON,再把 Native 错误转换成页面异常:

export function parseYaml(yaml: string): KamlTransform {
  return parse<KamlTransform>(kamlNative.parseYaml(yaml));
}
5.2 编辑 YAML 和模板载入

页面的核心输入是 TextArea,不是硬编码文本:

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 编辑器:

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 工程结构
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 模块,类型声明位于:

example/ohosApp/entry/src/main/cpp/types/libentry/index.d.ts

ArkTS 页面使用:

import kamlNative from 'libentry.so';
6.3 Native 动态库准备

执行:

(cd example && ./gradlew :shared:jvmTest :nativeApp:prepareOhos)

成功后检查:

example/ohosApp/entry/libs/arm64-v8a/libkaml.so
example/ohosApp/entry/src/main/cpp/include/libkaml_api.h
6.4 构建、签名和安装

根目录一键准备:

./scripts/build-openharmony.sh

未签名 HAP 构建:

./scripts/build-hap.sh example/ohosApp

仓库不会自动写入证书和 profile。需要正式签名时,把工程复制到独立目录:

python3 scripts/prepare-signing-project.py /absolute/path/to/kaml-signing

将证书、profile、私钥和密码配置在独立工程中,再由 DevEco Studio 或本机 Hvigor 构建签名 HAP。签名完成前不要执行面向正式设备的安装验收。

四、完整代码对照

4.1 整体架构

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-kmpOHOS SnakeYAML KMP 源码构建
example/shared/.../KamlExamples.kt模板、解析、编码和共享自检
example/nativeApp/.../NativeBridge.ktC ABI、JSON 返回和 Native 内存释放
example/ohosApp/.../KamlClient.etsN-API JSON 解析和错误校验
example/ohosApp/.../Index.etsYAML 编辑、模板和结果页面
example/ohosApp/entry/src/main/cpp/napi_init.cppN-API 导出、参数读取和释放
scripts/build-openharmony.sh测试、Native 链接和产物复制
scripts/build-hap.sh未签名 HAP 构建
scripts/check-native-deps.pyELF 架构和 SDK 符号检查
scripts/prepare-signing-project.py复制独立签名工程
docs/openharmony/images/kaml-workbench.jpg示例页面效果图

4.3 关键 API 对照

层次API作用
KamlYaml.default.parseToYamlNode解析 YAML 文本
KamlYaml.default.encodeToString编码 Kotlin 值为 YAML
SharedKamlExamples.inspect返回节点类型、摘要和节点数
SharedKamlExamples.encodeValue编码页面输入的普通文本
NativeKamlInspectC ABI 解析入口
NativeKamlEncodeC ABI 标量编码入口
N-APIparseYaml向 ArkTS 暴露解析方法
N-APIencodeScalar向 ArkTS 暴露编码方法
ArkTSIndex.inspect处理编辑器输入并切换结果页

4.4 ArkTS 与 Kotlin 的边界

ArkTS 只负责输入和页面状态:

this.result = parseYaml(this.yamlText);

Kotlin 负责 YAML 语义和节点统计:

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 静态检查与单元测试

./gradlew jvmTest
(cd example && ./gradlew :shared:jvmTest)

共享示例测试覆盖服务模板解析、boolean 内容、列表数量、标量编码和可编辑 YAML 再解析。

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
./scripts/build-hap.sh example/ohosApp

已验证结果:

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 命令速查

# 根工程测试、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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配:必须把 Kaml 的 commonMain 真正编译成 ohosArm64 动态库。
  2. 不能把 Linux klib 当 OHOS klib:目标平台、sysroot 和 ABI 必须一致。
  3. SnakeYAML 依赖需要源码供应:上游没有 OHOS variant 时,应编译源码而不是改名伪装。
  4. N-API 不负责 YAML 语义:C++ 只做类型检查、字符串转换和释放,节点判断放在 Kotlin。
  5. 签名配置必须隔离:自动生成的本机证书路径不能进入 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 页面,而是一条完整跨端链路:

YAML TextArea
    → ArkTS 状态和交互
    → C++ N-API
    → Kotlin/Native C ABI
    → Kaml YamlNode / serializer
    → JSON
    → ArkUI 结果页

9.2 封装层次

  • Kaml 层:负责 YAML scanner、parser、节点树和序列化;
  • OHOS vendor 层:提供真实 ohosArm64 SnakeYAML 编译输入;
  • Native 层:生成 ARM64 动态库并输出有限 C ABI;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理编辑器、模板、结果页和错误状态;
  • DevEco 层:完成 CMake、HAP、签名、安装和运行。

9.3 三条经验

  1. 先让共享 Kaml API 在 JVM 和 Native 通过,再接入 ArkUI 页面;
  2. 使用 JSON 和少量 C ABI 代替跨语言对象传递;
  3. 把自动测试、HAP 构建、ELF 检查和签名安装分别记录,避免把“编译成功”误认为“设备验收完成”。

9.4 适配成果

当前 Kaml OpenHarmony 适配已完成:

  • ohosArm64 Kotlin/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。

参考文档

Logo

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

更多推荐