本文记录 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 新管线在鸿蒙社区工具链(Kotlin 2.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 深绑 ComposeHyphenTextState(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 / spansparse 返回无语法纯文本与样式区间数组(Link 带 url、Mention 带 id/display/scheme、Passthrough 带 rawMarkdown)。
markdownserialize 返回还原后的 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 MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
JDK21(JAVA_HOME 指向本机 JDK 21;不提交机器路径)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle8.14.1(wrapper 替换;2.2.21 线必须 8.x,与 KStore 的 2.4.20 线留 Gradle 9.x 互为印证——两条工具链线经验不能互相套用)
引擎依赖零第三方依赖(纯 Kotlin + kotlin.text.Regex),无需 kotlinx 制品线
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.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.kt1纯 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 方法覆盖内核
HyphenParsehyphenParse(req)MarkdownProcessor.process 全量规则 + TriggerConfig 触发器
HyphenSerializehyphenSerialize(req)MarkdownSerializer.serialize(全文 / 选区)
HyphenVersionhyphenVersion()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.kt3 个 C ABI、span ↔ JSON 双向映射、错误通道
nativeApp/.../MiniJson.kt完整递归下降 JSON(嵌套数组,继承自 cmp_qrose 审核模式并扩展)
harmonyApp/.../cpp/napi_init.cppN-API 导出 × 3、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 三 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 关闭打字启发式)
NativeHyphenParse / HyphenSerialize / HyphenVersionC ABI JSON 导出
N-APIhyphenParse(req) / hyphenSerialize(req) / hyphenVersion()向 ArkTS 暴露能力
ArkTSreparse() / 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 项用例全部通过(上游原测试文件,零改动):

测试类用例数钉住什么
MarkdownProcessorTest12解析状态机:标题/行内/块/任务前缀剥离、触发器识别、区间生成
MarkdownSerializerTest9序列化往返:定界符插入、嵌套优先级、选区裁剪
SpanManagerTest12区间代数: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 功能用例

  1. 启动后关于 Tab 出现 hyphen 0.7.1-alpha02-ohos.1(上游 0.7.1-alpha02)(来自真实 Native 调用,非静态文字);
  2. 解析 Tab 默认样例实时解析,状态行显示 18 个样式区间 · 249 字符 · 117 ms · changed=true(真机实测);
  3. 预览按引擎区间渲染:H1/H2 大字号、粗体/斜体/下划线/删除线/黄色高亮/等宽行内码、☐/☑ 任务勾选、• 无序与自动编号有序列表、┃ 引用、蓝色下划线链接、@提及 着色、围栏代码整块灰显等宽;
  4. 修改输入域任意字符即时重解析,区间清单滚动更新(Passthrough[215,249)、H1[0,20) 等逐条可见);
  5. 序列化 Tab 预置 Bold 与 Link 区间,Markdown 输出非空(**反向序列化** 与 [同一份引擎](url));
  6. 选中文本点样式按钮,Markdown 实时变化;再点同一样式按钮即移除(SpanManager.toggleStyle 的拆三段语义);
  7. 修改 URL 输入框,已有 Link 区间的 url 同步更新并重新序列化;
  8. @ 提及 按钮输出 [选中文本](mention:oh-tpc);```````透传 ````按钮把选区包成围栏代码原样回吐;
  9. 状态行实时显示当前选区 [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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :hyphen 引擎层真正编译成 ohosArm64 / ohosX64 动态库;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. 上游 libs.versions.toml 里的 AGP / CMP / KSP 别名必须随根构建替换一起清掉——留着会按上游版本解析插件,与 HarmonyOS Kotlin 2.2.21-1.0.0 冲突;
  4. Gradle 8.14.1 在 JBR 25 上起不来:Gradle 侧 JAVA_HOME 必须指到 JDK 21,HAP 打包用 DevEco 自带 JBR,两条 Java 路线互不通用;wrapper 发行包从 services.gradle.org 首次下载可能超时,缓存目录出现 .ok 标记后即离线可用;
  5. @Immutable 这类 Compose 注解留在 commonMain 会让 ohos 编译直接失败(鸿蒙线没有 compose runtime)——注解与 import 一起剥;
  6. internal object MarkdownProcessor 跨模块不可见:给桥用必须新增公共入口包装(Hyphen.parse/serialize),与 qrose 的 Qrose.encodeQr 同手法;
  7. 纯函数困在 Compose 文件里编不进引擎:BlockStyleManager.isBlockStyle 移进 SpanManager,composeMain 侧委托回来——切分时要顺着 import 引用链逐文件核对,不能只看目录名;
  8. Kotlin/Native 侧嵌套类型(JVal.JStr 等)必须带限定名,String.format 在 Native 不可用(改 toString(16).padStart(4, '0'))——桥代码写完先在 JVM 心里过一遍 stdlib 差异;
  9. ArkTS TextArea 的选区事件是 onTextSelectionChange,且回调是两个独立 number 参数而非元组——照 Web/Flutter 直觉写 onSelectionChange 会编译失败;
  10. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();手写头文件要与 KN 生成的官方头逐符号核对(libohoshyphen_symbols 等 4 个符号);
  11. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套缺一不可;
  12. 签名材料由 DevEco 自动写入 build-profile.json5,提交前把 signingConfigs 恢复为空数组(本仓提交版已验证为 []),证书绝不进入 AtomGit;
  13. DevEco Studio 只能打开 example/harmonyApp 子目录打 HAP,打开仓库根目录会把 Gradle 工程和 hvigor 工程搅在一起。

8.2 已知问题

  • Compose 编辑器 UI 未上鸿蒙(composeMain 隔离):WYSIWYG 打字即时转换、剪贴板 Markdown 拦截、触发器建议弹窗、细粒度撤销重做属于 Compose 事件管线,ArkTS Demo 不复刻;
  • HyphenTextState / SelectionManager / HistoryManager / BlockStyleManager(除 isBlockStyle)随 composeMain 隔离,随工具链对齐后接回;
  • 块样式的原始前缀(- [ ] 等)保留在引擎输出的无语法文本中——上游由 Compose OutputTransformation 在 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.foundation ohos 制品与上游 CMP 版本对齐后,把 composeMain 改回 commonMain、恢复 sample 构建,桥降级为 fallback;
  • 把三个可上游的小改动(剥 @Immutable、isBlockStyle 归位、Hyphen 无头入口)整理成 PR 提给上游——无头引擎入口对 CLI / 服务端用户同样有价值;
  • 基于 SpanManager 区间代数在 ArkTS RichEditor / 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 三条经验

  1. 适配路线按"Compose 依赖深度"选,而不是按名气选:KStore 零 UI、依赖全是 kotlinx——制品线补齐后真编译整库是最优解;hyphen 深度绑定 TextFieldState 新管线——源码集切分、引擎先行、UI 留盘等接回才是正解。切分标准"不 import compose 即留下"一条就够,但必须顺着 import 引用链逐文件核对(纯函数可能困在 Compose 文件里);
  2. 无头语义要显式建模:编辑器引擎带着"用户正在输入"的启发式(光标位置影响识别结果),桥接无头场景必须提供关闭手段(cursor = -1);internal API 跨模块不可见时,新增薄公共入口比放开可见性更可控;
  3. 交叉验证是"引擎没漂"的唯一可信证明: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。

参考文档

Logo

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

更多推荐