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

AutoLinkText 是基于 Compose Multiplatform 的文本自动链接库:给定一段纯文本和一组 TextRule,它用 TextMatcher 规则引擎找出其中的 URL、邮箱、电话(以及自定义字面量、自定义正则),生成带 LinkAnnotation 的 AnnotatedString,入口是 rememberAutoLinkText;Android 侧用 PatternsCompat + libphonenumber 增强识别,JVM 用 libphonenumber,iOS 用 NSDataDetector,js/wasmJs 落回纯 Kotlin 的 BackUpRegex 正则。本次适配与社区早期路线(qrose / haze / PreCompose / voyager 的"零依赖内核提取"与 composeMain 源码隔离)有本质区别:社区 Maven 上已经出现完整的 Compose ohos 制品线(runtime / ui / foundation / material3 / animation 1.12.0-0.1.0-07,全部带 ohosArm64 / ohosX64 Gradle 变体),因此不需要隔离任何源码——整个上游 commonMain(含 MaterialTheme.colorScheme.primary 默认链接色与 AnnotatedString / LinkAnnotation 文本栈)被原样编译成 OpenHarmony KLIB,库侧唯一的新增代码是 ohosMain 下三个 expect actual(照 js 实现的 BackUpRegex 兜底,约 44 行),ArkTS 页面驱动无组合依赖的引擎 API 作为验收宿主。

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

上游仓库: Calvin-LL/AutoLinkText

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

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

AutoLinkText 为 Compose Multiplatform 应用提供"纯文本 ↔ 可点击链接"这一件事的完整答案:TextRuleDefaults.defaultList() 一行拿到网址 / 邮箱 / 电话三条默认规则,TextRule.Url / Clickable / Styleable 三类规则覆盖开链接、回调、纯样式三种语义,TextMatcher.RegexMatcher / StringMatcher / FunctionMatcher 三种匹配器可任意组合,MatchFilter 过滤链(邮箱域名不误判为网址、电话最少位数门槛)与 MatchUrlProvider(裸域名补协议、mailto: / tel: 前缀)构成完整的链接语义。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、Web 共享。OpenHarmony 应用同样需要这套文本识别能力,但上游构建(Kotlin 2.0.21 / Compose 1.7.0 / AGP 8.8.2 / Gradle 8.10.2)不会生成 OpenHarmony 变体。

如果只把示例页面改成 ArkTS,页面可以显示文本,却无法证明公共规则引擎与 Compose 文本栈在 OpenHarmony ARM64 设备上完成编译、链接和执行。适配需要同时验证规则引擎、AnnotatedString 构建管线、Compose 运行时、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 交付链路。

障碍具体问题
目标缺失必须增加 ohosArm64() / ohosX64() 才能生成 OpenHarmony KLIB。
工具链不一致上游 Kotlin 2.0.21 / Gradle 8.10.2 与鸿蒙工具链无法共存于一个 Gradle 构建;ohos 制品线要求 Kotlin 2.4.20-0.1.0-07。
依赖矩阵commonMain 依赖 compose.runtime / compose.ui / compose.material3,每一个都要有 ohos 变体才能解析;jvm 侧 libphonenumber 是纯 JVM 库。
语言边界不同ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 JSON。
组合语义跨不了桥rememberAutoLinkText 是 @Composable,组合(composition)无法跨 C 签名。
交付链路复杂.so、CMake、N-API、HAP、签名和 hdc 都要单独验收。

因此,本项目把边界放在:整个规则引擎与文本管线的 ohos 编译、根构建替换(cmp_qrose 模式)和 C ABI/N-API 桥接层。链接语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和渲染结果。

1.2 库提供的能力

Demo 提供一个真实的文本识别会话,对应桥接的全部入口:

  • 规则开关(rules):webUrl / emailAddress / phoneNumber 三条内置规则按需组合——全部走 TextRuleDefaults 公有默认(含 MatchFilter 过滤链与 MatchUrlProvider 前缀补全);
  • 自定义字面量(strings):TextMatcher.StringMatcher 全文扫字符串;
  • 自定义正则(patterns):TextMatcher.RegexMatcher 编译任意正则(Demo 默认 #\w+ 命中话题标签);
  • 匹配明细:每条匹配的 start / end / text / kind / url,URL 由 Url 规则的 urlProvider 真实解析(裸域名 atomgit.com 被补全为 https://atomgit.com);
  • AnnotatedString 对照:桥在 Kotlin 侧真实构建 AnnotatedString 并回传 annotatedLength 与 annotatedSpans 范围,与 matches 逐条对账;
  • 版本握手:autoLinkTextVersion() 返回上游版本、fork 版本与工具链版本,来自真实 Native 调用而非静态文字。

响应包含:

字段作用
ok操作是否成功,失败时 error 带原文。
text / matchCount回显原文与匹配总数。
matches[]每条匹配的 start / end / text / kind(webUrl/emailAddress/phoneNumber/string/pattern)与 urlProvider 解析出的 url。
annotatedLength真实 AnnotatedString 的长度(应等于原文长度)。
annotatedSpanCount / annotatedSpans[]Styleable 探针经 addStyle 落进 spanStyles 的范围数与范围,与 matches 逐条对照。

引擎侧完整能力(三类规则、三种匹配器、过滤链、URL 前缀、ContextData 体系)全部在上游 commonMain 里原样随编译进入 .so,JVM 侧保留 15 项 jvmTest 交叉验证用例(上游原测试文件零改动),证明 ohos .so 里执行的识别逻辑没有漂移。

1.3 实现适配

维度要求
代码复用autolinktext 全部 11 个 commonMain 文件原样编译(含 material3 默认链接色),不提取、不隔离、不重写;仅新增 3 个 ohos actual 文件(约 44 行,全仓唯一新增库源码)。
平台目标模块声明 ohosArm64 + ohosX64 + jvm(),jvm() 保留上游 jvmMain actual(libphonenumber 电话识别)作交叉验证,库模块出 KLIB,example/nativeApp 出双 ABI 动态库。
构建隔离根构建替换为极简 ohos 构建(cmp_qrose 模式);demoApp(Android/iOS/Web 示例)与 .github CI 留盘不进构建。
桥接稳定使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。
依赖纪律ohos 线换到社区 Compose 制品线(换线不换版主体),jvm 线保持 Maven Central 上游制品;上游测试零改动全绿。
UI 完整单页覆盖全部桥接入口:文本编辑、三类规则开关、字面量/正则输入、检测、分段着色渲染、匹配明细、双通道对照和版本握手。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。rememberAutoLinkText 的组合宿主过不了 C ABI;但引擎本身(规则构建、匹配、annotateString)是无组合依赖的纯函数路径,ArkTS Demo 驱动的正是这条真实路径,着色渲染由 ArkTS 分段完成。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点模块与依赖边界、确认 Compose ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 根构建替换、eazytec Maven 置前、Kotlin 2.4.20-0.1.0-07 钉版、双依赖线
第 3 阶段:桥协议建模     ── 规则/字面量/正则 JSON 契约、双通道内省(matches + annotatedSpans)、错误通道
第 4 阶段:原生桥接       ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主     ── 文本域、规则开关、检测分发、分段着色、明细与版本握手
第 6 阶段:示例与验证     ── HAP、签名、设备安装、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
autolinktext/                          上游规则引擎(进 ohos 构建,jvm() + ohosArm64 + ohosX64)
autolinktext/src/commonMain/           11 个文件原样编译(TextRule/TextMatcher/AnnotatedString 管线)
autolinktext/src/ohosMain/             新增:3 个 expect actual(全仓唯一新增库源码)
autolinktext/src/{android,ios,js,wasmJs}Main/  上游平台 actual,留盘不进构建
demoApp/                               上游 Android/iOS/Web 示例,留盘不进 settings
example/nativeApp/                     Kotlin/Native 动态库(Bridge + MiniJson)
example/harmonyApp/                    DevEco Stage、CMake N-API 和 ArkTS 页面
README.OpenHarmony.md / _CN.md         鸿蒙适配说明(中英双份)
README.upstream.md                     上游原文保存
doc/                                   适配博客与效果图

构建拓扑走 cmp_qrose 模式(根构建即 ohos 构建):settings.gradle.kts 只 include :autolinktext 与 :example:nativeApp,rootProject.name = "ohos_autolinktext"。上游的 vanniktech 发布插件、AGP classpath、robolectric 单测、Android library 打包全部随根构建替换剥离——本 fork 不发 Maven Central。上游根构建、模块构建、版本目录、gradle.properties 共五份原文件以 *.upstream 后缀留档。

1.2 工具链矩阵
项目配置
Kotlin MultiplatformHarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven)
JDK21(org.gradle.java.home 钉住本机 JDK 21)
OpenHarmony 目标ohosArm64 + ohosX64(jvm() 保留作交叉验证)
Gradle9.6.1(wrapper 从上游 8.10.2 升级;2.4.20 线支持 Gradle 9.x)
Composeohos 线 runtime/ui/material3 1.12.0-0.1.0-07(ohosMain 声明);jvm 线 1.11.1 + material3 1.9.0(Maven Central,material3 独立版本线)
DevEco product5.1.0(18) 兼容
DevEco Studio5.1.0 Release
ABIarm64-v8a + x86_64
Bundleorg.terminator.ohos.autolinktext
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
模块commonMain 文件依赖形态处置
autolinktext11compose.runtime + compose.material3 + compose.ui(text),零 AGP整体编译(jvm + 双 ohos)
demoApp/composeApp—AGP application + iOS/js/wasm 工程留盘不进构建

expect 声明全库只有 3 处,全部是 internal expect fun(getMatcherDefaults / getTextRuleDefaults / getMatchUrlProviderDefaults),且 common 接口自带完整默认实现。上游给了 5 类平台 actual——android(PatternsCompat + TelephonyManager + PhoneNumberUtils)、jvm(PhoneNumberUtil + URI)、ios(NSDataDetector + NSURL)、js / wasmJs(空 object 走 BackUpRegex,WebUrl 补 https:// 前缀)。ohos 补第 6 类,与 js 同款——这就是整个 fork 唯一的新增库源码:

// autolinktext/src/ohosMain/kotlin/sh/calvin/autolinktext/TextMatcher.ohos.kt
// ohos actual, same shape as the upstream js/wasmJs actuals: the platform has
// no system text detector exposed to Kotlin/Native, so every default matcher
// runs on the common BackUpRegex engines.
internal actual fun getMatcherDefaults() = object : TextMatcherDefaultsInterface {}

TextRule.ohos.kt 同形;MatchUrlProvider.ohos.kt 照抄 js 的 WebUrl 前缀补全(无协议则拼 https://)。ohos 没有暴露给 Kotlin/Native 的系统级 DataDetector,识别质量与 js/wasmJs 档对齐——这是平台能力的诚实边界,不是适配缺陷。

顺带清理两处上游遗留:commonMain 声明了 kermit 但全源码零引用,移除;上游钉的 languageVersion 1.9 / coreLibrariesVersion 1.9(为兼容旧 wasm 消费者)在 2.4.20 线不再被接受,解除后源码在 2.4 语言级零改动通过。

1.4 页面边界
标题区          AutoLinkText · OpenHARMONY / 副标题(CMP 文本自动识别库 · Kotlin/Native 引擎 · libautolinktext_napi.so)
输入区          多行文本域(预填含 URL/裸域名/邮箱/电话/话题标签的示例)
规则区          网址 / 邮箱 / 电话三个开关 + 自定义字面量输入框 + 自定义正则输入框(默认 #\w+)
操作区          检测按钮
结果区          按匹配范围分段着色渲染(匹配段蓝色 + 下划线,可点击弹窗)
明细区          匹配明细列表([kind] start-end text → url,可点击)+ AnnotatedString 对照行
页脚            版本握手(上游 2.0.2 · fork 2.0.2-ohos.1 · Kotlin · Compose)

页面启动时 aboutToAppear 先调 autoLinkTextVersion() 做握手,再用默认文本自动跑一次检测,首屏即出结果。

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

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

先查社区 Maven(https://maven.eazytec-cloud.com)上有没有 Compose 的 ohos 制品。用 maven-metadata.xml 逐个验证:

org.jetbrains.compose.runtime:runtime    1.12.0-0.1.0-07   ohosArm64/ohosX64 变体 ✔
org.jetbrains.compose.ui:ui              1.12.0-0.1.0-07   ohosArm64/ohosX64 变体 ✔(含 ui-text/graphics/geometry 子模块)
org.jetbrains.compose.foundation:foundation 1.12.0-0.1.0-07 ohosArm64/ohosX64 变体 ✔
org.jetbrains.compose.material3:material3   1.12.0-0.1.0-07 ohosArm64/ohosX64 变体 ✔

三个关键结论:其一,commonMain 的三个 Compose 依赖在 ohos 线全部齐货,material3 的 MaterialTheme.colorScheme.primary 默认链接色可以在 ohos 上真实取值——上游 API 一行不用改;其二,ohos 制品线不发布 *-desktop 子制品,jvm 交叉验证目标不能从这条线解析,必须双线隔离(同 koalaplot-core / compose-imageloader 的分线方式);其三,jvm 线的 material3 走独立版本线,在 CMP 1.11.1 代停在 1.9.0,不能与 runtime/ui 混用同一个 composeJvm 版本号——这是本适配唯一需要拆版本号的地方。

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

社区此前批准的钉法是 HarmonyOS Kotlin 2.2.21-1.0.0(qrose / haze 用的)。但 Compose ohos 线 1.12.0-0.1.0-07 按 Kotlin 2.4.20 编译,2.2.21 读不了 2.4 元数据。工具链版本跟着依赖走:Kotlin 钉 2.4.20-0.1.0-07(与 koalaplot-core / compose-imageloader / Enro 同代)。上游源码是 Kotlin 2.0.21 编的,2.4.20 向后兼容,源码级改动为零;Gradle 方面 2.4.20 线支持 9.x,wrapper 从 8.10.2 升到 9.6.1(连 gradle-wrapper.jar 一起换,旧 wrapper jar 过不了新发行版的校验)。

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_autolinktext"

include(":autolinktext")
include(":example:nativeApp")
2.3 编译真实引擎

autolinktext/build.gradle.kts 收敛为双线纯 KMP 模块,commonMain 不声明任何外部依赖、两线各自解析:

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.kotlin.compose.compiler)
}

kotlin {
    applyDefaultHierarchyTemplate()

    jvm() { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } }   // 交叉验证(jvmMain actual 原样编译)
    ohosArm64()
    ohosX64()

    sourceSets {
        ohosMain.dependencies {          // ohos 线(社区制品)
            implementation(libs.compose.runtime)
            implementation(libs.compose.material3)
            api(libs.compose.ui)
        }
        jvmMain.dependencies {           // jvm 线(Maven Central 上游制品)
            implementation(libs.compose.runtime.jvm)
            implementation(libs.compose.material3.jvm)   // 1.9.0,独立版本线
            api(libs.compose.ui.jvm)
            implementation(libs.libphonenumber)          // 上游 jvm actual 的电话识别
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
        }
    }
}

实测:

./gradlew :autolinktext:compileKotlinOhosArm64 :autolinktext:compileKotlinOhosX64   # BUILD SUCCESSFUL

规则引擎与 AnnotatedString 管线的全部 commonMain 文件一个没动——包括 AnnotatedString+autoLinkText.kt 里的 MaterialTheme.colorScheme.primary 默认参数——只有 ohosMain 三个 actual 是新的。

2.4 测试依赖的处置:ohos 没有测试 runner

ohos 目标没有 Kotlin/Native 测试运行器,处置与已审仓库同款:测试统一在 JVM 跑(jvmTest),根构建全局禁用 ohos 测试任务:

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

这样上游测试文件(commonTest 四个测试类 + jvmTest 的 Ignore.jvm.kt)零改动保留,ohos 侧只承担编译与链接。

第 3 阶段:桥协议建模

3.1 JSON 契约
sh.calvin.autolinktext/**(上游引擎,原样编译)
        ↓ TextRuleDefaults / TextMatcher.apply / urlProvider / annotateString
Bridge.kt(@CName × 2,扁平 JSON 进出)
        ↓ UTF-8 JSON
N-API autoLinkTextVersion() / autoLinkTextDetect()
        ↓
ArkUI Index.ets(单页滚动)

请求与响应:

请求 : {"text":"欢迎体验 ... #OpenHarmony",
        "rules":["webUrl","emailAddress","phoneNumber"],   // 可选,缺省全开
        "strings":["字面量"],                               // 可选
        "patterns":["#\\w+"]}                               // 可选
响应 : {"ok":true,"text":"...","matchCount":6,
        "matches":[{"start":26,"end":65,"text":"https://atomgit.com/...","kind":"webUrl","url":"..."}],
        "annotatedLength":150,
        "annotatedSpanCount":6,
        "annotatedSpans":[{"start":26,"end":65},...]}
       {"ok":false,"error":"unknown rule \"xxx\" ..."}

一个关键的不对称设计:matches 通道只经过规则引擎(rule.textMatcher.apply(text) + TextMatchResult 公有构造 + Url 规则的 urlProvider),annotatedSpans 通道经过完整的 annotateString 构建管线——两条通道独立产出、页面并排对账,任何一条漂移都会在对照行现形。

3.2 载荷与规则构造:纯 public API 驱动真实引擎

桥内规则这样建:

val rule: TextRule<*> = when (kind) {
    "webUrl"       -> TextRuleDefaults.webUrl(NullContextData)        // 含 MatchFilter.WebUrls
    "emailAddress" -> TextRuleDefaults.emailAddress(NullContextData)
    "phoneNumber"  -> TextRuleDefaults.phoneNumber(NullContextData)
    ...
}
"string"  -> TextRule.Url(TextMatcher.StringMatcher(s), urlProvider = MatchUrlProviderDefaults.Verbatim)
"pattern" -> TextRule.Url(TextMatcher.RegexMatcher(Regex(p)), urlProvider = MatchUrlProviderDefaults.Verbatim)

TextRuleDefaults.webUrl(contextData) 这组带 @NotForAndroid(@RequiresOptIn(ERROR))标注的非组合重载是 defaultList() 的同款实现,桥侧文件级 @OptIn(NotForAndroid::class) 后即可调用——不碰任何 @Composable。匹配后逐规则构造 TextMatchResult(rule, text, simple),Url 规则现场调 rule.urlProvider.invoke(match) 拿到真实 URL(裸域名在这里被补全为 https://,见效果图第二条明细)。

3.3 双通道内省:绕开缺失的 linkAnnotations

最初设计想直接内省 AnnotatedString.linkAnnotations,但 ohos 线 ui-text klib 里没有这个属性(klib 符号表 0 命中;spanStyles / getStringAnnotations 都在)。最终方案是两遍 annotateString(公有扩展 Collection<TextRule<T>>.annotateString(text)):

// 第一遍:注入默认样式,镜像 rememberAutoLinkText 的 defaultLinkStyles 注入
//         完整走 addLink(LinkAnnotation) 路径,产出 annotatedLength
val defaultStyles = TextLinkStyles(
    style = SpanStyle(color = Color(0xFF6750A4), textDecoration = TextDecoration.Underline)
)
val styled = styledRules.map { it.second }.annotateString(text)

// 第二遍:同匹配器的 TextRule.Styleable 探针,addStyle 落进可内省的 spanStyles
val probe = rules.map { TextRule.Styleable(it.textMatcher, defaultStyles) as TextRule<Any?> }
    .annotateString(text)
val spans = probe.spanStyles.map { "{\"start\":${it.start},\"end\":${it.end}}" }

验收标准随之确定:matchCount == annotatedSpanCount(每个引擎匹配都落成一条 SpanStyle 范围)、annotatedLength == text.length(AnnotatedString 完整构建)、annotatedSpans 与 matches 的 start/end 逐条一致。

3.4 错误通道

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

@CName("AutoLinkTextDetect")
fun autoLinkTextDetect(request: String): String = try {
    detect(request)
} catch (t: Throwable) {
    val message = t.message ?: t::class.simpleName ?: "unknown error"
    "{\"ok\":false,\"error\":${MiniJson.escape(message)}}"
}

非法 JSON、缺 text、未知规则名、正则编译失败,全部走同一个 ok:false 通道,ArkTS 侧统一渲染。桥内置手写 MiniJson(引号状态机解析器 + 转义器),不引 kotlinx.serialization——同 cmp_qrose 审核模式。

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

4.1 三层桥接
ArkTS
  │ JSON string
  ▼
C++ N-API entry(libautolinktext_napi.so)
  │ const char*
  ▼
Kotlin/Native C ABI(libohosautolinktext.so)
  │ AutoLinkTextDetect / AutoLinkTextVersion
  ▼
UTF-8 JSON + DisposeString

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

4.2 Kotlin/Native 导出函数
@CName("AutoLinkTextDetect")
fun autoLinkTextDetect(request: String): String   // 规则构建 + 匹配 + annotateString 一口进

@CName("AutoLinkTextVersion")
fun autoLinkTextVersion(): String                 // 版本握手(上游/fork/Kotlin/Compose 四元组)
C ABI 符号ArkTS 方法覆盖内核
AutoLinkTextDetectautoLinkTextDetect(req)TextRuleDefaults 三默认 + 字面量/正则规则 + matcher.apply + urlProvider + 两遍 annotateString
AutoLinkTextVersionautoLinkTextVersion()fork 版本握手(真机版本行的数据来源)

example/nativeApp 只在 binaries.sharedLib 开 baseName = "ohosautolinktext"(库模块不开,避免每个模块各带一份 KN 运行时),链接任务 finalizedBy 两个 copyKnSo* 任务,把 libohosautolinktext.so 连同匹配 triple 的 libc++_shared.so 回填 harmonyApp/entry/libs/{arm64-v8a,x86_64}/。

4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
    {"autoLinkTextDetect", nullptr, NapiAutoLinkTextDetect, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
    {"autoLinkTextVersion", nullptr, NapiAutoLinkTextVersion, nullptr, nullptr, nullptr,
     static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};

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

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

add_library(autolinktext_napi SHARED napi_init.cpp)
target_link_libraries(autolinktext_napi PUBLIC
  libace_napi.z.so
  libhilog_ndk.z.so
  ohosautolinktext)

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

export const autoLinkTextDetect: (request: string) => string;
export const autoLinkTextVersion: () => string;

第 5 阶段:ArkUI 宿主

5.1 启动握手与检测分发
aboutToAppear(): void {
  this.loadVersion();   // autoLinkTextVersion() 版本握手:来自真实 Native 调用
  this.detect();        // 默认文本首屏自动检测
}

private detect(): void {
  const request: DetectRequest = new DetectRequest(
    this.inputText, rules, this.splitList(this.literalInput), this.splitList(this.patternInput)
  );
  const raw: string = autoLinkTextDetect(JSON.stringify(request));
  const parsed: DetectResponse = JSON.parse(raw) as DetectResponse;   // 严格模式:interface + as,无 any
  this.apply(parsed);   // @State 刷新:segments 重建 + matches + 对照行
}

空数组字段在请求序列化时省略,桥侧按缺省全开处理。

5.2 分段着色渲染

rebuildSegments() 按 matches 的 start/end 把原文切成普通段与匹配段交替的 Segment[],结果区用 Flex({ wrap: FlexWrap.Wrap }) + ForEach 渲染:普通段黑字,匹配段蓝色 + decoration: TextDecorationStyle.UNDERLINE + onClick 弹出 AlertDialog 显示 kind / 范围 / url。匹配明细区每行 [kind] start-end text → url,同样可点击弹窗;页脚展示版本握手四元组。

5.3 错误边界

ok:false 时错误原文进错误提示行;Native 调用本身抛错(理论不该发生)同样被 try/catch 收进提示区并打 hilog。检测期间按钮置灰防重入。

第 6 阶段:示例与验证

6.1 工程结构
example/
├── nativeApp/
│   ├── build.gradle.kts                      双 ABI sharedLib(baseName=ohosautolinktext)+ cxxShared + copyKnSo*
│   └── src/ohosMain/kotlin/example/nativeapp/
│       └── Bridge.kt                         @CName × 2 + MiniJson(扁平 JSON,继承自 cmp_qrose 审核模式)
└── harmonyApp/
    ├── build-profile.json5                   signingConfigs 留空(签名由 DevEco 自动生成回填)
    ├── AppScope/app.json5                    bundleName org.terminator.ohos.autolinktext、versionName 2.0.2-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/libohosautolinktext_api.h
        ├── src/main/cpp/types/libautolinktext_napi/{index.d.ts, oh-package.json5}
        └── src/main/ets/pages/Index.ets      ArkUI 验收页面
6.2 构建、签名和安装
./gradlew :autolinktext:jvmTest                                    # 15 项交叉验证
./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 中勾选自动生成,证书写入本机,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组,勾选自动签名后由 IDE 回填完整配置)。

hdc list targets -v
hdc -t <设备序列号> install -r \
  entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
  -a EntryAbility -b org.terminator.ohos.autolinktext -m entry

四、完整代码对照

4.1 整体架构

sh.calvin.autolinktext/**(上游引擎原样编译,含 material3 默认色)
    │ TextRule / TextMatcher / MatchFilter / MatchUrlProvider
Bridge.kt + MiniJson
    │ AutoLinkTextDetect / AutoLinkTextVersion
libautolinktext_napi.so
    │ autoLinkTextDetect / autoLinkTextVersion
Index.ets
    │ JSON parse + 分段重建 + @State 更新
    ▼
ArkUI 设备页面(单页滚动)

4.2 文件清单

文件职责
settings.gradle.kts极简 ohos 构建、eazytec Maven 置前、ohos_autolinktext、demoApp 移出
gradle/libs.versions.tomlKotlin 2.4.20-0.1.0-07 + ohos/jvm 双 Compose 线钉版(material3 jvm 1.9.0 独立版本线)
build.gradle.kts(根)版本 2.0.2-ohos.1、全局禁用 Ohos*Test* 任务
autolinktext/build.gradle.kts双线 KMP:jvm() + ohosArm64 + ohosX64,commonMain 零外部依赖
autolinktext/src/ohosMain/...唯一新增库源码(3 个 actual,约 44 行)
example/nativeApp/build.gradle.kts双 ABI sharedLib、cxxShared 定位 libc++、copyKnSo* 回填
nativeApp/.../Bridge.kt2 个 C ABI、双通道内省、MiniJson、错误通道
harmonyApp/.../cpp/napi_init.cppN-API 导出、参数检查、DisposeString
harmonyApp/.../cpp/CMakeLists.txtIMPORTED 引入 KN so、$ORIGIN rpath、POST_BUILD 三件套
harmonyApp/.../ets/pages/Index.etsArkUI 验收页面
README.OpenHarmony_CN.md鸿蒙适配主文档(22 节)

4.3 关键 API 对照

层次API作用
引擎TextRuleDefaults.webUrl/emailAddress/phoneNumber(ctx)三条默认规则(含过滤链与前缀补全),桥直接调用
引擎TextMatcher.apply(text)逐规则匹配,返回 SimpleTextMatchResult 列表
引擎TextMatchResult(rule, text, match)公有构造,携带 matchedText 与范围
引擎TextRule.Url.urlProviderURL 解析(裸域名补 https://、mailto:、tel:)
引擎Collection<TextRule<T>>.annotateString(text)公有扩展:构建带 LinkAnnotation 的 AnnotatedString
引擎rememberAutoLinkText@Composable 入口(组合宿主内使用,不跨 C ABI)
NativeAutoLinkTextDetect / AutoLinkTextVersionC ABI JSON 导出
N-APIautoLinkTextDetect(req) / autoLinkTextVersion()向 ArkTS 暴露能力
ArkTSdetect()触发检测并重建分段 UI

4.4 ArkTS 与 Kotlin 的边界

const raw: string = autoLinkTextDetect(JSON.stringify({
  text: '欢迎体验 AutoLinkText 演示!项目主页 https://atomgit.com/oh-tpc/AutoLinkText ...',
  rules: ['webUrl', 'emailAddress', 'phoneNumber'],
  patterns: ['#\\w+']
}));
const res = JSON.parse(raw) as DetectResponse;
// res.ok === true;res.matches / res.annotatedSpans 驱动分段着色与对照
for ((kind, rule) in rules) {
    for (simple in rule.textMatcher.apply(text)) {
        val match = TextMatchResult(rule, text, simple)
        val url = (rule as? TextRule.Url)?.urlProvider?.invoke(match)
        matches.add(Out(match.start, match.endExclusive, match.matchedText, kind, url))
    }
}

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

五、关键决策说明

决策 1:真编译整库而不是 composeMain 隔离

qrose 时代必须把 Compose Painter 挪进不编译的 composeMain,因为 ohos 上没有 Compose 制品。本次适配先验证制品线(runtime / ui / material3 全部存在 ohos 变体),障碍已消失。AutoLinkText 的整个 API 面都站在 AnnotatedString / LinkAnnotation / MaterialTheme 上——隔离等于把库阉割成空壳;全量编译让默认链接色、TextLinkStyles、LinkAnnotation 构建管线全部是上游原件,ohos klib 与 Android/iOS 侧执行同一份代码。

决策 2:工具链升到 Kotlin 2.4.20-0.1.0-07,Gradle 升到 9.6.1,双依赖线隔离

Compose ohos 线 1.12.0-0.1.0-07 基于 Kotlin 2.4.20 构建,工具链跟着依赖走。ohos 制品线不发布 *-desktop 子制品,jvm 交叉验证目标必须从 Maven Central 解析上游制品——于是 commonMain 零外部依赖、ohosMain 与 jvmMain 各自声明依赖线(同 koalaplot-core / compose-imageloader 的分线纪律)。唯一拆版本号的坐标是 material3:jvm 线在 CMP 1.11.1 代停在独立版本线的 1.9.0。

决策 3:根构建替换(cmp_qrose 模式),demoApp 与 CI 留盘

上游根构建(AGP application/library、compose 发布链、vanniktech 发布、robolectric)对鸿蒙没有保留价值,整个替换为极简 ohos 构建,五份上游构建文件以 *.upstream 留档。demoApp(Android/iOS/Web 示例)与 .github CI 是上游遗留,绑定旧工具链,留盘不进 settings.gradle.kts——与已审仓库处置一致。

决策 4:双通道桥协议替代 linkAnnotations 内省

ohos 线 ui-text klib 未暴露 AnnotatedString.linkAnnotations 属性(klib 符号表 0 命中)。桥的答案是两遍 annotateString:第一遍注入默认样式完整走 addLink(LinkAnnotation) 路径(镜像 rememberAutoLinkText 的 defaultLinkStyles 注入),产出 annotatedLength;第二遍用同匹配器的 TextRule.Styleable 探针把样式落进可内省的 spanStyles,产出 annotatedSpans。识别语义(matches)与构建管线(annotatedSpans)两条通道独立对账,比单一列表更强的验证结构。

决策 5:jvmTest 交叉验证 + 测试零改动

引擎逻辑在 commonMain,JVM 与 ohos 跑同一份代码;15 项 jvmTest 跑的就是上游原测试文件(含 jvmMain 的 libphonenumber 电话识别 actual),是"内核没漂"的闸门。ohos 没有测试 runner,处置不是改测试,而是"测试统一 JVM + 根构建禁用 Ohos*Test*"——测试文件一个字节不动,交叉验证的有效性才成立。

六、测试与验证

6.1 测试环境

本次验证使用 Windows(x64)、JDK 21、DevEco Studio 5.1.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与 OpenHarmony 设备(arm64 真机 / x86_64 模拟器双 ABI 覆盖)。

6.2 JVM 交叉验证

./gradlew :autolinktext:jvmTest

15 项用例全部通过(上游原测试文件,零改动):

测试类用例数钉住什么
TextMatcherTest7三种匹配器语义与过滤链
TextRuleTest4规则默认值、样式注入、annotateString 产物
SimpleTextMatchResultTest3范围语义(slice / OpenEndRange)
TextMatchResultTest1匹配结果构造

依赖换线没有 API 漂移:Compose 从上游 1.7.0 线换到 ohos 1.12.0-0.1.0-07 线后上游测试照跑全绿——commonMain 源码零改动是"换线不换版"的直接证据。

6.3 产物与安装验证

:autolinktext:compileKotlinOhosArm64 / OhosX64: BUILD SUCCESSFUL
:example:nativeApp:linkDebugSharedOhosArm64 / OhosX64: BUILD SUCCESSFUL(全链路 5m02s)
copyKnSoArm64 / copyKnSoX64: 三件套回填 entry/libs(gitignore 覆盖)
llvm-nm -D: AutoLinkTextDetect / AutoLinkTextVersion / libohosautolinktext_symbols 均为全局 T 符号(双 ABI)

libohosautolinktext.so(debugShared 未 strip,含 Kotlin/Native 运行时与完整 Compose 文本栈)arm64 约 28.1 MB、x86_64 约 26.9 MB,libc++_shared.so 约 1.85 / 1.73 MB。HAP 侧经 DevEco 自动签名后安装启动成功(org.terminator.ohos.autolinktext),运行效果见下节。

6.4 功能用例

  1. 启动后页脚出现版本握手四元组(上游 2.0.2 · fork 2.0.2-ohos.1 · Kotlin 2.4.20-0.1.0-07 · Compose 1.12.0-0.1.0-07),来自真实 Native 调用;
  2. 首屏自动检测默认文本,matchCount=6:两条 webUrl(完整 URL 与裸域名)、一条 emailAddress、一条 phoneNumber、两条 pattern(#OpenHarmony / #Compose 话题);
  3. 裸域名 atomgit.com 的明细行为 [网址] 71-82 atomgit.com → https://atomgit.com——MatchUrlProvider.WebUrl 的 ohos actual 现场补全协议前缀;
  4. 下标体系对齐:明细行 26-65 与结果区着色分段位置一致,Kotlin String 与 ArkTS string 同为 UTF-16 编码单元,中文混排不错位;
  5. 结果区匹配段蓝色 + 下划线渲染,点击弹窗显示 kind / 范围 / url;
  6. 关闭"电话"开关重测,电话明细与着色段同步消失;
  7. 自定义字面量输入 鸿蒙 后重测,文中所有"鸿蒙"落成 string 规则匹配;
  8. 自定义正则输入非法串(如 [),返回 ok:false 且错误原文进提示行,不崩页面。

6.5 验证结论

规则引擎、Compose 文本栈、material3 默认色取值、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。应用在 OpenHarmony 设备上成功安装并进入前台,页面展示了真实的识别会话:规则开关、双通道对照与版本握手闭环(见下节效果图)。

七、运行效果

7.1 设备截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/AutoLinkText.git
cd AutoLinkText
export JAVA_HOME="/path/to/jdk-21"
./gradlew :autolinktext:jvmTest                                              # 15 项交叉验证
./gradlew :autolinktext:compileKotlinOhosArm64 :autolinktext:compileKotlinOhosX64   # klib
./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.autolinktext -m entry
hdc shell hilog | grep AutoLinkText

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让 :autolinktext 真正编译成 ohosArm64 / ohosX64 产物;
  2. settings.gradle.kts 里 eazytec 双仓必须排在 pluginManagement 与 dependencyResolutionManagement 最前,否则配置期直接 Unresolved reference: ohosArm64;
  3. ohos 制品线不发布 *-desktop 子制品,jvm 交叉验证目标不能与 ohos 目标共用一条依赖线——commonMain 必须零外部依赖、两线各自声明(同 koalaplot-core / compose-imageloader);
  4. material3 是独立版本线:ohos 线跟 compose 同版本号 1.12.0-0.1.0-07,jvm 线在 CMP 1.11.1 代停在 1.9.0——一个 composeJvm 版本号盖两个坐标会解析失败;
  5. ohos 线 ui-text klib 没有 AnnotatedString.linkAnnotations 属性(klib 符号表 0 命中,spanStyles / getStringAnnotations 都在)——内省缺口用 Styleable 探针的 spanStyles 通道绕行,文档与桥协议同步写明;
  6. 上游钉的 languageVersion 1.9 / coreLibrariesVersion 1.9 在 Kotlin 2.4.20 线不再被接受,直接解除即可,源码在 2.4 语言级零改动通过;
  7. TextRuleDefaults.webUrl(contextData) 这组重载带 @NotForAndroid(@RequiresOptIn(ERROR))标注,桥侧必须文件级 @OptIn(NotForAndroid::class) 才能调用非组合版本;
  8. ohos 没有测试 runner:测试统一 JVM 跑 + 根构建全局禁用 Ohos*Test* 任务,上游测试文件零改动;
  9. ArkTS 严格模式禁用 any:JSON 解析全部 interface 定义 + JSON.parse(...) as T,请求对象用 class 构造以省略空数组字段;
  10. Gradle wrapper 从 8.10.2 升 9.6.1 时 gradle-wrapper.jar 必须连同替换,旧 jar 过不了新发行版的校验;
  11. HAP 缺当前 ABI 的 so 会报安装失败 9568347,abiFilters 必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可;
  12. KN 返回字符串必须 DisposeString 归还运行时,N-API 层不能 free();签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持 signingConfigs: [],证书绝不进入 AtomGit。

8.2 已知问题

  • rememberAutoLinkText 的组合 API 不跨 C ABI:组合(composition)需要宿主运行时,桥驱动的是同引擎的无组合路径;ArkTS 侧以分段渲染复刻着色语义;
  • ohos 无系统级 DataDetector 暴露给 Kotlin/Native:识别走 BackUpRegex 纯 Kotlin 正则(与上游 js/wasmJs 同策略),电话识别弱于 jvm/android 的 libphonenumber(无区域号推断);
  • linkAnnotations 内省缺口:ohos 线 ui-text klib 未暴露该属性,annotatedSpans 是 Styleable 探针的替代证明(addLink 路径仅以 annotatedLength 间接验证);
  • android / js / wasmJs / apple 目标剥离(源文件留在树里),robolectric 单测一并剥离,本 fork 不发 Maven Central;
  • .github CI 为上游遗留(构建 demoApp APK/Jar),demoApp 已移出构建,流水线待 ohos 化;
  • HAP 安装必须使用与 bundle name org.terminator.ohos.autolinktext 匹配的签名。

8.3 未来优化方向

  • 评估 ohos 侧接入系统文本识别能力(如 ArkTS 侧 DataDetector 类能力经 NAPI 回表)增强电话/地址识别;
  • 关注 ArkUI 富文本组件演进(如 ClickSpan 类能力),用原生链接渲染替代手写分段;
  • 评估 .har 形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套;
  • 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建,替换上游遗留流水线;
  • 补充模拟器(x86_64)与其他设备 ABI 的验收矩阵和自动化截图脚本。

九、总结

9.1 核心难点回顾

上游 autolinktext(全部 commonMain,含 material3 默认色)
    → 验证 ohos Compose 制品线(runtime / ui / material3)
    → 工具链对齐 Kotlin 2.4.20-0.1.0-07(Gradle 9.6.1 升级)
    → 根构建替换(cmp_qrose 模式,双依赖线隔离)
    → ohosArm64 / ohosX64 KLIB + 仅 3 个 actual(约 44 行)
    → C ABI(@CName × 2)+ 双通道 JSON 协议
    → C++ N-API(DisposeString 纪律)
    → ArkTS JSON 解析 + 分段着色
    → ArkUI 设备单页验收(规则开关 / 明细 / 双通道对照 / 版本握手)

9.2 封装层次

  • 引擎层:上游原版 TextRule / TextMatcher / MatchFilter / MatchUrlProvider 与 annotateString 管线;
  • Native 层:生成双 ABI 动态库并通过 2 个 C ABI 输出 JSON;
  • N-API 层:完成参数检查、字符串转换和 Native 内存释放;
  • ArkTS 层:管理页面状态、JSON 校验、检测分发、分段渲染与错误文本;
  • DevEco 层:完成 CMake、HAP、签名、安装和设备运行。

9.3 三条经验

  1. 先验证生态制品线再定适配路线:Compose ohos 线补齐后,UI 库的正确答案从"隔离 Compose 代码"变成"全量编译 + 补平台 actual"——AutoLinkText 全库只有 3 个 expect,适配面是这一批 Compose UI 库里最小的;路线选择必须基于当天的制品可达性,而不是历史结论;
  2. API 缺口用运行时证据绕行而不是硬等:linkAnnotations 在 ohos klib 缺失,Styleable 探针的 spanStyles 通道提供了同等强度的构建管线证明——协议里两条通道独立对账,验证结构反而更强;
  3. 交叉验证是"内核没漂"的唯一可信证明:15 项 jvmTest 跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;文本识别库的验收重点天然落在"识别结果与渲染分段逐条对得上"这条对照闭环上。

9.4 适配成果

当前适配已完成:

  • AutoLinkText 2.0.2 真实引擎的双 ABI Kotlin/Native 编译(上游 commonMain 11 个文件零改动,含 material3 默认链接色与 AnnotatedString / LinkAnnotation 文本栈;仅 3 个 ohos actual 约 44 行);
  • HarmonyOS Compose 制品线在文本链接库上的完整落地(runtime / ui / material3 1.12.0-0.1.0-07,jvm 交叉验证线 1.11.1 + material3 1.9.0);
  • 2 个 C ABI 符号和 C++ N-API 包装层(llvm-nm 验证全局 T 符号,双 ABI);
  • DevEco Stage ArkUI 单页验收页面(规则开关、自定义字面量/正则、分段着色、匹配明细、双通道对照、版本握手);
  • 15 项 JVM 交叉验证全绿(上游原测试文件)+ 双 ABI 动态库链接(libohosautolinktext.so debug 版 arm64 约 28.1 MB / x86_64 约 26.9 MB)、签名、设备安装和运行验证;
  • AtomGit 项目文档、OpenHarmony README(中英双份)、适配博客与设备效果图。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

Logo

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

更多推荐