本文记录 simple-robot(simbot)接入 OpenHarmony 的完整过程,覆盖事件调度框架的模块盘点、鸿蒙 kotlinx 制品线选型、真实引擎编译、ohosArm64 / ohosX64 目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。

simple-robot 是基于 kotlinx.coroutines + kotlinx-serialization + kotlinx-io 的 Kotlin Multiplatform Bot 风格高性能异步事件调度框架:Application 提供应用与生命周期,EventDispatcher 提供事件调度,EventListener 以优先级、过滤与拦截器体系组织业务逻辑,EventResult 以 simple / empty / invalid / error 四种结果描述每次处理。平台能力(QQ、OneBot、KOOK)由基于 Ktor 的组件库提供。本次适配与 KStore 那批"零依赖内核提取"路线有本质区别:社区 Maven 上 kotlinx 制品线(coroutines / serialization 1.11.0-0.1.0-07、kotlinx-io-core 0.9.1-0.1.0-07,全部带 ohosArm64 / ohosX64 Gradle 变体)与 KStore 验证过的是同一条,因此框架核心 14 个纯 Kotlin 模块的 commonMain 源码一行未动、连 actual 都一个没补,真实的引擎(launchSimpleApplication、listeners DSL、SimpleEventDispatcher)被原样编译成 OpenHarmony KLIB 与动态库,ArkUI 页面作为验收宿主通过 JSON 协议驱动它。

相比 KStore,simbot 适配多出的真正难点不在依赖线,而在构建期的代码生成与 JPMS:simbot-api 的 Messages.standardSerializersModule 硬引用 KSP 生成的 internal 扩展函数、love.forte.simbot 包只有 buildconfig 生成的 SimbotBuiltin 一个类却被 module-info.java 导出、14 个模块的 JPMS 描述符需要 --patch-module 才能编译——这三样在鸿蒙工具链下全部保活,是本文第 2.5 节与踩坑复盘的重点。

项目地址: AtomGit/oh-tpc/simpler-robot

上游仓库: simple-robot/simpler-robot

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台 KMP 适配

simbot 为 Kotlin Multiplatform 应用提供"事件进来 → 引擎调度 → 监听器处理 → 结果收集"这一件事的完整答案:launchSimpleApplication { } 一行起应用,listeners { listen<T> { } } 注册带优先级的监听器,dispatcher.push(event) 返回 Flow<EventResult> 按优先级顺序收集每个监听器的处理结果,拦截器与过滤器体系挂在调度路径上,Application 作为协程作用域管理生命周期。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面共享。OpenHarmony 应用同样需要这套事件调度能力,但上游构建(Kotlin 2.4.21 / Gradle 9.5.0 / build-logic 全量约定插件)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明事件引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证引擎本体、协程与序列化运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.4.21 / Gradle 9.5.0 / build-logic 约定插件矩阵,与鸿蒙工具链无法原样共存。
依赖矩阵引擎依赖 kotlinx-coroutines、kotlinx-serialization(-core/-json)、kotlinx-io-core,每个都要有 ohos 变体才能解析;测试依赖 mockk / lincheck / ktor 没有 ohos 制品。
构建期代码生成simbot-api 依赖 KSP 生成 includeMessageElementPolymorphic() 与 buildconfig 生成的 SimbotBuiltin,鸿蒙构建里少一样都编不过。
语言边界不同ArkTS 不能直接持有 Kotlin 对象、协程与 Flow 收集过不了 C 签名,需要 C ABI、C++ N-API 和 JSON。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:框架核心 14 个纯 Kotlin 模块(8 个 commons + logger + api + core + test + quantcat-common + continuous-session)与 1 个 KSP 处理器模块的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。调度语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。依赖 Ktor 的组件模块(QQ / OneBot / KOOK、apidefinition、ktor-inputfile)与纯 JVM 模块(Spring Boot starters、Gradle 插件、内部处理器)留盘不进构建。

1.2 库提供的能力

Demo 提供一个真实的事件调度会话,对应桥接的全部入口:

  • 配置(configure):launchSimpleApplication 启动真实 Application,listeners { listen { } } DSL 按 JSON 声明逐个注册监听器(名称、优先级、匹配规则、行为);重复 configure 会 cancel 旧应用再启动新的;
  • 推送(push):EventDispatcher.push(自定义 Event) 推入真实调度器,Flow<EventResult> 全量收集,逐位配对监听器后返回优先级顺序的处理轨迹;
  • 状态(status):回显引擎内当前已注册的监听器;
  • 版本握手(version):simbotVersion() 返回 fork 版本号,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
op / type / text回显本次操作与事件载荷。
count / elapsedMs结果数量与引擎耗时(TimeSource.Monotonic 实测)。
results[]每个监听器的 {listener, priority, kind, content};kind 是四种真实 EventResult 的直译:simple(有返回值)、empty(空结果)、invalid(匹配未命中的跳过语义)、error(监听器抛异常,HAP 不崩)。

引擎侧完整能力(优先级排序、拦截器、过滤器、continuous-session 会话、quantcat 过滤注解)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留上游测试源码零改动,用于交叉验证 ohos .so 里执行的调度逻辑没有漂移。

1.3 实现适配

维度要求
代码复用14 个模块的全部 commonMain 文件原样编译,不提取、不重写、零新增库源码——连 KStore 那 8 行 dispatcher actual 都不需要:simbot 的 native expect/actual 全部齐备,ohosMain 经默认层级模板直接继承 nativeMain。
平台目标根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);build-logic 约定插件、dokka、detekt、发布插件、js/wasm 与全量 native tier 矩阵剥离;KSP、buildconfig、JPMS patch-module 三样构建期生成物保活。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律版本主体与上游逐字相同(coroutines / serialization 1.11.0),只把仓库换到 ohos 制品线;kotlinx-io 从上游 0.9.0 进位到线上的 0.9.1(唯一的版本进位,io core 0.9.x 内 API 兼容)。
UI 完整单页覆盖全部桥接入口:监听器增删改、事件编辑、配置、推送、处理轨迹、操作日志和版本握手。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。simbot 本身不碰 UI,适配面天然最小;ArkTS Demo 只是把桥暴露的调度闭环画出来。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点 40+ 模块的依赖边界、确认 kotlinx ohos 制品线、确定 14 模块交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、KSP/buildconfig/JPMS 保活
第 3 阶段:桥协议建模     ── configure/push/status JSON 契约、EventResult 四态直译、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 监听器编辑、事件推送、处理轨迹、操作日志和版本握手
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
simbot-commons/simbot-common-{annotations,atomic,collection,streamable,   进 ohos 构建(纯 Kotlin)
                              time,stage-loop,suspend-runner,core}
simbot-logger/ simbot-api/ simbot-cores/simbot-core/                     进 ohos 构建(框架核心)
simbot-test/ simbot-quantcat/simbot-quantcat-common/
simbot-extensions/simbot-extension-continuous-session/
simbot-processors/simbot-processor-message-element-polymorphic-include/  KSP 处理器(JVM,构建期依赖)
example/nativeApp/                                                        Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/                                                       DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md                                            鸿蒙适配说明(中英双份)
README.upstream.md                                                        上游原文保存
simbot-common-apidefinition / ktor-inputfile                              Ktor 依赖,留盘不进 settings
simbot-component-{qq,onebot,kook}/**                                      Ktor 依赖,留盘不进 settings
simbot-cores/simbot-core-spring-boot-starter*/  simbot-logger-slf4j2-impl JVM 专用,留盘
build-logic/ internal-processors/ samples/ benchmarks/ website/ docs/     上游工程设施,留盘不进构建

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include 上述 14 + 1 + 1 个模块,rootProject.name = "ohos_simbot"。上游的 build-logic 约定插件、dokka / detekt / kover / 发布插件、foojay 解析器、kotlinWrappers 版本目录全部随根构建替换剥离——本 fork 不发 Maven Central。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
Gradle9.5.0(沿用上游 wrapper;2.4.20 线支持 Gradle 9.x)
JDK21(org.gradle.java.home 指向 Eclipse Temurin jdk-21.0.12.101-hotspot)
kotlinxcoroutines / coroutines-test / serialization-core / serialization-json 1.11.0-0.1.0-07、kotlinx-io-core 0.9.1-0.1.0-07(ohos 制品线;atomicfu 0.33.0-0.1.0-07 随 coroutines 传递解析)
KSP2.3.9(kspCommonMainMetadata + message-element 处理器,实测兼容鸿蒙 Kotlin)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.simbot
$env:JAVA_HOME = "C:\Program Files\Eclipse Adoptium\jdk-21.0.12.101-hotspot"
java -version
1.3 源码依赖度盘点
模块依赖形态处置
simbot-common-annotations / atomic / streamable / time / stage-loop纯 Kotlin,stage-loop 的 slf4j 仅 jvmMain整体编译
simbot-common-collection / core / suspend-runnercoroutines(+core 再加 serialization);reactive / reactor / rx 仅 jvmMain compileOnly整体编译
simbot-loggerslf4j 仅 jvmMain;webMain(kotlin-wrappers)仅 js/wasm整体编译,webMain 源码集留盘
simbot-apicommonMain:coroutines + serialization-core + io-core;KSP 生成物硬引用;ktor 仅测试整体编译(14 模块里唯一带构建期生成)
simbot-core / simbot-test / quantcat-common / continuous-session纯 Kotlin(reactive 家族仅 jvmMain)整体编译
simbot-common-apidefinition / ktor-inputfilecommonMain 依赖 ktor-http / ktor-client-core留盘不进构建
simbot-component-qq / onebot / kookapi 层即依赖 ktor-client留盘不进构建

expect/actual 盘点结论是本次适配最舒服的一处:上述 14 个模块的 nativeMain 零 cinterop、零 platform.*,全部站在 stdlib + coroutines + serialization 上(个别模块用 kotlin.native.ref.WeakReference 与 kotlin.concurrent.atomics,均为 Kotlin/Native 标准 API)。applyDefaultHierarchyTemplate() 让 ohosMain 免费继承 nativeMain——所以 ohos 侧一行 actual 都不用写,上游给 linux/mingw/apple 准备的 actual 原样生效。

1.4 页面边界
标题区          SIMBOT · OPENHARMONY / 版本握手行(事件调度框架 · Kotlin/Native ohosArm64/ohosX64)
Tab 切换        事件管道 / 关于
监听器区        行编辑:name / priority / match / action,增删行,默认 A(p0,echo) B(p2,ping,constant:OK) C(p4,error)
事件区          type 输入框(默认 ping) + text 输入框(默认 hello simbot)
操作区          应用配置(configure,深色) / 推送事件(push,深绿)
状态行          最近一次操作结果(如"配置成功:3 个监听器已注册到真实 EventDispatcher")
轨迹区          处理结果按 priority 顺序逐行渲染,kind 着色(simple 深绿 / invalid 琥珀 / error 酒红 / empty 灰)
日志区          [HH:mm:ss] 时间戳操作日志(最新在前,上限 100 条)
关于区          架构分层、参与编译模块清单、action 语义、AtomGit 仓库与社区链接

页面启动时先调 simbotVersion() 做握手,版本行渲染进标题下方。

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

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

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

org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0-0.1.0-07
  variants: ohosArm64ApiElements-published / ohosX64ApiElements-published / jvmApiElements-published
  dependencies: kotlin-stdlib 2.4.20-0.1.0-07、atomicfu 0.33.0-0.1.0-07
org.jetbrains.kotlinx:kotlinx-serialization-core:1.11.0-0.1.0-07   ohos 双变体 ✓
org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0-0.1.0-07    ohos 双变体 ✓
org.jetbrains.kotlinx:kotlinx-io-core:0.9.1-0.1.0-07                ohos 双变体 ✓
org.jetbrains.kotlin:kotlin-gradle-plugin / 插件 marker 2.4.20-0.1.0-07  ✓

三个关键结论:其一,coroutines / serialization 的版本主体与上游声明逐字相同(1.11.0),只是仓库换线;其二,ohos 制品同时发布 jvm 变体,jvm() 交叉验证用同一个版本号,不需要双线隔离;其三,kotlinx-io 上游钉的是 0.9.0,ohos 线只有 0.9.1-0.1.0-07——版本主体进一位是本 fork 版本目录里唯一的一处进位,io core 在 0.9.x 内 API 兼容,Resource / SourceResource 用法不变。

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

社区此前批准的钉法是 HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 用的)。但 kotlinx ohos 线依赖 kotlin-stdlib 2.4.20-0.1.0-07(元数据 2.4.0),Kotlin 2.2.21 只能读到 2.3.0,编译期直接报:

Class 'kotlin.Unit' was compiled with an incompatible version of Kotlin.
The binary version of its metadata is 2.4.0, expected version is 2.3.0 or less.

工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07。额外好处:上游源码本来就是 Kotlin 2.4.21 编的,2.4.20 与之同代,源码级兼容成本为零;Gradle 9.5.0 上游 wrapper 原样沿用(2.4.20 线支持 Gradle 9.x,KStore 先例 9.6.1),不像 2.2.21 旧线必须降 8.14.1。

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 同序;rootProject.name = "ohos_simbot"
2.3 编译真实引擎

每个库模块收敛为纯 KMP 模块(以 simbot-core 为例):

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.kotlin.serialization)   // 上游在 build-logic 里统一 apply,重写后必须模块级补回
}

kotlin {
    applyDefaultHierarchyTemplate()
    jvmToolchain(21)

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

    compilerOptions {
        freeCompilerArgs.addAll("-Xexpect-actual-classes")   // 上游同款
    }

    sourceSets {
        commonMain.dependencies {
            api(project(":simbot-commons:simbot-common-annotations"))
            implementation(project(":simbot-commons:simbot-common-collection"))
            api(project(":simbot-api"))
            api(libs.kotlinx.coroutines.core)               // 1.11.0-0.1.0-07
        }
        // commonTest / jvmMain / jvmTest 依赖照抄上游;js/wasm 相关行删除
    }
}

依赖换线后版本目录(gradle/libs.versions.toml)里 Kotlin 与 kotlinx 四件套全部换成 -0.1.0-07 线,mockk / lincheck / ktor / reactor 等 JVM 测试依赖留在 Maven Central。实测:

.\gradlew.bat compileKotlinJvm          # 14 个库模块 + KSP 处理器全部 BUILD SUCCESSFUL

引擎的全部 commonMain 文件一个没动,ohos 侧零新增源码——这是"ohosMain 继承 nativeMain"分层带来的最大红利。

2.4 测试依赖的处置:mockk / lincheck / ktor 没有 ohos 制品

commonTest / jvmTest 的依赖表对 ohos 测试编译可见,mockk 等解析不到 ohos 变体就会卡死 compileTestKotlinOhosArm64。处置是两条纪律的组合:测试统一在 JVM 跑,根构建全局禁用 ohos 测试任务:

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

这样上游测试文件零改动保留,ohos 侧只承担编译与链接;jvmTest 可随时在 JVM 侧复核(上游 ApplicationEventDispatcherTests 等测试源码未动一行)。

2.5 构建里不能丢的三样东西(本次适配的真正难点)

KStore 只需要补一个 dispatcher actual;simbot 的难点全在构建期生成物——少了任何一样,compileKotlinJvm 或 JPMS 编译当场失败:

其一,KSP。 simbot-api 的 Messages.standardSerializersModule 调用 KSP 生成的 internal 扩展函数 includeMessageElementPolymorphic()(包 love.forte.simbot.message)。fork 构建保留 kspCommonMainMetadata 与处理器模块(:simbot-processors:simbot-processor-message-element-polymorphic-include,JVM 模块),沿用上游的 KspAATask 排序补丁、commonMain.kotlin.srcDir(generated/ksp/metadata/commonMain/kotlin) 接线和三 个 KSP 参数(localOnly=true、outputPackage、visibility=internal);文档用的 interface-uml-processor 丢弃。实测 KSP 2.3.9 在 HarmonyOS Kotlin 2.4.20-0.1.0-07 下正常生成。处理器模块本身用了 serialization 插件的生成物(serializer()),插件别忘补回,否则 Unresolved reference 'serializer'。

其二,buildconfig。 simbot-api 的 love.forte.simbot 包里只有 buildconfig 生成的 SimbotBuiltin 一个类,而 jvmMain/module-info.java 的 exports love.forte.simbot; 导出这个包——不生成它,javac 直接报"程序包为空或不存在"。fork 保留 buildconfig 插件(VERSION / BUILD_KOTLIN_VERSION / IS_SNAPSHOT)。

其三,JPMS patch-module。 14 个模块的 jvmMain/java 都有 module-info.java,导出的包内容来自 Kotlin 编译输出。上游 build-logic 用 configJavaCompileWithModule(--patch-module + Kotlin 输出)解决;fork 把等价逻辑放进根构建,按项目路径映射模块名注入 JavaCompile 的 compilerArgumentProviders。这里有一个平台差异值得单独记录:HarmonyOS KGP 自己注册 compileJvmMainJava 任务,不应用 Gradle Java 插件——钩 plugins.withId("java") 永远不触发,sourceSets 扩展也不存在,必须直接钩任务类型,且 Kotlin 输出用 KGP 2.x 标准布局 build/classes/kotlin/jvm/main 惰性解析。

另外两处对上游构建的"减法"都有明确依据:org.jetbrains:annotations 是纯 JVM 库且 commonMain 源码零引用,从 commonMain 下沉到 jvmMain(避免 ohos 目标解析纯 Java jar);suspend-transform 编译器插件只生成 JVM 桥接方法(@ST / @STP 是仓库自定义普通注解),ohos 编译不需要,fork 未应用。

第 3 阶段:桥协议建模

3.1 JSON 契约
love.forte.simbot.**(上游引擎原样编译,14 模块)
        ↓ launchSimpleApplication / listeners DSL / EventDispatcher.push
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API simbotDispatch() / simbotVersion()
        ↓
ArkUI Index.ets(单页滚动)

请求与响应:

配置 : {"op":"configure","listeners":[{"name":"A","priority":0,"match":"","action":"echo"},
                                      {"name":"B","priority":2,"match":"ping","action":"constant:OK"},
                                      {"name":"C","priority":4,"match":"","action":"error"}]}
       → {"ok":true,"op":"configure","listeners":3}
推送 : {"op":"push","type":"ping","text":"hello simbot"}
       → {"ok":true,"op":"push","type":"ping","text":"hello simbot","count":3,"elapsedMs":12,
          "results":[{"listener":"A","priority":0,"kind":"simple","content":"HELLO SIMBOT"},
                     {"listener":"B","priority":2,"kind":"simple","content":"OK"},
                     {"listener":"C","priority":4,"kind":"error","content":"demo error from listener 'C'"}]}
状态 : {"op":"status"} → {"ok":true,"op":"status","configured":true,"listeners":[…]}

两个不对称设计:监听器列表走 ArkTS JSON.stringify 生成的嵌套数组(桥内解析器必须支持对象/数组/转义字符串的完整递归下降,不能像 qrose 那样只切顶层字段);results 的 kind 是引擎 StandardEventResult 的直译,配对关系依赖引擎顺序语义(优先级升序、同优先级按注册顺序)——这正是上游 ApplicationEventDispatcherTests.launchTest 断言过的顺序。

3.2 监听器语义为什么落在引擎里

桥内应用这样起:

currentApp?.cancel()
val app = runBlocking { launchSimpleApplication { } }

app.listeners {
    for (spec in specs) {
        listen<DemoEvent>({
            priority = spec.priority
        }) { e ->
            if (spec.match.isNotEmpty() && !e.text.contains(spec.match, ignoreCase = true)) {
                EventResult.invalid()               // 引擎的"跳过"结果,不是桥自己发明的状态
            } else {
                when {
                    spec.action == "echo" -> EventResult.of(e.text.uppercase())
                    spec.action == "error" -> throw RuntimeException("demo error from listener '${spec.name}'")
                    spec.action.startsWith("constant:") -> EventResult.of(spec.action.removePrefix("constant:"))
                    else -> EventResult.empty()
                }
            }
        }
    }
}

DemoEvent 是实现了 Event 接口(id: ID = UUID.random()、time: Timestamp = Timestamp.now(),@OptIn(FuzzyEventTypeImplementation))的自定义事件。注册、优先级排序、调度、异常收集、结果顺序全部是上游代码——桥只负责把 JSON 翻译成 DSL 调用,再把 Flow<EventResult>.toList() 的结果翻译回 JSON。match 未命中返回 EventResult.invalid() 是刻意选择:让 Demo 能展示引擎一等公民的"跳过语义",而不是桥层自造一个 filtered 状态。

suspend 调用统一用 runBlocking 收进同步 C ABI:鸿蒙线 Dispatchers.Main 的跨线程派发有已知问题(resaca 仓适配时记录过),桥全程不碰 Main,launchSimpleApplication 内部调度走 Default,天然绕开。

3.3 错误通道

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

@CName("SimbotDispatch")
fun simbotDispatch(request: String): String = try {
    handle(MiniJson.parseObject(request))
} catch (t: Throwable) {
    """{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}

非法 JSON、空 listeners 数组、不支持的 op、未 configure 就 push,全部走同一个 ok:false 通道;监听器内部抛异常则被引擎收进 EventResult.Error,在 results[].kind="error" 里回显——监听器抛错是演示特性,不是事故,HAP 不会崩。

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

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libsimbot_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohossimbot.so)
  │ SimbotDispatch / SimbotVersion
  ▼
UTF-8 JSON + DisposeString

直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制调度语义,所以选择 C ABI + JSON。

4.2 Kotlin/Native 导出函数
@CName("SimbotDispatch")
fun simbotDispatch(request: String): String   // configure / push / status 一口进

@CName("SimbotVersion")
fun simbotVersion(): String =
    """{"ok":true,"name":"simple-robot","fork":"5.0.0-Alpha1-ohos.1",
        "upstream":"5.0.0-Alpha1","kotlin":"${KotlinVersion.CURRENT}"}"""
C ABI 符号ArkTS 方法覆盖内核
SimbotDispatchsimbotDispatch(req)launchSimpleApplication 构造 + listeners 注册 + push 收集
SimbotVersionsimbotVersion()fork 版本握手(真机版本行的数据来源)

桥内的 MiniJson 是手写的完整递归下降解析器(对象/数组/字符串转义/数字/布尔/null,数字保留原始字面量让整数往返不走浮点)。协议变大时只换解析器,C 签名永远 const char* -> const char*。

4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"simbotDispatch", nullptr, NapiSimbotDispatch, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"simbotVersion", nullptr, NapiSimbotVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

每个回调统一三步:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohossimbot_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohossimbot_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与两个业务符号),不拖整份生成头。

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

add_library(simbot_napi SHARED napi_init.cpp)
target_link_libraries(simbot_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohossimbot)

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

export const simbotDispatch: (request: string) => string;
export const simbotVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 版本握手和触发操作
aboutToAppear(): void {
  this.handshake();                       // simbotVersion() → 版本行 + 日志
}

configure(): void {
  const listeners = this.rows.map((r: ListenerRow): ListenerJson =>
    ({ name: r.name, priority: r.priority, match: r.match, action: r.action }));
  const res = JSON.parse(simbotDispatch(JSON.stringify({ op: 'configure', listeners }))) as ConfigureOk;
  // ok:true → 状态行"配置成功:N 个监听器已注册到真实 EventDispatcher"并清空轨迹
}

push(): void {
  const res = JSON.parse(simbotDispatch(JSON.stringify({ op: 'push', type: this.eventType, text: this.eventText }))) as PushOk;
  // results[] 逐条渲染为 "P{priority} {listener} · {kind} · {content}",按 kind 着色
}

监听器行是 ListenerRow 类实例数组,TextInput.onChange 里整行替换后重新赋值 @State 数组触发渲染,ForEach 以 uid+内容做键;增删行、改优先级全部即时生效于下一次 configure。

5.2 页面交互

事件管道 Tab 自上而下:监听器行编辑区(name / priority / match / action 四列 + 删除,添加按钮深蓝)、事件区(type + text)、双色操作按钮(应用配置深黑 / 推送事件深绿)、状态行、处理轨迹区(kind 着色:simple 深绿、invalid 琥珀、error 酒红、empty 灰)、[HH:mm:ss] 时间戳日志(最新在前,上限 100 条)。关于 Tab 展示架构分层(ArkTS → NAPI → C ABI → Kotlin 引擎 → kotlinx)、参与编译模块清单、action 语义表与 AtomGit 仓库 / 社区链接。

5.3 对比度和错误边界

主操作深黑 / 深绿按钮白字,删除按钮酒红,语义与破坏性对齐;ok:false 时错误原文进状态行与日志(如删除全部监听器再配置会收到 listeners must be a non-empty array);Native 调用本身抛错(理论不该发生)同样被 try/catch 收进日志区。kind=error 的轨迹行用酒红显式标注——它证明监听器抛的异常被引擎收进 EventResult.Error 并安全跨过 FFI,不是页面失败。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohossimbot)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       ├── Bridge.kt                         @CName × 2 + runBlocking + 引擎 DSL 调用
│       └── MiniJson.kt                       完整递归下降 JSON 解析器(监听器嵌套数组)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.simbot、versionName 5.0.0-Alpha1-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/libohossimbot_api.h
        ├── src/main/cpp/types/libsimbot_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面
6.2 构建、签名和安装
.\gradlew.bat :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64

cd example\harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组)。

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.simbot -m entry

四、完整代码对照

4.1 整体架构

love.forte.simbot.**(上游引擎原样编译,14 模块 commonMain 零修改、零新增 actual)
    │ Application / EventDispatcher / EventListener / EventResult / 拦截器
simbot-quantcat-common / simbot-extension-continuous-session(随 api 链进入 .so)
Bridge.kt + MiniJson
    │ SimbotDispatch / SimbotVersion
libsimbot_napi.so
    │ simbotDispatch / simbotVersion
Index.ets
    │ JSON parse + @State 更新
    ▼
ArkUI 真机页面(单页滚动)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_simbot、14+1 模块 include
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-07 + ohos kotlinx 制品线钉版 + KSP/buildconfig 别名
build.gradle.kts(根)版本 5.0.0-Alpha1-ohos.1、全局禁用 Ohos*Test*、JPMS patch-module 集中配置
14 个模块 build.gradle.kts纯 KMP:jvm + ohosArm64 + ohosX64;serialization 模块级补回;optIn / -Xexpect-actual-classes / 依赖照抄上游
simbot-api/build.gradle.kts额外保留 KSP(kspCommonMainMetadata + 参数 + KspAATask 排序)与 buildconfig(SimbotBuiltin)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、runBlocking 包裹、引擎 DSL 调用、错误通道
nativeApp/.../MiniJson.kt递归下降 JSON 解析器(支持监听器嵌套数组)+ 转义器
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鸿蒙适配主文档(16 节)

4.3 关键 API 对照

层次API作用
引擎launchSimpleApplication { }启动真实 Application(suspend,桥内 runBlocking)
引擎Application.listeners { }DSL 注册监听器,桥的 op=configure 逐个展开
引擎listen<DemoEvent>({ priority = p }) { }优先级 + reified 事件类型监听
引擎EventDispatcher.push(event)返回 Flow<EventResult>,桥内 toList() 全量收集
引擎EventResult.of / empty / invalidsimple / empty / invalid 三种正常结果直译为 kind
引擎StandardEventResult.Error监听器抛异常的收口,直译为 kind=error
引擎Application.cancel()重新 configure 时回收旧应用协程作用域
NativeSimbotDispatch / SimbotVersionC ABI JSON 导出
N-APIsimbotDispatch(req) / simbotVersion()向 ArkTS 暴露能力
ArkTSconfigure() / push()触发操作并刷新 UI

4.4 ArkTS 与 Kotlin 的边界

const res = JSON.parse(simbotDispatch(JSON.stringify({
  op: 'configure',
  listeners: [
    { name: 'A', priority: 0, match: '', action: 'echo' },
    { name: 'B', priority: 2, match: 'ping', action: 'constant:OK' },
    { name: 'C', priority: 4, match: '', action: 'error' }
  ]
})));
// res.ok === true;随后 op:'push' 的 results 按 A→B→C 顺序返回三种 kind
app.listeners {
    listen<DemoEvent>({ priority = spec.priority }) { e ->
        if (spec.match.isNotEmpty() && !e.text.contains(spec.match, ignoreCase = true)) {
            EventResult.invalid()
        } else {
            EventResult.of(e.text.uppercase())   // 或抛异常 / 固定值 / empty
        }
    }
}
val results = runBlocking { app.eventDispatcher.push(DemoEvent(type, text)).toList() }

两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象、协程或 Flow。

五、关键决策说明

决策 1:真编译 14 个模块而不是提取零依赖内核

早期仓库抽内核,是因为 ohos 上没有它们依赖的 kotlinx 制品。本次先验证制品线(coroutines / serialization / kotlinx-io 全部存在 ohos 变体),障碍已消失——再抽内核等于把事件引擎手抄一遍,反而引入漂移风险。真编译让优先级排序、拦截器路径、异常收集全部是上游原件;而且 simbot 的 nativeMain expect/actual 全部齐备、零 cinterop,ohosMain 经默认层级模板直接继承,连 KStore 那 8 行 dispatcher actual 都省了——适配面落在构建系统而非源码。

决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 沿用 9.5.0

kotlinx ohos 线基于 stdlib 2.4.20 构建,旧钉法 2.2.21 读不了 2.4 元数据;上游本来就是 Kotlin 2.4.21,2.4.20 同代零降级。Gradle 方面 2.4.20 线支持 9.x(KStore 先例 9.6.1),上游 wrapper 9.5.0 原样保留——只有 2.2.21 旧线才必须降 8.14.1,两条工具链线的经验不能互相套用。

决策 3:根构建替换(cmp_qrose 模式),组件模块留盘

上游根构建(build-logic 约定插件 / detekt / dokka / kover / 发布插件)对鸿蒙没有保留价值,整个替换为极简 ohos 构建;中间层聚合脚本(simbot-commons 等目录的上游 build.gradle.kts 引用 build-logic 的 P 对象)同步简化为空聚合。依赖 Ktor 的组件模块与 apidefinition / ktor-inputfile 留盘不进 settings.gradle.kts——Ktor 没有 ohos 制品,强行移植等于先适配一遍 Ktor,超出本次范围;框架核心不依赖 Ktor,裁剪线画在依赖边界上而不是模块数量上。

决策 4:EventResult 四态直译 + runBlocking 同步桥

桥不自造状态机:kind 的 simple / empty / invalid / error 就是 StandardEventResult 的四个成员,invalid 专门用来演示引擎的跳过语义(match 未命中)。结果与监听器的配对依赖引擎顺序语义(优先级升序、同级按注册顺序),与上游测试的断言顺序一致。updates Flow 式的长驻订阅不跨 C ABI,桥是同步一次一调用,runBlocking 收 suspend、全程不碰 Dispatchers.Main(鸿蒙线已知问题)。

决策 5:JVM 交叉验证 + 测试零改动 + 构建期生成物保活

引擎逻辑在 commonMain,JVM 与 ohos 编译同一份代码;全部模块 compileKotlinJvm 通过、上游测试源码一行不动。mockk / lincheck / ktor 没有 ohos 制品,处置不是改测试,而是"测试统一 JVM + 根构建禁用 Ohos*Test*"。同时 KSP、buildconfig、JPMS patch-module 三样构建期生成物在 fork 里全部保活——这三样是"源码零修改"成立的前提,也是 simbot 这类带注解处理器的框架库与纯手写库适配难度的主要差异。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x86_64)、JDK 21(Eclipse Temurin)、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。Kotlin/Native 工具链 kotlin-native-prebuilt-windows-x86_64-2.4.20-0.1.0-07 经 kotlin.native.distribution.downloadFromMaven=true 从社区 Maven 拉取。

6.2 JVM 交叉验证

.\gradlew.bat compileKotlinJvm

14 个库模块 + KSP 处理器全部编译通过(含 compileJvmMainJava 的 JPMS 编译与 KSP 元数据处理);上游 commonTest / jvmTest 测试源码零改动保留,jvmTest 可在 JVM 侧随时复核(其中 ApplicationEventDispatcherTests.launchTest 断言的四监听器顺序与类型,正是桥 results[] 配对语义的参照)。依赖换线没有 API 漂移:coroutines 1.11.0、serialization 1.11.0 换成 -0.1.0-07 线后版本主体不变——"换线不换版"在框架类库上同样成立。

6.3 ohos 编译、链接与 HAP 验证

:simbot-commons:* / :simbot-api / :simbot-core / 其余 10 模块: compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:example:nativeApp:linkDebugSharedOhosArm64: BUILD SUCCESSFUL(libohossimbot.so ≈8.6MB + libc++_shared.so)
:example:nativeApp:linkDebugSharedOhosX64:   BUILD SUCCESSFUL(libohossimbot.so ≈8.1MB + libc++_shared.so)
llvm-nm -D: SimbotDispatch / SimbotVersion / libohossimbot_symbols 均为全局 T 符号
真机(arm64): install bundle successfully / start ability successfully

.so 体积比存储类库大一个数量级是正常的:Kotlin/Native 运行时 + coroutines + serialization + 14 个模块的事件引擎全量链接。三件套(libsimbot_napi.so 由 CMake 在 HAP 构建期编出、KN so 与 libc++ 由 copyKnSo* 回填)在每个 ABI 目录缺一不可。

6.4 功能用例(真机)

已在鸿蒙真机验证(见下节效果图):

  1. 启动后版本行出现 simple-robot 5.0.0-Alpha1-ohos.1 · 上游 5.0.0-Alpha1 · Kotlin 2.4.20(来自真实 Native 调用 simbotVersion(),非静态文字);
  2. 默认三监听器(A/p0/echo、B/p2/match:ping/constant:OK、C/p4/error)点「应用配置」,状态行出现 配置成功:3 个监听器已注册到真实 EventDispatcher;
  3. 日志区按时间戳滚动留痕([04:05:39] 版本握手成功、[04:05:48] 配置成功)。

验收清单(真机逐项执行):

  1. 推送 type=ping, text=hello simbot:轨迹为 P0 A · simple · HELLO SIMBOT → P2 B · simple · OK → P4 C · error · demo error…,顺序即优先级;
  2. 把 text 改为不含 ping 的内容再推送:B 变为 invalid(真实跳过语义),A、C 不受影响;
  3. 将 A 的优先级改为 10 再配置推送:轨迹顺序变为 B → C → A;
  4. 删除全部监听器再配置:返回 ok:false listeners must be a non-empty array;
  5. 未配置直接推送:返回 not configured yet: call op=configure first;
  6. hdc shell hilog | grep -i simbot 可见 NAPI 层 simbotDispatch request bytes=… 日志。

6.5 验证结论

事件引擎、协程与序列化运行时、KSP 构建期生成物、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的调度会话:版本握手、监听器注册与配置闭环(见下节效果图);推送/轨迹类用例按清单执行。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/simpler-robot.git
cd simpler-robot
export JAVA_HOME="/path/to/jdk-21"
./gradlew compileKotlinJvm                                                  # 14 模块 JVM 交叉验证
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 :example:nativeApp:linkDebugSharedOhosX64
cd example/harmonyApp
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b org.terminator.ohos.simbot -m entry
hdc shell hilog | grep -i simbot

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 14 个库模块真正编译成 ohosArm64 / ohosX64 目标并链接出动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. kotlinx-io-core 在 Maven Central 上的 0.9.1 没有 ohos 变体,必须钉 0.9.1-0.1.0-07;上游钉 0.9.0 时版本主体要进一位——版本主体写对、后缀写错,解析照样失败;
  4. HarmonyOS KGP 不应用 Gradle Java 插件:compileJvmMainJava 任务存在但 plugins.withId("java") 永远不触发、sourceSets 扩展不存在——JPMS patch-module 要直接钩 JavaCompile 任务类型,Kotlin 输出用标准布局 build/classes/kotlin/jvm/main 惰性解析,不能用上游 build-logic 的 SourceSetContainer 写法;
  5. module-info.java 导出"只有生成类"的包会炸:love.forte.simbot 包内只有 buildconfig 生成的 SimbotBuiltin,javac 报"程序包为空或不存在"——构建期生成的类也可能是 JPMS 导出的唯一内容,buildconfig 插件必须保留;
  6. KSP 处理器模块重写构建时丢了 serialization 插件,Unresolved reference 'serializer'——处理器源码本身用了插件生成物;KSP 参数(localOnly / outputPackage / visibility)与 commonMain.kotlin.srcDir(generated/ksp/metadata/commonMain/kotlin) 接线、KspAATask 排序补丁都要照搬,否则 commonMain 编译期就缺符号;
  7. 上游根构建在约定插件里统一 apply 的东西(serialization 插件、-Xexpect-actual-classes、optIn 列表、configJavaCompileWithModule),根构建替换后必须在模块级逐一补回——漏了的报错位置往往不在漏的那一行;
  8. clone 可能缺 gradle-wrapper.jar:上游 .gitignore 里 *.jar 写在 !gradle-wrapper.jar 之后(后匹配优先),取反被覆盖——在文件末尾再补一条 !gradle/wrapper/gradle-wrapper.jar 并把 jar 纳入版本控制,否则新 clone 直接 ClassNotFoundException: GradleWrapperMain;
  9. DevEco 打开工程后生成的 example/harmonyApp/.idea/.deveco(clangd 数据库约 14MB)与 .clangd / .clang-tidy 会混入提交,单独追加 gitignore;根目录 .idea/* 的根锚定规则盖不住嵌套目录;
  10. Kotlin/Native 侧桥代码不能用 JVM-only API:String.format / KotlinVersion.current / TimeMark.elapsed() 分别换成 toString(16).padStart(4,'0') / KotlinVersion.CURRENT / elapsedNow();
  11. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;
  12. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();
  13. 文档与提交里仓库链接统一 AtomGit,不留任何第三方镜像站地址。

8.2 已知问题

  • 依赖 Ktor 的组件模块(QQ / OneBot / KOOK)与 apidefinition / ktor-inputfile 未进 ohos 构建:Ktor 无 ohos 制品;"在鸿蒙上跑一个真的平台 bot"目前做不到,框架核心不受影响;
  • 测试仅在 JVM 运行(ohos 测试任务全局禁用),ohos 侧承担编译与链接验证;
  • 桥是同步一次一调用:长驻事件流(监听器向 ArkTS 侧推送)不跨 C ABI,updates Flow 式订阅留在 Kotlin 侧;
  • 结果与监听器的配对依赖引擎顺序语义(优先级升序、同级按注册顺序),上游若改变排序策略,桥的逐位配对需跟着调整;
  • suspend-transform 生成的 JVM 桥接方法不在本 fork 产物中(fork 不发布 Maven 制品,只影响 fork 自己 jvm() 产物的 API 面);
  • kotlinx-io 从上游 0.9.0 进位到 0.9.1-0.1.0-07(ohos 线只发 0.9.1);
  • 上游 README_en.md、docs/、website/ 留盘未动,不参与 ohos 构建;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.simbot 匹配的签名。

8.3 未来优化方向

  • 增加事件推送回表(N-API 线程安全回调),让 ArkTS 侧订阅长驻事件流,替代"操作后读回";
  • 跟踪社区 Maven 的 Ktor ohos 制品,出现后按同样三步接入组件模块(加目标、进 settings、补桥接字段);
  • 把拦截器(EventInterceptor / EventDispatchInterceptor)与 continuous-session 会话补上桥接入口;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本;评估 .har 形态发布。

九、总结

9.1 核心难点回顾

上游 14 个纯 Kotlin 模块(全部 commonMain)
    → 验证 ohos kotlinx 制品线(coroutines / serialization / kotlinx-io)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.5.0 沿用)
    → 根构建替换(cmp_qrose 模式)+ KSP / buildconfig / JPMS 三样构建期生成物保活
    → ohosArm64 / ohosX64 KLIB(零新增 actual,ohosMain 继承 nativeMain)
    → C ABI(@CName × 2)+ EventResult 四态直译
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析
    → ArkUI 真机单页验收(握手 / 配置 / 推送 / 轨迹)

9.2 封装层次

  • 引擎层:上游原版 Application、EventDispatcher、优先级监听器、拦截器与 EventResult 四态;
  • 通用层:上游原版 quantcat 过滤注解与 continuous-session 会话扩展(随 api 链进入 .so);
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、监听器行编辑、操作分发、轨迹渲染与日志;
  • DevEco 层:完成 CMake、HAP、签名、安装和真机运行。

9.3 三条经验

  1. 框架类库的适配面是"引擎语义"而非渲染面:simbot 没有一行 UI,验收重点天然落在"注册的监听器真的按优先级被真实调度器调起"上——所以桥必须直译 EventResult 四态而不是自造状态码,Demo 必须能看到 invalid(跳过)与 error(异常收口)这两种容易被手写总线忽略的语义;
  2. 带注解处理器的框架,难点在构建期而不是源码期:KSP 生成函数被 commonMain 硬引用、buildconfig 生成类是 JPMS 导出的唯一内容、--patch-module 要适配鸿蒙 KGP "不应用 Java 插件"的差异——这三样保活了,"源码零修改"才成立;换到别的带处理器框架(Room 类、Moship 类)同样适用;
  3. 制品线可达性决定裁剪线:kotlinx ohos 制品补齐让 14 个核心模块"真编译整库、零 actual";Ktor 制品缺失让组件模块整体留盘——两条线都是当天事实,路线选择必须基于当天的制品可达性,而不是"多编一个是一个"的主观意愿。

9.4 适配成果

当前适配已完成:

  • simbot 5.0.0-Alpha1 框架核心 14 个模块的双 ABI Kotlin/Native 编译(上游 commonMain 文件零修改、零新增 actual;KSP / buildconfig / JPMS 构建期生成物在鸿蒙工具链下全部保活);
  • HarmonyOS kotlinx 制品线在事件调度框架上的完整落地(coroutines / serialization 1.11.0-0.1.0-07 + kotlinx-io-core 0.9.1-0.1.0-07);
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号;arm64 ≈8.6MB / x86_64 ≈8.1MB 双 ABI so);
  • DevEco Stage ArkUI 单页验收页面(监听器编辑、配置、推送、处理轨迹、操作日志、版本握手);
  • 全模块 JVM 编译交叉验证 + HAP 构建、签名、真机安装与运行验证(版本握手与 configure 闭环见效果图);
  • 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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐