开源鸿蒙平台 CMP 三方库「hyphen」适配全流程
本文记录
hyphen接入 OpenHarmony 的完整过程,覆盖 Markdown 编辑引擎盘点、源码集切分、真实引擎编译、ohosArm64/ohosX64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收。
hyphen是 Compose Multiplatform 的 Markdown-Native WYSIWYG 富文本编辑器:Markdown 语法是唯一数据源,输入**、#、- [ ]的瞬间语法被剥离并渲染成样式,复制选区时又自动还原为 Markdown。它的价值分两层——引擎层(MarkdownProcessor解析 20 种语法、MarkdownSerializer按嵌套优先级还原定界符、SpanManager区间代数)是纯 Kotlin;编辑器层(HyphenTextState状态机深度绑定TextFieldState/InputTransformation/OutputTransformation新管线)离不开androidx.compose.foundation。本次适配与 KStore"制品线补齐、真编译整库"的路线有本质区别:上游 CMP 1.10.1 / Kotlin 2.3.21 的 TextField 新管线在鸿蒙社区工具链(Kotlin2.2.21-1.0.0)上编不过,因此走 cmp_qrose 已过审的源码集切分路线——引擎层commonMain原样编译成 OpenHarmony KLIB,Compose 编辑器 UI 完整保留在src/composeMain不参与编译,等社区工具链对齐后改回目录名即可接回。一个"输入即转换、复制即还原"的 Markdown 双向转换引擎运行在真机上,ArkUI 页面作为验收宿主。
项目地址: AtomGit/oh-tpc/hyphen.git
上游仓库: DenserMeerkat/hyphen
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
hyphen 为 Compose Multiplatform 应用提供"Markdown ↔ 富文本"这一件事的完整答案:rememberHyphenTextState() 一行建状态,HyphenBasicTextEditor / HyphenTextField 两个开箱编辑器,20 种样式即时转换(粗体、斜体、下划线、删除线、高亮、行内代码、H1–H6、链接、提及、无序/有序列表、引用块、勾选/未勾选任务,外加围栏代码、表格、水平线、图片四种原样保留的 Passthrough 区),复制选区自动序列化回 Markdown,@ / # / {{ 触发器自动补全,细粒度撤销重做与 RTL 支持。KMP 项目把这些能力放在 commonMain,由 Android、桌面、Web 共享。OpenHarmony 应用同样需要这套编辑能力,但上游构建(Kotlin 2.3.21 / CMP 1.10.1 / AGP 8.13.1)不会生成 OpenHarmony 变体。
如果只把示例页面改成 ArkTS,页面可以显示文本,却无法证明公共 Markdown 引擎在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证解析/序列化引擎、区间代数、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游 Kotlin 2.3.21 / CMP 1.10.1 / AGP 8.13.1 与鸿蒙工具链无法共存于一个 Gradle 构建;鸿蒙线 HarmonyOS Kotlin 2.2.21-1.0.0 读不了 2.3+ 元数据。 |
| UI 深绑 Compose | HyphenTextState(1340 行)绑定 TextFieldState + InputTransformation / OutputTransformation,HyphenBasicTextEditor 依赖 Modifier.Node 与剪贴板拦截,社区 CMP 鸿蒙制品编不过这套 API。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。 |
| 编辑语义跨不了桥 | 打字即时转换、剪贴板拦截、触发器弹窗是 Compose 事件管线概念,C ABI 只能同步一来一回。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:引擎层(markdown/ + model/ + SpanManager)的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。解析与序列化语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
Demo 提供一个真实的 Markdown 双向转换会话,对应桥接的全部入口:
- 解析(parse):
Hyphen.parse(markdown, cursor, triggers)把原始 Markdown 解析为「无语法纯文本 + 样式区间列表」——# 标题变成大字号文本加H1区间,**加粗**变成粗体文本加Bold区间,[链接](url)剥掉语法保留文本加带url参数的Link区间,@提及识别为Mention(scheme, id, display),围栏代码块整体标记为Passthrough(rawMarkdown)原样保留; - 序列化(serialize):
Hyphen.serialize(text, spans)把文本 + 样式区间还原为 Markdown——嵌套样式按上游getStylePriority优先级表(标题 10 < 粗体 20 < 斜体 30 < 下/删/高 40 < 链接/提及 50 < 行内码 100)决定定界符插入顺序,Bold + Italic得到稳定的***text***;支持只序列化选区[start, end)(上游复制选区到剪贴板的语义); - 版本握手:
HyphenVersion()返回 fork 版本号,来自真实 Native 调用而非静态文字。
响应包含:
| 字段 | 作用 |
|---|---|
ok | 操作是否成功,失败时 error 带原文。 |
text / spans | parse 返回无语法纯文本与样式区间数组(Link 带 url、Mention 带 id/display/scheme、Passthrough 带 rawMarkdown)。 |
markdown | serialize 返回还原后的 Markdown 文本。 |
changed / cursor | 输入是否含可识别语法、建议光标位置。 |
引擎侧完整能力(20 种样式规则、区间位移/合并/去重/切换的 SpanManager 代数、触发器动态正则)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 33 项 jvmTest 交叉验证用例,证明 ohos .so 里执行的解析/序列化逻辑没有漂移。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 引擎层(markdown/ 4 文件 + model/ 5 文件 + state/SpanManager.kt)原样编译,不重写;全仓只新增 1 个 Kotlin 源文件(Hyphen.kt 无头公共入口)与 3 处手工微调(剥注解、纯函数归位)。 |
| 平台目标 | 根构建直接声明 ohosArm64 + ohosX64,保留 jvm() 作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。 |
| 构建隔离 | 根构建替换为极简 ohos 构建(cmp_qrose 模式);Compose 编辑器 UI 与状态机隔离到 src/composeMain、依赖 Compose 的测试隔离到 src/composeTest,留盘不进构建;上游 sample/(Android/Desktop/Web)留盘不进 settings。 |
| 桥接稳定 | 使用 3 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | 引擎层零第三方依赖(纯 Kotlin + 正则),不需要 kotlinx 制品线;工具链钉 HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(qrose 已验证组合)。 |
| UI 完整 | 三 Tab 覆盖全部桥接入口:解析实时预览 + 区间清单、序列化选区 toggle + 实时 Markdown、关于版本握手。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。hyphen 的编辑器 UI 随
composeMain隔离,ArkTS Demo 只是把桥暴露的双向转换闭环画出来——这正是"WYSIWYG 打字转换属 Compose 事件管线、引擎数据模型可以独立验收"的证明。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、源码集切分标准(不 import compose 即留下)、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、HarmonyOS Kotlin 2.2.21-1.0.0 钉版、Gradle 8.14.1
第 3 阶段:桥协议建模 ── parse/serialize/version JSON 契约、cursor=-1 无头语义、错误通道
第 4 阶段:原生桥接 ── @CName C ABI × 3、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 三 Tab:解析实时预览、序列化选区 toggle、关于版本握手
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
hyphen/ 库模块(引擎进 ohos 构建,jvm + ohosArm64 + ohosX64)
hyphen/src/commonMain/ 引擎层:markdown/ + model/ + state/SpanManager.kt + Hyphen.kt(唯一新增)
hyphen/src/composeMain/ 上游 Compose 编辑器 UI 与状态机(留盘不编,含 composeResources)
hyphen/src/composeTest/ 依赖 Compose 的 5 个测试(留盘不编)
hyphen/src/androidMain|desktopMain|jsMain|wasmJsMain
上游平台 actual(ui/internal 的剪贴板与指针实现,留盘不编)
example/nativeApp/ Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(英文摘要 + 25 节中文主文档)
README.upstream.md 上游原文保存
sample/ 上游 Android/Desktop/Web 示例,留盘不进构建
构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :hyphen 与 :example:nativeApp,rootProject.name = "ohos_hyphen"。上游的 android/compose 插件矩阵、Room 3 / KSP、dokka、vanniktech 发布与签名配置全部随根构建替换剥离——本 fork 不发 Maven Central。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| JDK | 21(JAVA_HOME 指向本机 JDK 21;不提交机器路径) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 8.14.1(wrapper 替换;2.2.21 线必须 8.x,与 KStore 的 2.4.20 线留 Gradle 9.x 互为印证——两条工具链线经验不能互相套用) |
| 引擎依赖 | 零第三方依赖(纯 Kotlin + kotlin.text.Regex),无需 kotlinx 制品线 |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.hyphen |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
切分标准只有一条:不 import androidx.compose.* / org.jetbrains.compose.* 的文件留在编译集。
| 内容 | commonMain 文件 | 依赖形态 | 处置 |
|---|---|---|---|
markdown/ | 3(Processor 348 行 / Serializer 129 行 / Constants) | 纯 Kotlin + 正则 | 整体编译 |
model/ | 5(MarkupStyle 20 种样式 / MarkupStyleRange / StyleSets / TextRangeWith / TriggerConfig) | 纯 Kotlin,仅 TriggerConfig 带 @Immutable 注解 | 剥注解后编译 |
state/SpanManager.kt | 1 | 纯 Kotlin,但引用了 Compose 文件里的 BlockStyleManager.isBlockStyle | 纯函数归位后编译 |
state/ 其余 4 文件 | — | TextFieldState / TextRange / TextFieldBuffer | 隔离到 composeMain |
ui/ 全部 + composeResources/ | — | BasicTextField / Modifier.Node / 剪贴板 / Material3 | 隔离到 composeMain |
| 引擎测试 | 3 个测试类(33 用例) | 纯 Kotlin | 留在 commonTest 编译 |
| Compose 测试 | 5 个测试类 | TextFieldState / Compose runtime | 隔离到 composeTest |
三处手工微调,每处对上游 Android/Desktop/Web 零行为影响:
// 1. model/TriggerConfig.kt —— 剥离 Compose 注解与 import(ohos 无 compose runtime)
- import androidx.compose.runtime.Immutable
- @Immutable × 2
// 2. state/SpanManager.kt —— 吸收 BlockStyleManager 里的纯函数,Compose 侧委托回来
fun isBlockStyle(style: MarkupStyle): Boolean = style in StyleSets.allBlock
// 3. composeMain/state/BlockStyleManager.kt —— 委托
fun isBlockStyle(style: MarkupStyle): Boolean = SpanManager.isBlockStyle(style)
1.4 页面边界
Tab 切换 解析 / 序列化 / 关于
解析 Tab Markdown 多行输入(默认样例覆盖全部 20 种语法)
实时预览:标题分级、粗斜下删、行内码/高亮底色、链接蓝色下划线、
引用竖线、任务勾选框、有序列表自动编号、围栏代码灰度等宽、提及着色
状态行:区间数 / 字符数 / 耗时 / changed
区间调试清单:Passthrough[215,249) 形式逐条列出
序列化 Tab 纯文本输入 + 链接 URL 输入 + 选区跟踪(onTextSelectionChange)
20 种样式 toggle 按钮 + 提及/透传/清空/恢复
实时 Markdown 输出(等宽字体) + 当前选区与区间数状态行
关于 Tab 版本握手、库简介、调用链分层、仓库与社区链接、已知限制
页面启动时先调 HyphenVersion() 做握手,再对默认样例发起首次解析。
第 2 阶段:目标与依赖打通
2.1 仓库置前与钉版
settings.gradle.kts 把 eazytec 双仓写在最前(ohosArm64 目标只存在于 HarmonyOS Kotlin 插件,仓序不对配置期直接 Unresolved reference: ohosArm64):
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()
}
}
rootProject.name = "ohos_hyphen"
include(":hyphen")
include(":example:nativeApp")
工具链钉 HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 已验证线),gradle/libs.versions.toml 从 58 行上游目录缩减为 6 行——只剩 kotlin 版本与 multiplatform 插件,AGP / CMP / Room / KSP / dokka / vanniktech 别名全部随根构建替换消失;gradle.properties 写入 version=0.7.1-alpha02-ohos.1 与 kotlin.native.ignoreDisabledTargets=true、kotlin.native.distribution.downloadFromMaven=true;wrapper 替换为 8.14.1。
2.2 编译真实引擎
hyphen/build.gradle.kts 收敛为纯 KMP 模块:
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
applyDefaultHierarchyTemplate()
jvmToolchain(21)
jvm() // 交叉验证用
ohosArm64()
ohosX64()
sourceSets {
commonTest.dependencies {
implementation(kotlin("test"))
}
}
}
上游 commonMain 里的 compose 依赖声明、android 配置、发布与签名段全部随重写剥离。实测:
./gradlew :hyphen:compileKotlinJvm :hyphen:compileKotlinOhosArm64 :hyphen:compileKotlinOhosX64
# BUILD SUCCESSFUL —— 引擎层一行行为逻辑未改
2.3 测试的处置:Compose 测试隔离、引擎测试零改动
依赖 Compose 的 5 个测试类(EditorExtensionsTest、HyphenTextStateTest 等)随源码集搬移到 src/composeTest 不再编译;引擎测试 3 个类留在 commonTest。ohos 测试任务本身跑不了(无测试运行器),根构建全局禁用:
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
这样引擎测试文件(33 项用例)零改动保留,交叉验证在 jvmTest 上全部跑绿。
第 3 阶段:桥协议建模
3.1 JSON 契约
com.denser.hyphen/**(上游引擎:Processor / Serializer / SpanManager,原样编译)
↓ Hyphen.parse / Hyphen.serialize(新增公共入口)
Bridge.kt(@CName × 3,JSON 进出)
↓ UTF-8 JSON
N-API hyphenParse() / hyphenSerialize() / hyphenVersion()
↓
ArkUI Index.ets(三 Tab)
请求与响应:
请求 : {"markdown":"# hi\n**x**","cursor":-1,"triggers":[{"trigger":"@","scheme":"mention"}]}
{"text":"加粗","spans":[{"style":"Bold","start":0,"end":2}],"start":-1,"end":-1}
响应 : {"ok":true,"text":"hi\nx","spans":[{"style":"H1","start":0,"end":2},
{"style":"Bold","start":3,"end":4}],"changed":true,"cursor":-1}
{"ok":true,"markdown":"**加粗**"}
{"ok":false,"error":"..."}
span 的 style 共 20 种:六种行内(Bold / Italic / Underline / Strikethrough / Highlight / InlineCode)、六种标题(H1–H6)、五种块(BulletList / OrderedList / Blockquote / CheckboxUnchecked / CheckboxChecked)与三种参数化样式(Link 带 url、Mention 带 id/display/scheme、Passthrough 带 rawMarkdown)。start / end 给 -1(默认)时全文序列化,给 [start, end) 时只序列化选区。
3.2 无头语义与公共入口
上游 MarkdownProcessor 是 internal object,跨模块不可见;且 process(rawText, cursorPosition, ...) 带有"用户正在输入"启发式(光标落在 Mention 区间内且非显式闭合则跳过识别)。桥需要的是无头全量解析,因此新增全仓唯一 Kotlin 源文件:
// hyphen/src/commonMain/kotlin/com/denser/hyphen/Hyphen.kt
public object Hyphen {
public data class Document(val text: String, val spans: List<MarkupStyleRange>,
val changed: Boolean, val cursor: Int)
public fun parse(markdown: String, cursor: Int = -1,
triggerConfigs: List<TriggerConfig> = emptyList()): Document
public fun serialize(text: String, spans: List<MarkupStyleRange>): String
public fun serialize(text: String, spans: List<MarkupStyleRange>,
start: Int, end: Int): String // 选区(剪贴板语义)
}
cursor = -1 恒使"正在输入"判定为假,这是无头全量解析的正确姿势——与 cmp_qrose 给 internal QRCode 加 Qrose.encodeQr 公共包装是同一手法。桥内 span ↔ JSON 双向映射(styleName / jsonToSpan)覆盖全部 20 种样式与参数字段。
3.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("HyphenParse")
fun hyphenParse(request: String): String = try {
/* MiniJson 解包 → Hyphen.parse → JSON 打包 */
} catch (t: Throwable) {
err(t) // {"ok":false,"error":"..."}
}
非法 JSON、未知 style 名、非法区间(start > end 返回 null 丢弃而非崩溃)、触发器缺 trigger 字段,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry(libhyphen_napi.so)
│ const char*
▼
Kotlin/Native C ABI(libohoshyphen.so)
│ HyphenParse / HyphenSerialize / HyphenVersion
▼
UTF-8 JSON + DisposeString
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制解析语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("HyphenParse")
fun hyphenParse(request: String): String // markdown → {text, spans}
@CName("HyphenSerialize")
fun hyphenSerialize(request: String): String // {text, spans} → markdown
@CName("HyphenVersion")
fun hyphenVersion(): String // 版本握手
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
HyphenParse | hyphenParse(req) | MarkdownProcessor.process 全量规则 + TriggerConfig 触发器 |
HyphenSerialize | hyphenSerialize(req) | MarkdownSerializer.serialize(全文 / 选区) |
HyphenVersion | hyphenVersion() | fork 版本握手(真机版本行的数据来源) |
桥的 JSON 编解码用手写 MiniJson——但与 KStore / qrose 的扁平版不同,hyphen 的序列化请求带嵌套数组(spans 数组),所以这版是完整递归下降实现(对象/数组/字符串/全部转义/数字/布尔/null 约 200 行),nm 验证与生成头逐符号一致。
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"hyphenParse", nullptr, NapiHyphenParse, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"hyphenSerialize", nullptr, NapiHyphenSerialize, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
{"hyphenVersion", nullptr, NapiHyphenVersion, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
三个回调抽成一个 CallStringBridge 模板函数:napi_typeof 校验必须是 string、OH_LOG_INFO 打字节数、调 KN 函数并包装返回。KN 返回的字符串必须经 libohoshyphen_symbols()->DisposeString 归还 Kotlin 运行时,绝不 free()。libohoshyphen_api.h 按社区已审仓库同款手写(只声明 ExportedSymbols 结构与三个业务符号),并与 KN 生成的官方头逐行核对一致。
4.4 CMake 和类型声明
add_library(ohoshyphen SHARED IMPORTED)
set_target_properties(ohoshyphen PROPERTIES
IMPORTED_LOCATION ${KN_SO}
IMPORTED_NO_SONAME TRUE)
add_library(hyphen_napi SHARED napi_init.cpp)
target_link_libraries(hyphen_napi PUBLIC
libace_napi.z.so
libhilog_ndk.z.so
ohoshyphen)
OHOS_ARCH 为空或 libohoshyphen.so 缺失直接 FATAL_ERROR(提示先跑 link 任务);$ORIGIN rpath 加 POST_BUILD 拷贝保证 NAPI so、KN so、libc++ 三件套同目录,dlopen 时互相可见。
export const hyphenParse: (request: string) => string;
export const hyphenSerialize: (request: string) => string;
export const hyphenVersion: () => string;
第 5 阶段:ArkUI 宿主
5.1 版本握手与实时解析
aboutToAppear(): void {
this.reparse();
this.resetDemoSpans();
this.versionInfo = /* hyphenVersion() 解包 */;
}
private reparse(): void {
const req = `{"markdown":${jsonStr(this.mdInput)},"cursor":-1,` +
`"triggers":[{"trigger":"@","scheme":"mention"},{"trigger":"#","scheme":"tag"}]}`;
const res = JSON.parse(hyphenParse(req)) as ParseResponse;
this.previewLines = buildPreview(res.text, res.spans); // @State 刷新预览
this.spanBriefs = res.spans.map(spanBrief); // 区间调试清单
}
预览构建把引擎的 span 区间还原为 ArkTS Text + Span 富文本:逐字符聚合样式签名得到样式游程(run),块样式换算行前缀(引用 ┃、任务 ☐/☑、无序 •、有序自动编号),标题按级别映射字号,行内码与 Passthrough 走等宽灰显——这是 SpanManager.consolidateSpans 语义的 ArkTS 复刻。序列化 Tab 用 TextArea 的 onTextSelectionChange(注意是两个独立 number 参数)跟踪选区,toggleStyle 完全复刻上游 SpanManager.toggleStyle 的"完全包裹则拆三段,否则追加后合并"语义,每次变更即时调 hyphenSerialize 出 Markdown。
5.2 页面交互
解析 Tab 自上而下:Markdown 输入域(每输入一个字符实时重解析)、状态行(区间数/字符数/耗时)、富文本预览滚动区、前 12 条区间调试清单。序列化 Tab:纯文本域 + URL 输入 + 20 个样式按钮(含 @ 提及、```````透传 ````、清空、恢复默认)+ 实时 Markdown 输出。关于 Tab 展示版本握手行、库简介、ArkTS → NAPI → C ABI → Kotlin 引擎 分层说明与仓库 / 社区链接。
5.3 对比度和错误边界
预览区白底卡片、状态行绿色(错误红色)、样式按钮按语义着色(链接蓝、提及紫、透传青、清空红);ok:false 时错误原文进状态行;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进状态行。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/
│ ├── build.gradle.kts 双 ABI sharedLib(baseName=ohoshyphen)+ cxxShared + copyKnSo*
│ └── src/ohosMain/kotlin/example/nativeapp/
│ ├── Bridge.kt @CName × 3 + span ↔ JSON 映射
│ └── MiniJson.kt 完整递归下降 JSON(嵌套数组)
└── harmonyApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName org.terminator.ohos.hyphen、versionName 0.7.1-alpha02-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 导出 × 3、参数检查、DisposeString
├── src/main/cpp/include/libohoshyphen_api.h
├── src/main/cpp/types/libhyphen_napi/{index.d.ts, oh-package.json5}
└── src/main/ets/pages/Index.ets ArkUI 三 Tab 验收页面
6.2 构建、签名和安装
./gradlew :hyphen:jvmTest # 33 项交叉验证
./gradlew :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 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,勾选自动签名后由 IDE 回填带 material 的完整配置)。
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.hyphen -m entry
四、完整代码对照
4.1 整体架构
com.denser.hyphen.markdown/**(上游引擎原样编译)
│ MarkdownProcessor(20 规则 + 触发器)/ MarkdownSerializer(优先级定界符)/ MarkdownConstants
com.denser.hyphen.model/**(上游模型原样编译)
│ MarkupStyle × 20 / MarkupStyleRange / StyleSets / TriggerConfig
com.denser.hyphen.state/SpanManager(上游区间代数原样编译)
│ shift / merge / consolidate / toggle
Hyphen.kt(唯一新增:无头公共入口)
│ parse / serialize
Bridge.kt + MiniJson
│ HyphenParse / HyphenSerialize / HyphenVersion
libhyphen_napi.so
│ hyphenParse / hyphenSerialize / hyphenVersion
Index.ets
│ JSON parse + 预览构建 + 选区 toggle
▼
ArkUI 真机页面(三 Tab)
4.2 文件清单
| 文件 | 职责 |
|---|---|
settings.gradle.kts | 极简 ohos 构建、eazytec Maven 置前、ohos_hyphen、sample 移出 |
gradle/libs.versions.toml | 缩减为 Kotlin 2.2.21-1.0.0 + multiplatform 插件 |
build.gradle.kts(根) | 版本 0.7.1-alpha02-ohos.1、全局禁用 Ohos*Test* 任务 |
hyphen/build.gradle.kts | 纯 KMP:jvm + ohosArm64 + ohosX64,compose/android/发布全剥离 |
hyphen/src/**(搬移) | ui/、state 4 文件、composeResources → composeMain;5 个 Compose 测试 → composeTest |
hyphen/src/commonMain/.../Hyphen.kt | 唯一新增源文件(无头入口 + Document 模型) |
example/nativeApp/build.gradle.kts | 双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填 |
nativeApp/.../Bridge.kt | 3 个 C ABI、span ↔ JSON 双向映射、错误通道 |
nativeApp/.../MiniJson.kt | 完整递归下降 JSON(嵌套数组,继承自 cmp_qrose 审核模式并扩展) |
harmonyApp/.../cpp/napi_init.cpp | N-API 导出 × 3、参数检查、DisposeString |
harmonyApp/.../cpp/CMakeLists.txt | IMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套 |
harmonyApp/.../ets/pages/Index.ets | ArkUI 三 Tab 验收页面 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(25 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | MarkdownProcessor.process(raw, cursor, triggers) | Markdown → 无语法文本 + 样式区间(internal,经 Hyphen 暴露) |
| 引擎 | MarkdownSerializer.serialize(text, spans[, start, end]) | 样式区间 → Markdown,嵌套按优先级定序,支持选区 |
| 引擎 | SpanManager.shiftSpans / mergeSpans / consolidateSpans / toggleStyle | 区间位移 / 重解析合并 / 去重 / 切换(Demo toggle 的语义来源) |
| 模型 | MarkupStyle × 20 / MarkupStyleRange / TriggerConfig | 样式判别联合与区间、触发器配置 |
| 入口 | Hyphen.parse / Hyphen.serialize | 无头公共入口(cursor = -1 关闭打字启发式) |
| Native | HyphenParse / HyphenSerialize / HyphenVersion | C ABI JSON 导出 |
| N-API | hyphenParse(req) / hyphenSerialize(req) / hyphenVersion() | 向 ArkTS 暴露能力 |
| ArkTS | reparse() / toggleStyle(style) | 实时解析刷新 / 选区样式切换 |
4.4 ArkTS 与 Kotlin 的边界
// Markdown → 文本 + 样式区间
const parsed = JSON.parse(hyphenParse(JSON.stringify({
markdown: '# 标题\n**加粗** [链接](https://atomgit.com/oh-tpc/hyphen)',
cursor: -1,
triggers: [{ trigger: '@', scheme: 'mention' }],
})));
// parsed.text === '标题\n加粗 链接'
// parsed.spans === [{style:'H1',start:0,end:2}, {style:'Bold',...},
// {style:'Link',...,url:'https://atomgit.com/oh-tpc/hyphen'}]
// 文本 + 样式区间 → Markdown
const out = JSON.parse(hyphenSerialize(JSON.stringify({
text: '加粗',
spans: [{ style: 'Bold', start: 0, end: 2 }],
})));
// out.markdown === '**加粗**'
两者之间只传输 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:源码集切分而不是真编译整库
KStore 适配时 kotlinx 制品线补齐,真编译整库是最优解;hyphen 不行——HyphenTextState 绑定 TextFieldState 新管线(InputTransformation / OutputTransformation),这套 API 依赖 CMP 1.10.1 / Kotlin 2.3.21,鸿蒙社区工具链 2.2.21-1.0.0 编不过,也没有"补一个 actual"级别的接缝。引擎层(解析/序列化/区间代数)恰是纯 Kotlin,于是按 cmp_qrose 已过审手法切分:引擎留 commonMain 原样编译,UI 隔离 composeMain 保留源码不删——工具链对齐后把目录改回来即可接回,光栅化/桥降级为 fallback。
决策 2:工具链钉 Kotlin 2.2.21-1.0.0,Gradle 降 8.14.1
引擎零第三方依赖,不受 kotlinx 制品线约束,直接沿用 qrose / haze 已验证的 2.2.21-1.0.0 组合;该线只支持 Gradle 8.x,wrapper 从上游 8.13 替换为 8.14.1(qrose 同款)。与 KStore 的 2.4.20 线留 Gradle 9.x 互为印证:工具链线决定 Gradle 版本,两条线的经验不能互相套用。
决策 3:根构建替换(cmp_qrose 模式),sample 与平台 actual 留盘
上游根构建(AGP / compose 插件矩阵 / Room 3 / KSP / dokka / vanniktech 发布 / Maven Central 签名)对鸿蒙没有保留价值,整个替换为极简 ohos 构建。sample/(含 Room 数据库的 Android/Desktop/Web 示例)与 androidMain 等平台 actual(Compose UI 的剪贴板/指针实现)留盘不进 settings.gradle.kts——它们属于 Compose 层,随 composeMain 一起接回。
决策 4:三个导出函数 + 完整递归 MiniJson
parse / serialize / version 是三个独立用途,分三个 @CName 导出让 ArkTS 调用面最小。序列化请求的 spans 是嵌套数组,KStore / qrose 的扁平 MiniJson 覆盖不了,桥内改为完整递归下降实现(约 200 行)——这是本批适配里 MiniJson 的进化点,后续带嵌套载荷的库可以直接复用。
决策 5:jvmTest 交叉验证 + 测试零改动
引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;33 项 jvmTest 跑的就是上游原测试文件(MarkdownProcessorTest 12 项、MarkdownSerializerTest 9 项、SpanManagerTest 12 项),文件一个字节不动。依赖 Compose 的 5 个测试类随源码集隔离到 composeTest 而不是删除——测试零改动才让"引擎没漂"有说服力。
六、测试与验证
6.1 测试环境
本次验证使用 macOS(arm64)、JDK 21、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew :hyphen:jvmTest
33 项用例全部通过(上游原测试文件,零改动):
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
MarkdownProcessorTest | 12 | 解析状态机:标题/行内/块/任务前缀剥离、触发器识别、区间生成 |
MarkdownSerializerTest | 9 | 序列化往返:定界符插入、嵌套优先级、选区裁剪 |
SpanManagerTest | 12 | 区间代数:shift/merge/consolidate/toggle 的边界矩阵 |
源码集切分没有动引擎一行行为逻辑:同一份测试在 JVM 全绿,就是 ohos .so 里执行的解析/序列化逻辑没漂的直接证据。
6.3 HAP 验证
:hyphen:compileKotlinJvm / compileKotlinOhosArm64 / compileKotlinOhosX64: BUILD SUCCESSFUL
:hyphen:jvmTest: 33 项全绿
linkDebugSharedOhosArm64 / linkDebugSharedOhosX64: BUILD SUCCESSFUL
nm -gU: HyphenParse / HyphenSerialize / HyphenVersion / libohoshyphen_symbols 均为全局 T 符号
BuildNativeWithCmake: finished(双 ABI 编出 libhyphen_napi.so)
CompileArkTS: finished
SignHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully
entry-default-signed.hap 约 7.8 MB,内含双 ABI 各三件套(libhyphen_napi.so / libohoshyphen.so / libc++_shared.so);entry/libs 下 KN so arm64 约 8.7 MB、x86_64 约 7.9 MB(debugShared 含 Kotlin/Native 运行时)。
6.4 功能用例
- 启动后关于 Tab 出现
hyphen 0.7.1-alpha02-ohos.1(上游 0.7.1-alpha02)(来自真实 Native 调用,非静态文字); - 解析 Tab 默认样例实时解析,状态行显示
18 个样式区间 · 249 字符 · 117 ms · changed=true(真机实测); - 预览按引擎区间渲染:H1/H2 大字号、粗体/斜体/下划线/删除线/黄色高亮/等宽行内码、
☐/☑任务勾选、•无序与自动编号有序列表、┃引用、蓝色下划线链接、@提及着色、围栏代码整块灰显等宽; - 修改输入域任意字符即时重解析,区间清单滚动更新(
Passthrough[215,249)、H1[0,20)等逐条可见); - 序列化 Tab 预置
Bold与Link区间,Markdown 输出非空(**反向序列化**与[同一份引擎](url)); - 选中文本点样式按钮,Markdown 实时变化;再点同一样式按钮即移除(
SpanManager.toggleStyle的拆三段语义); - 修改 URL 输入框,已有 Link 区间的
url同步更新并重新序列化; @ 提及按钮输出[选中文本](mention:oh-tpc);```````透传 ````按钮把选区包成围栏代码原样回吐;- 状态行实时显示当前选区
[start, end)与区间计数。
6.5 验证结论
解析/序列化引擎、区间代数、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在鸿蒙真机上成功安装并进入前台,页面展示了真实的双向转换会话:版本握手、Markdown 实时解析预览、选区样式 toggle 与实时序列化闭环(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/hyphen.git
cd hyphen
export JAVA_HOME="/path/to/jdk-21"
./gradlew :hyphen:compileKotlinOhosArm64 :hyphen:compileKotlinOhosX64 # klib
./gradlew :hyphen:jvmTest # 33 项交叉验证
./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.hyphen -m entry
hdc shell hilog | grep HyphenNapi
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让
:hyphen引擎层真正编译成ohosArm64/ohosX64动态库; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- 上游
libs.versions.toml里的 AGP / CMP / KSP 别名必须随根构建替换一起清掉——留着会按上游版本解析插件,与 HarmonyOS Kotlin2.2.21-1.0.0冲突; - Gradle 8.14.1 在 JBR 25 上起不来:Gradle 侧
JAVA_HOME必须指到 JDK 21,HAP 打包用 DevEco 自带 JBR,两条 Java 路线互不通用;wrapper 发行包从services.gradle.org首次下载可能超时,缓存目录出现.ok标记后即离线可用; @Immutable这类 Compose 注解留在commonMain会让 ohos 编译直接失败(鸿蒙线没有 compose runtime)——注解与 import 一起剥;internal object MarkdownProcessor跨模块不可见:给桥用必须新增公共入口包装(Hyphen.parse/serialize),与 qrose 的Qrose.encodeQr同手法;- 纯函数困在 Compose 文件里编不进引擎:
BlockStyleManager.isBlockStyle移进SpanManager,composeMain侧委托回来——切分时要顺着 import 引用链逐文件核对,不能只看目录名; - Kotlin/Native 侧嵌套类型(
JVal.JStr等)必须带限定名,String.format在 Native 不可用(改toString(16).padStart(4, '0'))——桥代码写完先在 JVM 心里过一遍 stdlib 差异; - ArkTS TextArea 的选区事件是
onTextSelectionChange,且回调是两个独立number参数而非元组——照 Web/Flutter 直觉写onSelectionChange会编译失败; - KN 返回字符串必须
DisposeString归还运行时,N-API 层不能free();手写头文件要与 KN 生成的官方头逐符号核对(libohoshyphen_symbols等 4 个符号); - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套缺一不可; - 签名材料由 DevEco 自动写入
build-profile.json5,提交前把signingConfigs恢复为空数组(本仓提交版已验证为[]),证书绝不进入 AtomGit; - DevEco Studio 只能打开
example/harmonyApp子目录打 HAP,打开仓库根目录会把 Gradle 工程和 hvigor 工程搅在一起。
8.2 已知问题
- Compose 编辑器 UI 未上鸿蒙(
composeMain隔离):WYSIWYG 打字即时转换、剪贴板 Markdown 拦截、触发器建议弹窗、细粒度撤销重做属于 Compose 事件管线,ArkTS Demo 不复刻; HyphenTextState/SelectionManager/HistoryManager/BlockStyleManager(除isBlockStyle)随composeMain隔离,随工具链对齐后接回;- 块样式的原始前缀(
- [ ]等)保留在引擎输出的无语法文本中——上游由 ComposeOutputTransformation在 UI 层隐藏,属于编辑器层职责;Demo 忠实呈现引擎数据模型,预览符号(☐)与前缀原文并存; - 触发器"正在输入跳过识别"的启发式在无头侧以
cursor = -1关闭;ArkTS 要模拟打字语义需把真实光标传入HyphenParse.cursor; - android / js / wasmJs 平台 actual 剥离(源文件留在树里),发布、dokka 一并剥离,本 fork 不发 Maven Central;
- 上游 CI(GitHub Pages 部署 / Maven Central 发布)不适用本 fork,保留未动;
example/harmonyApp图标沿用 DevEco 默认图(与 cmp_qrose 一致),发布前可替换;- HAP 安装必须使用与 bundle name
org.terminator.ohos.hyphen匹配的签名。
8.3 未来优化方向
- 等社区
compose.ui/compose.foundationohos 制品与上游 CMP 版本对齐后,把composeMain改回commonMain、恢复sample构建,桥降级为 fallback; - 把三个可上游的小改动(剥
@Immutable、isBlockStyle归位、Hyphen无头入口)整理成 PR 提给上游——无头引擎入口对 CLI / 服务端用户同样有价值; - 基于
SpanManager区间代数在 ArkTSRichEditor/StyledString上复刻打字即时转换(工作量集中在processMarkdownInput的等价移植); - 大文档异步解析(N-API 线程安全回表),替代 UI 线程同步调用;
- 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。
九、总结
9.1 核心难点回顾
上游 hyphen(commonMain = 引擎 + Compose UI 混居)
→ 源码集切分:不 import compose 的留下(引擎 10 文件),UI 隔离 composeMain
→ 三处手工微调(剥注解 / 纯函数归位 / 公共入口)
→ 工具链对齐 HarmonyOS Kotlin 2.2.21-1.0.0 + Gradle 8.14.1(qrose 组合)
→ 根构建替换(cmp_qrose 模式)
→ ohosArm64 / ohosX64 KLIB + 唯一新增 Hyphen.kt
→ C ABI(@CName × 3)+ cursor=-1 无头语义
→ C++ N-API(DisposeString 纪律)
→ ArkTS JSON 解析 + 预览构建 / 选区 toggle
→ ArkUI 真机三 Tab 验收(解析预览 / 序列化 / 版本握手)
9.2 封装层次
- 引擎层:上游原版
MarkdownProcessor(20 规则 + 触发器)、MarkdownSerializer(优先级定界符)、SpanManager(区间代数)与MarkupStyle模型; - 入口层:新增
Hyphen.parse / serialize把 internal 引擎包装成无头公共 API; - Native 层:生成双 ABI 动态库并通过 3 个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:管理页面状态、样式游程预览构建、选区跟踪、toggle 分发与错误文本;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 适配路线按"Compose 依赖深度"选,而不是按名气选:KStore 零 UI、依赖全是 kotlinx——制品线补齐后真编译整库是最优解;hyphen 深度绑定
TextFieldState新管线——源码集切分、引擎先行、UI 留盘等接回才是正解。切分标准"不 import compose 即留下"一条就够,但必须顺着 import 引用链逐文件核对(纯函数可能困在 Compose 文件里); - 无头语义要显式建模:编辑器引擎带着"用户正在输入"的启发式(光标位置影响识别结果),桥接无头场景必须提供关闭手段(
cursor = -1);internal API 跨模块不可见时,新增薄公共入口比放开可见性更可控; - 交叉验证是"引擎没漂"的唯一可信证明:33 项
jvmTest跑的是上游原测试文件,测试零改动才让切分有说服力;编辑器类库的 Demo 验收重点落在"双向转换闭环 + 区间清单可见",与存储类库的"重启后数据还在"异曲同工。
9.4 适配成果
当前适配已完成:
- hyphen
0.7.1-alpha02真实引擎的双 ABI Kotlin/Native 编译(引擎层零重写,唯一新增Hyphen.kt无头入口,三处手工微调对上游零行为影响); - Compose 编辑器 UI 与状态机完整保留在
composeMain(77 个文件的提交里全部以 rename 记录,历史可追溯,工具链对齐后可整体接回); - 3 个 C ABI 符号和 C++ N-API 包装层(
nm -gU验证全局 T 符号,手写头与 KN 生成头逐行一致); - DevEco Stage ArkUI 三 Tab 验收页面(解析实时预览 + 区间清单、选区 toggle 实时序列化、版本握手);
- 33 项 JVM 交叉验证全绿(上游原测试文件)+ HAP 构建(双 ABI 六件套 so,签名包约 7.8MB)、签名、设备安装和真机验证(真机解析 249 字符 / 18 区间 / 117 ms);
- AtomGit 项目文档、OpenHarmony README(中英双份 + 25 节中文主文档)和本地效果图。
适配过程 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)