开源鸿蒙平台 KMP 三方库「simple-robot」适配全流程
本文记录
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 / serialization1.11.0-0.1.0-07、kotlinx-io-core0.9.1-0.1.0-07,全部带ohosArm64/ohosX64Gradle 变体)与 KStore 验证过的是同一条,因此框架核心 14 个纯 Kotlin 模块的commonMain源码一行未动、连 actual 都一个没补,真实的引擎(launchSimpleApplication、listenersDSL、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 Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| Gradle | 9.5.0(沿用上游 wrapper;2.4.20 线支持 Gradle 9.x) |
| JDK | 21(org.gradle.java.home 指向 Eclipse Temurin jdk-21.0.12.101-hotspot) |
| kotlinx | coroutines / 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 传递解析) |
| KSP | 2.3.9(kspCommonMainMetadata + message-element 处理器,实测兼容鸿蒙 Kotlin) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.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-runner | coroutines(+core 再加 serialization);reactive / reactor / rx 仅 jvmMain compileOnly | 整体编译 |
| simbot-logger | slf4j 仅 jvmMain;webMain(kotlin-wrappers)仅 js/wasm | 整体编译,webMain 源码集留盘 |
| simbot-api | commonMain:coroutines + serialization-core + io-core;KSP 生成物硬引用;ktor 仅测试 | 整体编译(14 模块里唯一带构建期生成) |
| simbot-core / simbot-test / quantcat-common / continuous-session | 纯 Kotlin(reactive 家族仅 jvmMain) | 整体编译 |
| simbot-common-apidefinition / ktor-inputfile | commonMain 依赖 ktor-http / ktor-client-core | 留盘不进构建 |
| simbot-component-qq / onebot / kook | api 层即依赖 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 方法 | 覆盖内核 |
|---|---|---|
SimbotDispatch | simbotDispatch(req) | launchSimpleApplication 构造 + listeners 注册 + push 收集 |
SimbotVersion | simbotVersion() | 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.toml | Kotlin 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.kt | 2 个 C ABI、runBlocking 包裹、引擎 DSL 调用、错误通道 |
nativeApp/.../MiniJson.kt | 递归下降 JSON 解析器(支持监听器嵌套数组)+ 转义器 |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 验收页面(事件管道 + 关于) |
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 / invalid | simple / empty / invalid 三种正常结果直译为 kind |
| 引擎 | StandardEventResult.Error | 监听器抛异常的收口,直译为 kind=error |
| 引擎 | Application.cancel() | 重新 configure 时回收旧应用协程作用域 |
| Native | SimbotDispatch / SimbotVersion | C ABI JSON 导出 |
| N-API | simbotDispatch(req) / simbotVersion() | 向 ArkTS 暴露能力 |
| ArkTS | configure() / 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 功能用例(真机)
已在鸿蒙真机验证(见下节效果图):
- 启动后版本行出现
simple-robot 5.0.0-Alpha1-ohos.1 · 上游 5.0.0-Alpha1 · Kotlin 2.4.20(来自真实 Native 调用simbotVersion(),非静态文字); - 默认三监听器(A/p0/echo、B/p2/match:ping/constant:OK、C/p4/error)点「应用配置」,状态行出现
配置成功:3 个监听器已注册到真实 EventDispatcher; - 日志区按时间戳滚动留痕(
[04:05:39] 版本握手成功、[04:05:48] 配置成功)。
验收清单(真机逐项执行):
- 推送
type=ping, text=hello simbot:轨迹为P0 A · simple · HELLO SIMBOT→P2 B · simple · OK→P4 C · error · demo error…,顺序即优先级; - 把
text改为不含ping的内容再推送:B 变为invalid(真实跳过语义),A、C 不受影响; - 将 A 的优先级改为 10 再配置推送:轨迹顺序变为 B → C → A;
- 删除全部监听器再配置:返回
ok:falselisteners must be a non-empty array; - 未配置直接推送:返回
not configured yet: call op=configure first; 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 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让 14 个库模块真正编译成
ohosArm64/ohosX64目标并链接出动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;kotlinx-io-core在 Maven Central 上的 0.9.1 没有 ohos 变体,必须钉0.9.1-0.1.0-07;上游钉 0.9.0 时版本主体要进一位——版本主体写对、后缀写错,解析照样失败;- HarmonyOS KGP 不应用 Gradle Java 插件:
compileJvmMainJava任务存在但plugins.withId("java")永远不触发、sourceSets扩展不存在——JPMS patch-module 要直接钩JavaCompile任务类型,Kotlin 输出用标准布局build/classes/kotlin/jvm/main惰性解析,不能用上游 build-logic 的 SourceSetContainer 写法; module-info.java导出"只有生成类"的包会炸:love.forte.simbot包内只有 buildconfig 生成的SimbotBuiltin,javac 报"程序包为空或不存在"——构建期生成的类也可能是 JPMS 导出的唯一内容,buildconfig 插件必须保留;- KSP 处理器模块重写构建时丢了 serialization 插件,
Unresolved reference 'serializer'——处理器源码本身用了插件生成物;KSP 参数(localOnly/outputPackage/visibility)与commonMain.kotlin.srcDir(generated/ksp/metadata/commonMain/kotlin)接线、KspAATask 排序补丁都要照搬,否则 commonMain 编译期就缺符号; - 上游根构建在约定插件里统一 apply 的东西(serialization 插件、
-Xexpect-actual-classes、optIn 列表、configJavaCompileWithModule),根构建替换后必须在模块级逐一补回——漏了的报错位置往往不在漏的那一行; - clone 可能缺
gradle-wrapper.jar:上游.gitignore里*.jar写在!gradle-wrapper.jar之后(后匹配优先),取反被覆盖——在文件末尾再补一条!gradle/wrapper/gradle-wrapper.jar并把 jar 纳入版本控制,否则新 clone 直接ClassNotFoundException: GradleWrapperMain; - DevEco 打开工程后生成的
example/harmonyApp/.idea/.deveco(clangd 数据库约 14MB)与.clangd/.clang-tidy会混入提交,单独追加 gitignore;根目录.idea/*的根锚定规则盖不住嵌套目录; - Kotlin/Native 侧桥代码不能用 JVM-only API:
String.format/KotlinVersion.current/TimeMark.elapsed()分别换成toString(16).padStart(4,'0')/KotlinVersion.CURRENT/elapsedNow(); - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free(); - 文档与提交里仓库链接统一 AtomGit,不留任何第三方镜像站地址。
8.2 已知问题
- 依赖 Ktor 的组件模块(QQ / OneBot / KOOK)与 apidefinition / ktor-inputfile 未进 ohos 构建:Ktor 无 ohos 制品;"在鸿蒙上跑一个真的平台 bot"目前做不到,框架核心不受影响;
- 测试仅在 JVM 运行(ohos 测试任务全局禁用),ohos 侧承担编译与链接验证;
- 桥是同步一次一调用:长驻事件流(监听器向 ArkTS 侧推送)不跨 C ABI,
updatesFlow 式订阅留在 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 三条经验
- 框架类库的适配面是"引擎语义"而非渲染面:simbot 没有一行 UI,验收重点天然落在"注册的监听器真的按优先级被真实调度器调起"上——所以桥必须直译
EventResult四态而不是自造状态码,Demo 必须能看到invalid(跳过)与error(异常收口)这两种容易被手写总线忽略的语义; - 带注解处理器的框架,难点在构建期而不是源码期:KSP 生成函数被 commonMain 硬引用、buildconfig 生成类是 JPMS 导出的唯一内容、
--patch-module要适配鸿蒙 KGP "不应用 Java 插件"的差异——这三样保活了,"源码零修改"才成立;换到别的带处理器框架(Room 类、Moship 类)同样适用; - 制品线可达性决定裁剪线: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-core0.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。
参考文档
更多推荐




所有评论(0)