本文记录 KToon 接入 OpenHarmony 的完整过程,覆盖序列化库盘点、鸿蒙 kotlinx / Ktor 制品线选型、真实引擎编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

KToon 是基于 kotlinx.serialization 的 Kotlin Multiplatform 序列化库:实现 TOON 格式(Token-Oriented Object Notation,表头 + CSV 行的紧凑文本格式,比 JSON 小 30–60%),由 ktoon-core(Toon : StringFormat 编解码引擎)、ktoon-ktor(Ktor client ContentNegotiation 集成)、ktoon-ktor-server(Ktor server ContentNegotiation 集成)三个模块组成。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"Compose 源码集隔离")有本质区别:ktoon 没有一行 Compose 代码、没有一处 expect 声明,三个库模块被原样全量编译成 OpenHarmony KLIB;更关键的是,社区 Maven 上出现了完整的 Ktor ohos 制品线(client / server / 双 CIO 引擎全部带 ohosArm64 / ohosX64 变体),因此本仓的 Demo 能在真机上做应用内本机回环 HTTP 实测——同一份载荷分别以 application/toon 与 application/json 真实过 TCP,把"省多少带宽"这个问题在设备上闭环。

项目地址: AtomGit/oh-tpc/ktoon.git

上游仓库: JosephSanjaya/ktoon

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配

KToon 为 Kotlin Multiplatform 应用提供"对象 ↔ 紧凑文本"这一件事的完整答案:Toon.Default 是标准 StringFormat,encodeToString / decodeFromString 直接对接 @Serializable 模型;对象数组被编码为「表头行 + CSV 数据行」,字段名只在表头出现一次,比 JSON 小 30–60%;词法器逐行解析,O(N) 单遍;ktoon-ktor / ktoon-ktor-server 把这套格式注册进 Ktor 的 ContentNegotiation,Accept: application/toon 一行开启线格式切换。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要"更省流量的序列化",但上游构建(Kotlin 2.3.20 / Gradle 9.4.1 / AGP 9.1 / CMP 1.11-alpha / sjy-build-logic 约定子模块)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明序列化引擎与 Ktor 集成在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证编解码引擎、Ktor 转换器、coroutines 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路——以及"过一次真网络"的线格式对比。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.3.20 / AGP 9.1 / sjy-build-logic 复合构建与鸿蒙工具链无法共存于一个 Gradle 构建。
依赖矩阵引擎依赖 kotlinx-serialization,集成依赖 Ktor 全家(client-core / content-negotiation / cio / server-core…),每一个都要有 ohos 变体才能解析。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
载荷语义跨不了桥JsonElement 的 serializer 绑定 Json 格式;嵌套集合字段上游编码器不支持——桥的载荷 schema 必须重新设计。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:三个库模块的 ohos 全量编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。序列化语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。

1.2 库提供的能力

Demo 提供一个真实的序列化会话,对应桥的全部入口:

  • 编码(encode):Toon.encodeToString(List<User>) 把对象数组编成 items[8]{id,name,email,is_active,role,score}: 表头 + CSV 行——字段名只出现一次,行内只有值;响应同时给出压缩 JSON 与 TOON 的字节数和节省百分比;
  • 解码(decode):Toon.decodeFromString<List<User>>(toon) 把 TOON 文本还原成对象数组再序列化为 pretty JSON 展示,roundTrip 往返校验;
  • 本机 HTTP 实测(http):应用内启动 Ktor CIO server(注册 toon() + json() 双格式),CIO client 分别以两种 Accept 请求 /users,对比线上传输字节数——全程 127.0.0.1,不触外网;
  • 版本握手:ktoonVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
op回显本次操作(encode / decode / http)。
count载荷行数。
jsonBytes / toonBytes压缩 JSON 与 TOON 的 UTF-8 字节数(同口径对比)。
savedPct(jsonBytes - toonBytes) * 100 / jsonBytes。
toon / jsonencode 返回 TOON 文本;decode 返回 pretty JSON。
elapsedMs仅 http:回环全程耗时。
roundTripdecode(encode(x)) == x 语义校验。

引擎侧完整能力(自定义 serializer、多态、错误行号定位、Ktor 双向转换器)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 144 项测试交叉验证,证明 ohos .so 里执行的编解码逻辑没有漂移。

1.3 实现适配

维度要求
代码复用三个库模块的全部 commonMain 原样编译,不提取、不重写;仅 2 行 JVM 风格 charset 调用被修正(ohos stdlib 无 kotlin.text.Charsets)。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);上游演示 App(shared / androidApp / iosApp / backend)与 sjy-build-logic 子模块留盘不进构建。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律kotlinx 系换到 ohos 制品线(换线不换版);Ktor 因无 3.4.1 ohos 制品升一条小版本到分叉线 3.5.1-0.1.0-01。
UI 完整单页覆盖全部桥接入口:编码 / 解码 / 关于三 Tab + 本机 HTTP 实测 + 操作日志 + 版本握手。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit,不出现任何第三方镜像站。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认零 Compose / 零 expect、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-08 钉版、Ktor ohos 线选型
第 3 阶段:桥协议建模     ── 3 op JSON 契约、纯原语列载荷、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 版本握手、编码/解码双文本域、本机 HTTP 实测、操作日志
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
ktoon-core/                          上游编解码引擎(进 ohos 构建,jvm + ohosArm64 + ohosX64)
ktoon-ktor/                          上游 Ktor client 集成(进 ohos 构建,源码零改动)
ktoon-ktor-server/                   上游 Ktor server 集成(进 ohos 构建,2 行 charset 修正)
example/nativeApp/                   Kotlin/Native 动态库(Bridge.kt:@CName × 2)
example/harmonyApp/                  DevEco Stage、CMake N-API 和 ArkTS 页面
README.md                            鸿蒙版首页(原文备份 README.upstream.md)
README.OpenHarmony.md / _CN.md       鸿蒙适配说明(中英双份)
doc/                                 本文章与效果图
shared/ androidApp/ iosApp/          上游 CMP 演示 App,留盘不进 settings
backend/ sjy-build-logic/            上游 JVM 演示服务器与约定插件子模块,留盘不进构建

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :ktoon-core、:ktoon-ktor、:ktoon-ktor-server 与 :example:nativeApp,rootProject.name = "ohos_ktoon"。上游的 AGP classpath、sjy-build-logic 复合构建、foojay resolver、CMP 1.11-alpha 与 KSP/ktorfit 插件全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-08(eazytec Maven)
JDK21(org.gradle.java.home 钉本机 F:\cmp\.tools\jdk-21.0.12.1+1)
OpenHarmony 目标ohosArm64 + ohosX64(jvm 保留作交叉验证)
Gradle9.6.1(上游 wrapper 为 9.4.1,因本机下载超时对齐到已缓存的 9.6.1——KStore 同款组合)
kotlinxserialization 1.11.0-0.1.0-07、coroutines 1.11.0-0.1.0-07(传递:atomicfu 0.33.0-0.1.0-07、kotlinx-io 0.9.1-0.1.0-07)
Ktor3.5.1-0.1.0-01(ohos 分叉线;上游钉 3.4.1,该版本无 ohos 制品)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.ktoon
$env:JAVA_HOME = "F:\cmp\.tools\jdk-21.0.12.1+1"
java -version

注意:org.gradle.java.home 只决定 daemon JVM,gradlew 启动器自身仍需 JAVA_HOME——这是本次实测的第一个坑。

1.3 源码依赖度盘点
模块commonMain 文件依赖形态处置
ktoon-core4(Toon / ToonEncoder / ToonDecoder / ToonLexer)纯 Kotlin + kotlinx-serialization-core,零 UI、零 AGP整体编译(jvm + 双 ohos)
ktoon-ktor2(ContentNegotiationExt / ToonContentConverter)ktoon-core + ktor-client 四件套整体编译(源码零改动)
ktoon-ktor-server2 + jvmMain demo 3ktoon-core + ktor-server 两件套整体编译(2 行 charset 修正)

expect 声明全库为 0:唯一的 expect/actual(getPlatform())在上游 shared 演示模块里,随根构建替换一并留在盘上。这意味着 ohos 侧不需要补任何 actual 文件——适配面天然最小。

ktoon-ktor-server 值得单独说明:ToonContentConverter.serialize 里写的是 toonString.toByteArray(Charsets.UTF_8)——kotlin.text.Charsets 是 JVM/JS 产物,ohos stdlib 里没有这个对象,ohos 编译直接报 Unresolved reference 'Charsets'。整个 fork 对上游源码的唯一修改就是这两行:

// 修正前(JVM 习语,ohos 编不过)
val bytes = toonString.toByteArray(Charsets.UTF_8)
contentType.withCharset(Charsets.UTF_8)

// 修正后(ktor 多平台 charset + stdlib UTF-8 编码器,JVM 侧输出逐字节不变)
import io.ktor.utils.io.charsets.Charsets
val bytes = toonString.encodeToByteArray()
contentType.withCharset(Charsets.UTF_8)
1.4 页面边界
标题区          KTOON · OPENHARMONY / 版本握手行(aboutToAppear 真调 ktoonVersion())
Tab 切换        编码 / 解码 / 关于
编码 Tab        JSON 多行文本域(默认 8 行样例)+「编码为 TOON」(深绿)+「本机 HTTP 实测」(蓝)
编码结果区      TOON 等宽文本(可复制)+ 对比行「JSON x B → TOON y B · 省 p%」+ roundTrip 徽标
HTTP 结果区     「Ktor CIO 回环 · 8 行 · JSON x B / TOON y B · 省 p% · z ms」+ roundTrip 徽标
解码 Tab        TOON 多行文本域+「用样例生成」+「解码为 JSON」(蓝)
解码结果区      pretty JSON +「TOON x B → JSON y B · n 行」+ roundTrip 徽标
关于 Tab        版本握手、TOON 简介、架构分层、AtomGit 仓库与社区链接
日志区          带时间戳的操作日志(→ 请求 / ← 响应,最新在前,上限 100 条)

页面启动时先调 ktoonVersion() 做握手,状态行与日志同时留痕。

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

2.1 制品线验证(本适配的成败手)

先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 ohos 制品。用 .module 元数据逐个验证:

org.jetbrains.kotlinx:kotlinx-serialization-json(-core):1.11.0-0.1.0-07
  variants: ohosArm64Api-published / ohosX64Api-published / jvmApi-published / jvmRuntime-published
org.jetbrains.kotlinx:kotlinx-coroutines-core(-test):1.11.0-0.1.0-07
  variants: 同上(jvm + ohos 双线齐全)
io.ktor:ktor-client-core:3.5.1-0.1.0-01
  variants: ohosArm64ApiElements-published / ohosX64ApiElements-published + 全平台
  kotlin-stdlib dependency: 2.4.20-0.1.0-08
io.ktor:ktor-client-cio / ktor-server-cio / ktor-client-content-negotiation /
        ktor-server-content-negotiation / ktor-serialization(-kotlinx-json):3.5.1-0.1.0-01
  variants: ohos 双 ABI 齐全

四个关键结论:其一,kotlinx 系版本主体与 KStore 适配一致(serialization / coroutines 1.11.0),换线不换版;其二,Ktor 的 ohos 分叉线是本批适配的新变量——maven-metadata 里 2.2.21-OH0.1.0-06、3.3.3-1.1.0-05、3.5.1-0.1.0-01 三条线并存,3.5.1-0.1.0-01 与 stdlib 2.4.20-0.1.0-08 同线,且 client / server / CIO / content-negotiation 全家族都发布了 ohosArm64 / ohosX64 变体;其三,上游钉的 3.4.1 在仓库里只有普通版本、没有 ohos 变体——Ktor 无法做到"换线不换版",只能升一条小版本(3.4.1 → 3.5.1)或降一条(→ 3.3.3),本仓取与工具链同线的新线;其四,kotest 没有 ohos 制品,处置见第 2.4 节。

2.2 工具链钉版(为什么是 2.4.20-0.1.0-08)

社区此前批准的钉法有两代:qrose 的 Kotlin 2.2.21-1.0.0(配 Gradle 8.14.1)和 KStore 的 2.4.20-0.1.0-07(配 Gradle 9.6.1)。ktoon 的钉版由 Ktor 反推:ktor 3.5.1-0.1.0-01 依赖 stdlib 2.4.20-0.1.0-08,因此 Kotlin 钉 2.4.20-0.1.0-08(该线最新)。serialization / coroutines 1.11.0-0.1.0-07 基于 stdlib -07,Gradle 冲突解析统一收敛到 -08——同一 2.4.20 编译线内元数据互通,无降级无漂移。

Gradle 上游 wrapper 是 9.4.1,本机拉取 services.gradle.org 超时,对齐到本机已缓存的 9.6.1(KStore 验证过 2.4.20 线 + Gradle 9.x 组合)。另删除上游 gradle/gradle-daemon-jvm.properties(foojay 拉取 JetBrains JBR 的策略与钉死本机 JDK 21 冲突)。

settings.gradle.kts 把 eazytec 仓库写在最前:

pluginManagement {
    repositories {
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
    repositories {
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
        google()
        mavenCentral()
    }
}

rootProject.name = "ohos_ktoon"

include(":ktoon-core")
include(":ktoon-ktor")
include(":ktoon-ktor-server")
include(":example:nativeApp")

gradle/libs.versions.toml 收敛为 Kotlin 2.4.20-0.1.0-08 + kotlinx ohos 线 + Ktor 3.5.1-0.1.0-01 三组坐标;gradle.properties 钉 JDK 21 路径、version=1.0.0-ohos.1、kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true。

2.3 编译真实引擎

ktoon-core/build.gradle.kts 收敛为纯 KMP 模块:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.kotlin.serialization)   // 上游经约定插件注入,重写后必须模块级补回
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

    jvm()                                    // 交叉验证用
    ohosArm64()
    ohosX64()

    sourceSets {
        commonMain.dependencies {
            implementation(libs.kotlinx.serialization.core)   // 1.11.0-0.1.0-07
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

ktoon-ktor / ktoon-ktor-server 同构,依赖换成 ktor client / server 坐标。实测:

.\gradlew.bat :ktoon-core:compileKotlinOhosArm64 :ktoon-core:compileKotlinOhosX64            # BUILD SUCCESSFUL
.\gradlew.bat :ktoon-ktor:compileKotlinOhosArm64 :ktoon-ktor-server:compileKotlinOhosArm64   # BUILD SUCCESSFUL(server 需先修 2 行 Charsets)

三个模块的 commonMain 文件除那 2 行 charset 外一个没动,ohos 侧零新增源码。

2.4 测试依赖的处置:kotest 没有 ohos 制品

ktoon-ktor-server 的 commonTest 依赖 kotest-property,对 ohos 测试编译同样可见,kotest 解析不到 ohos 变体就会卡死 compileTestKotlinOhosArm64。处置是两条纪律的组合:测试统一在 JVM 跑(jvmTest),根构建全局禁用 ohos 测试任务:

subprojects {
    tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
        .configureEach { enabled = false }
}

这样上游测试文件零改动保留,ohos 侧只承担编译与链接。另外 jvmMain 的演示服务器依赖 ktor-server-compression——上游由约定插件隐式注入,重写后需在模块级显式补回,否则 compileKotlinJvm 报 Unresolved reference 'compression'。

第 3 阶段:桥协议建模

3.1 JSON 契约
io.ktoon/**(上游引擎 + Ktor 转换器,原样编译)
        ↓ Toon.encodeToString / decodeFromString / ContentNegotiation
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API ktoonDispatch() / ktoonVersion()
        ↓
ArkUI Index.ets(单页三 Tab)

请求与响应:

请求 : {"op":"encode","json":"<User 数组 JSON 文本>"}
       {"op":"decode","toon":"<TOON 文本>"}
       {"op":"http"}
响应 : {"ok":true,"op":"...","count":8,"jsonBytes":755,"toonBytes":478,"savedPct":36,
        "toon":"items[8]{...}:",...,"roundTrip":true}
       {"ok":false,"error":"..."}

两个对称设计:encode 的 jsonBytes 用压缩 JSON(无 prettyPrint)与 TOON 同口径对比,不占格式化的便宜;roundTrip 比较解码对象而非文本字节,语义校验不会被空白差异干扰。

3.2 载荷 schema 为什么是"纯原语列的 List<User>"

桥内载荷这样定:

@Serializable
data class User(
    val id: Int,
    val name: String,
    val email: String,
    @SerialName("is_active") val isActive: Boolean = true,
    val role: String = "user",
    val score: Double = 0.0,
)

三个约束层层筛出来的结果:

  1. JsonElement 不能当载荷:它的 serializer 绑定 Json 格式,交给 Toon 的通用 Encoder 会直接拒编——所以必须定义具体的 @Serializable 模型;
  2. 嵌套集合字段不支持:桥的第一版 schema 带 tags: List<String>,JVM 探针实测:根为单对象时编码直接抛 SerializationException(ToonCollectionEncoder 不认嵌套 String 列表),根为对象数组时静默产出错位数据——往返 equal=false,解码甚至抛 IndexOutOfBoundsException。这是上游引擎的能力边界,不是 ohos 的问题,但必须在桥上绕开;
  3. 对象数组是权威形态:TOON 的表头 + CSV 行就是为对象数组设计的,纯原语列的 List<User> 往返 equal=true(JVM 探针断言通过)。

因此最终 schema 六列全原语(tags 换成 role 字符串列),同载荷的引擎侧参考值:压缩 JSON 755 B → TOON 478 B,省 36%,TOON 文本以 items[8]{id,name,email,is_active,role,score}: 开头。

suspend 调用统一用 runBlocking + withTimeout 收进同步 C ABI:桥全程不碰 Main 派发(resaca 仓记录过鸿蒙线 Dispatchers.Main 的跨线程问题),Ktor 回环整体设 20 秒超时。

3.3 错误通道

每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:

@CName("KtoonDispatch")
fun ktoonDispatch(request: String): String = try {
    /* op 分发 */
} catch (t: Throwable) {
    buildJsonObject {
        put("ok", false)
        put("error", t.message ?: t.toString())
    }.toString()
}

非法 JSON、缺 op / json / toon、不支持的 op、schema 不匹配(多余字段)、TOON 语法错误(带行号与上下文片段的 SerializationException),全部走同一个 ok:false 通道,ArkTS 侧统一渲染。

第 4 阶段:原生桥接(技术难点)

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libktoon_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosktoon.so)
  │ KtoonDispatch / KtoonVersion
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制序列化语义,所以选择 C ABI + JSON——与 qrose / KStore 完全同款。

4.2 Kotlin/Native 导出函数
@CName("KtoonDispatch")
fun ktoonDispatch(request: String): String   // encode / decode / http 一口进

@CName("KtoonVersion")
fun ktoonVersion(): String = "1.0.0-ohos.1"  // 版本握手
C ABI 符号ArkTS 方法覆盖内核
KtoonDispatchktoonDispatch(req)Toon 编解码 + Ktor CIO 回环 + 字节对比
KtoonVersionktoonVersion()fork 版本握手(真机版本行的数据来源)

op=http 的实现是本桥的增量:embeddedServer(ServerCIO, host="127.0.0.1", port=18080) 注册 toon() + json() 双格式并暴露 GET /users;HttpClient(ClientCIO) 分别以 Accept: application/toon / application/json 拉取,记录线上字节差,finally 里 server.stop(500, 2000) 保证无端口残留。一个实现细节:Ktor 3.5 把 server ContentNegotiation 从 io.ktor.server.plugins 挪到了 io.ktor.server.plugins.contentnegotiation,桥的 import 必须用新包(上游库模块只 import Configuration / ContentNegotiationConfig,所以不受影响)。

4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"ktoonDispatch", nullptr, NapiKtoonDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"ktoonVersion", nullptr, NapiKtoonVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一四步:napi_get_cb_info 取参、napi_typeof 校验必须是 string(version 校验 argc==0)、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。取字符串用两段式写法(先量长度再 std::vector<char> 拷贝,兼容 API 26 NAPI 头的 const 约束)。KN 返回的字符串必须经 libohosktoon_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohosktoon_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。

4.4 CMake 和类型声明
add_library(ohosktoon SHARED IMPORTED)
set_target_properties(ohosktoon PROPERTIES
  IMPORTED_LOCATION ${KN_SO}
  IMPORTED_NO_SONAME TRUE)

add_library(ktoon_napi SHARED napi_init.cpp)
target_link_libraries(ktoon_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosktoon)

OHOS_ARCH 为空或 libohosktoon.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。

export const ktoonDispatch: (request: string) => string;
export const ktoonVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 版本握手和触发操作
aboutToAppear(): void {
  this.jsonText = this.prettyJson(DEFAULT_JSON);
  this.handshakeVersion();               // 真调 ktoonVersion(),状态行 + 日志留痕
}

private doEncode(): void {
  const raw = ktoonDispatch(JSON.stringify({ op: 'encode', json: this.jsonText }));
  const res = JSON.parse(raw) as DispatchOk;   // ok:false 时错误原文进状态行与日志
  /* @State 刷新 TOON 文本、对比行、roundTrip 徽标 */
}

encode 前不做人肉校验——schema 不匹配、非法 JSON 都由 Kotlin 侧 ok:false 通道如实回报,页面只渲染。

5.2 页面交互

编码 Tab 自上而下:JSON 文本域(默认 8 行样例)、「编码为 TOON」(深绿)/「本机 HTTP 实测」(蓝)双按钮、TOON 等宽文本(可复制)、对比行与 roundTrip 徽标、HTTP 实测结果行。解码 Tab:TOON 文本域(「用样例生成」内部先调 encode 回填)、「解码为 JSON」(蓝)、pretty JSON 结果。每条日志带 [HH:mm:ss] 时间戳,→ 行显示请求、← 行显示响应,最新在前、上限 100 条。关于 Tab 展示版本握手行、TOON 简介、ArkTS → NAPI → C ABI → Kotlin/Native → Toon 引擎 + Ktor CIO 分层说明与 AtomGit 链接。

5.3 对比度和错误边界

主操作深绿按钮白字、解码蓝、语义对齐;ok:false 时错误原文进状态行与日志(红字)。Native 调用本身抛错(理论不该发生)同样被 try/catch 收进日志区,ArkTS 侧 Record<string, Object> 模式取 code/name/message。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosktoon)+ copyKnSo*
│   └── src/ohosMain/kotlin/org/terminator/ohos/ktoon/
│       └── Bridge.kt                         @CName × 2 + User schema + Ktor 回环
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.ktoon、versionName 1.0.0-ohos.1
    └── entry/
        ├── build-profile.json5               externalNativeOptions + abiFilters [arm64-v8a, x86_64]
        ├── libs/{arm64-v8a,x86_64}/          linkDebugShared* 经 copyKnSo* 回填的三件套(gitignore)
        ├── src/main/cpp/CMakeLists.txt       IMPORTED 引入 KN so、$ORIGIN rpath
        ├── src/main/cpp/napi_init.cpp        N-API 导出、参数检查、DisposeString
        ├── src/main/cpp/include/libohosktoon_api.h
        ├── src/main/cpp/types/libktoon_napi/{index.d.ts, oh-package.json5}
        ├── src/main/ets/entryability/EntryAbility.ets
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面(module.json5 含 INTERNET 权限供回环使用)
6.2 构建、签名和安装
$env:JAVA_HOME = "F:\cmp\.tools\jdk-21.0.12.1+1"
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

HAP 在 DevEco Studio 打开 example/harmonyApp 构建(不要打开仓库根目录)。签名在 File → Project Structure → Signing Configs 勾选自动生成,证书写入本机、不进入仓库(build-profile.json5 的 signingConfigs 保持空数组;提交前可用 git update-index --skip-worktree 保护被 IDE 回填的文件)。

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.terminator.ohos.ktoon -m entry

四、完整代码对照

4.1 整体架构

io.ktoon/**(上游引擎原样编译)
    │ Toon / ToonEncoder / ToonDecoder / ToonLexer
io.ktoon.ktor(.server)/**(上游 Ktor 集成原样编译)
    │ ContentNegotiationConfig.toon() / ToonContentConverter
Bridge.kt
    │ KtoonDispatch / KtoonVersion
libktoon_napi.so
    │ ktoonDispatch / ktoonVersion
Index.ets
    │ JSON parse + @State 更新
    ▼
ArkUI 真机页面(单页三 Tab)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_ktoon、演示模块移出
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-08 + kotlinx ohos 线 + Ktor 3.5.1-0.1.0-01 钉版
build.gradle.kts(根)版本 1.0.0-ohos.1、全局禁用 Ohos*Test* 任务
ktoon-core / ktoon-ktor / ktoon-ktor-server 的 build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64,serialization 插件模块级补回
ktoon-ktor-server/.../ToonContentConverter.kt全仓唯一源码修改(2 行 charset)
example/nativeApp/build.gradle.kts双 ABI sharedLib、copyKnSo* 回填三件套
nativeApp/.../Bridge.kt2 个 C ABI、3 op 分发、Ktor 回环、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(17 节)

4.3 关键 API 对照

层次API作用
引擎Toon.encodeToString(value)reified 序列化为 TOON 表格文本
引擎Toon.decodeFromString<T>(string)TOON 文本反序列化,语法错误带行号上下文
引擎Toon(serializersModule)自定义 contextual serializer 注入
client 集成ContentNegotiationConfig.toon()注册 application/toon 转换器
server 集成ContentNegotiation 配置.toon()服务端同款注册(Configuration 接收器)
集成ToonContentConverterContentConverter 实现,双向复用
回环embeddedServer(ServerCIO) / HttpClient(ClientCIO)应用内 server + client
NativeKtoonDispatch / KtoonVersionC ABI JSON 导出
N-APIktoonDispatch(req) / ktoonVersion()向 ArkTS 暴露能力
ArkTSdoEncode() / doDecode() / doHttp()触发操作并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const res = JSON.parse(ktoonDispatch(JSON.stringify({
  op: 'encode',
  json: '[{"id":1,"name":"Aria Chen","email":"aria.chen@ktoon.dev","is_active":true,"role":"admin","score":98.5}]'
})));
// res.ok === true;res.toon === 'items[8]{id,name,email,is_active,role,score}: ...'
"encode" -> {
    val users: List<User> = Json.decodeFromString(request.required("json"))
    val toonText = Toon.encodeToString(users)
    val roundTrip = Toon.decodeFromString<List<User>>(toonText) == users
    /* jsonBytes / toonBytes / savedPct / toon / roundTrip 打包进响应 */
}

两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。

五、关键决策说明

决策 1:三个库模块全量编译,不抽内核

ktoon 零平台代码、零 expect、零 Compose——抽内核没有任何收益,反而引入双实现漂移。真编译让 Toon 引擎与两份 Ktor 转换器都是上游原件,"比 JSON 小 30–60%"的对比跑在真机上。

决策 2:工具链升到 Kotlin 2.4.20-0.1.0-08,Gradle 对齐 9.6.1

Ktor ohos 线的 stdlib 依赖是 2.4.20-0.1.0-08,钉版由此反推;serialization / coroutines 的 -07 线经 Gradle 冲突解析收敛到 -08,同一编译线内互通。Gradle 上游 9.4.1 因本机网络对不齐,换到已缓存且经 KStore 验证的 9.6.1。

决策 3:Ktor 换线 3.5.1-0.1.0-01(升一条小版本)

上游 3.4.1 没有 ohos 制品,可选分叉线是 3.3.3-1.1.0-05(旧后缀方案、旧 stdlib)与 3.5.1-0.1.0-01(与 stdlib -08 同线)。本库用到的 API(ContentConverter、ContentNegotiationConfig、CIO 双引擎)在 3.5.1 上源码兼容,JVM 侧 57 项集成测试佐证。代价是 server 侧 receive 失败的异常包裹行为有变(见 8.2),对本库 API 无影响。

决策 4:桥载荷固定"纯原语列 List<User>",Demo 带本机回环 HTTP

JsonElement 过不了 Toon 编码器、嵌套集合字段是上游引擎盲区——载荷 schema 被这两个事实筛成六列全原语。回环 HTTP 则是把"Ktor 就绪"从编译证明升级成运行证明:CIO server/client 都有 ohos 制品,Accept 协商 + 线上字节差在设备上直接读数。

决策 5:desktopTest 风格交叉验证 + 测试零改动

引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;144 项 jvmTest 跑的就是上游原测试文件。kotest 无 ohos 制品的处置不是改测试,而是"测试统一 JVM + 根构建禁用 Ohos*Test*";3 个红测试逐一归因(见 8.1),无一由适配引入——交叉验证的有效性才成立。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x86_64)、JDK 21(F:\cmp\.tools)、Gradle 9.6.1、DevEco Studio 5.1.0 Release(HAP 构建)与鸿蒙真机(arm64)。

6.2 JVM 交叉验证

.\gradlew.bat :ktoon-core:jvmTest :ktoon-ktor:jvmTest :ktoon-ktor-server:jvmTest

144 项用例(上游原测试文件,零改动):

测试类用例数结果钉住什么
ktoon-core(ToonTest / Lexer / Encoder / Annotation / Polymorphic / CustomSerializer / ErrorHandling)8787 全绿引擎状态机:编解码往返、注解、多态、自定义 serializer、错误行号定位
ktoon-ktor(转换器 / 注解 / 空值 / 错误处理 / Mock 引擎管线)3937 绿 2 红client 转换器与 ContentNegotiation 管线
ktoon-ktor-server(管线集成 / 双向转换)1817 绿 1 红server 转换器与 StatusPages 协作

3 个红测试的定性(关键:均与 ohos 无关):

  1. 2 个 charset 测试(上游既有):ToonContentConverter.serialize 上游源码写死 encodeToByteArray()(恒 UTF-8、忽略 charset 参数,本 fork 未改该文件),而测试期望 UTF-16 语义——任何 Ktor 版本下都不可能通过,属上游源码与测试的既有分歧(TOON 线格式事实上 UTF-8 only);
  2. 1 个错误上下文测试(换线行为差异):Ktor 3.5 中 receive 失败改抛 CannotTransformContentToTypeException,不再命中 StatusPages 的 exception<SerializationException> 分支——状态码断言仍过,消息前缀断言失败。

ktoon-core 87 项全绿是核心证明:编解码逻辑与上游逐字节同源。

6.3 HAP 验证

:ktoon-core / ktoon-ktor / ktoon-ktor-server:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL(三件套自动拷入 entry/libs)
llvm-nm -D: KtoonDispatch / KtoonVersion / libohosktoon_symbols 均为全局 T 符号
CompileArkTS: finished(修正 CopyOptions 枚举后)
PackageHap: finished、签名、install bundle successfully、start ability successfully

双 ABI libohosktoon.so:arm64 17,743,008 B、x86_64 16,782,552 B(debugShared 未 strip,含 KN 运行时 + 完整 Ktor client/server/双 CIO + coroutines + serialization;HAP 打包时 strip 后显著缩小)。比 qrose 的 so(约 4.7 MB)大是刻意为之——完整 Ktor 链进 so 正是"Ktor 就绪"的物理证明。

6.4 功能用例

  1. 启动后日志出现 ktoonVersion() → 1.0.0-ohos.1(来自真实 Native 调用,非静态文字)——真机已验证(见 7.1 截图);
  2. 编码 Tab 默认 8 行样例点「编码为 TOON」,返回 ok:true,出现 items[8]{id,name,email,is_active,role,score}: 表格文本与对比行(引擎侧同载荷参考值:JSON 755 B → TOON 478 B · 省 36%)、roundTrip ✓;
  3. 篡改 JSON 文本(非法 JSON / 多余字段 / 改列名)再编码,被 ok:false 错误通道拦截并原文呈现,不崩溃;
  4. 解码 Tab 点「用样例生成」再「解码为 JSON」,pretty JSON 还原 8 行、roundTrip ✓;
  5. 点「本机 HTTP 实测」,出现回环行 Ktor CIO 回环 · 8 行 · JSON x B / TOON y B · 省 p% · z ms 与 roundTrip ✓;
  6. 每步操作日志按时间戳滚动留痕(→ 请求 / ← 响应);
  7. hdc shell hilog | grep -i ktoon 可见 NAPI 层请求字节数日志。

6.5 验证结论

序列化引擎、Ktor 双向转换器、coroutines 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面完成版本握手(见下节效果图);编码 / 解码 / 回环三个入口的验收口径与参考值如上,供后续点验留档。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/ktoon.git
cd ktoon
$env:JAVA_HOME = "F:\cmp\.tools\jdk-21.0.12.1+1"
.\gradlew.bat :ktoon-core:compileKotlinOhosArm64 :ktoon-ktor:compileKotlinOhosArm64 :ktoon-ktor-server:compileKotlinOhosArm64   # klib
.\gradlew.bat :ktoon-core:jvmTest :ktoon-ktor:jvmTest :ktoon-ktor-server:jvmTest                                                # 144 项交叉验证
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64                            # 双 ABI so
# DevEco 打开 example\harmonyApp → 自动签名 → Build Hap(s)
hdc install -r entry\build\default\outputs\default\entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.ktoon -m entry
hdc shell hilog | grep -i ktoon

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让三个库模块真正编译成 ohosArm64 / ohosX64 产物并链接进 so;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. org.gradle.java.home 只管 daemon,gradlew 启动器仍需 JAVA_HOME;上游 wrapper 9.4.1 拉取超时可对齐到本机已缓存的 9.6.1(2.4.20 线支持 Gradle 9.x,KStore 已验证);
  4. 上游 serialization 插件经约定插件隐式 apply,根构建替换时这行最容易丢:丢了编译期不报错,运行期才炸 Serializer for class 'X' is not found——本仓改为模块级显式声明;
  5. kotlin.text.Charsets 在 ohos stdlib 不存在,跨平台代码请用 ktor 的 io.ktor.utils.io.charsets.Charsets 或 encodeToByteArray();
  6. Ktor 3.5 把 server ContentNegotiation 挪到 io.ktor.server.plugins.contentnegotiation,上游 jvmMain demo 已用新包;直接抄旧包名会 Unresolved reference,且报错行号只指向 import 不指向根因;
  7. StringFormat.encodeToString(value) / decodeFromString<T>() 是 reified 扩展函数,必须显式 import(kotlinx.serialization.encodeToString),否则静默回落到成员函数并报一串不知所云的参数不匹配;
  8. jvmMain demo 依赖的 ktor-server-compression 上游由约定插件注入,模块级重写后要显式补回;
  9. 演示 schema 的嵌套集合陷阱:第一版 User 带 tags: List<String>,JVM 探针发现根为单对象时编码抛异常、表格形态静默错位(往返 equal=false / 解码 IndexOutOfBoundsException)——上游引擎不支持嵌套集合,载荷 schema 必须纯原语列(tags → role);
  10. ArkTS 严格编译:CopyOptions 枚举没有 InLocal 成员,本地可复制是 CopyOptions.LocalDevice(合法值 None / InApp / LocalDevice / CrossDevice),HAP 的 CompileArkTS 会直接拦下;
  11. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  12. 签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持 signingConfigs 空数组,提交前用 git update-index --skip-worktree 保护,证书绝不进入 AtomGit。

8.2 已知问题

  • 上游引擎不支持嵌套集合字段(单对象形态抛异常、表格形态静默错位);根为单对象(非数组)时往返亦有字段错位——对象数组 + 原语列是当前权威形态,已在上游仓库文档化,待上游修复;
  • Ktor 3.4.1 → 3.5.1-0.1.0-01 换线带来一处行为差异:receive 失败异常类型变化导致 1 个 server 测试红(消息前缀断言),对本库 API 无影响;
  • 2 个 client charset 测试为上游既有红(转换器忽略 charset 参数、TOON 线格式 UTF-8 only),任何版本都不过;
  • op=http 在部分机型的 FFRT 调度下存在不确定性,失败时错误原文进状态行与日志,不影响 encode / decode 验收面;
  • android / js / wasmJs / iOS target 与发布链路剥离(源文件留盘),本 fork 不发 Maven Central;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.ktoon 匹配的签名。

8.3 未来优化方向

  • 跟进上游修复嵌套集合编码,修复后桥可放开任意 schema;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 提供不含 server / CIO 的 encode / decode 精简 so 变体(预期体积从约 17 MB 降到 qrose 量级);
  • 在 CI 中加入 ohos 编译、llvm-nm 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 ktoon-core / ktoon-ktor / ktoon-ktor-server(全部 commonMain)
    → 验证 ohos 制品线(serialization / coroutines + Ktor 全家族)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-08(Gradle 9.6.1 沿用)
    → 根构建替换(cmp_qrose 模式,serialization 插件模块级补回)
    → ohosArm64 / ohosX64 KLIB + 全仓仅 2 行源码修正
    → C ABI(@CName × 2)+ 纯原语列 List<User> 载荷
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析
    → ArkUI 真机三 Tab 验收(编码 / 解码 / 本机回环 HTTP 实测)

9.2 封装层次

  • 引擎层:上游原版 Toon / ToonEncoder / ToonDecoder / ToonLexer;
  • 集成层:上游原版 ContentNegotiationConfig.toon() 与双向 ToonContentConverter;
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、JSON 解析、操作分发、日志与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 制品线决定路线:Ktor ohos 分叉线的出现,让"Ktor 集成库"从只能编译核心变成了能做本机回环实测——选型必须基于当天的制品可达性逐个查 .module 元数据,而不是套用历史结论;
  2. 载荷 schema 要被引擎能力筛过:JsonElement 绑定 Json 格式、嵌套集合是上游盲区——这两个事实不查清楚,桥的 Demo 上真机就是一枚定时炸弹;JVM 探针(同引擎同数据往返断言)是上真机前的最后一道闸;
  3. 交叉验证的诚实性比绿灯数量重要:144 项里 3 个红测试逐一归因(上游既有 ×2、换线行为 ×1),"哪个红是我造成的、哪个红本来就在"说清楚,比强行凑全绿更有说服力。

9.4 适配成果

当前适配已完成:

  • ktoon 1.0.0 三个库模块的双 ABI Kotlin/Native 编译(上游 commonMain 仅 2 行 charset 修正,ohos 侧零新增源码);
  • HarmonyOS kotlinx + Ktor 制品线在序列化库上的完整落地(serialization / coroutines 1.11.0-0.1.0-07 + Ktor 3.5.1-0.1.0-01);
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号);
  • DevEco Stage ArkUI 三 Tab 验收页面(编码、解码、本机回环 HTTP 实测、版本握手、操作日志);
  • 144 项 JVM 交叉验证(141 绿,3 红逐一归因)+ HAP 构建、签名、设备安装和真机验证;
  • AtomGit 项目文档、OpenHarmony 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐