开源鸿蒙平台 KMP 三方库「AutoLinkText」适配全流程
本文记录
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/ohosX64Gradle 变体),因此不需要隔离任何源码——整个上游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 Multiplatform | HarmonyOS Kotlin 2.4.20-0.1.0-07(eazytec Maven) |
| JDK | 21(org.gradle.java.home 钉住本机 JDK 21) |
| OpenHarmony 目标 | ohosArm64 + ohosX64(jvm() 保留作交叉验证) |
| Gradle | 9.6.1(wrapper 从上游 8.10.2 升级;2.4.20 线支持 Gradle 9.x) |
| Compose | ohos 线 runtime/ui/material3 1.12.0-0.1.0-07(ohosMain 声明);jvm 线 1.11.1 + material3 1.9.0(Maven Central,material3 独立版本线) |
| DevEco product | 5.1.0(18) 兼容 |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.autolinktext |
export JAVA_HOME="/path/to/jdk-21"
java -version
1.3 源码依赖度盘点
| 模块 | commonMain 文件 | 依赖形态 | 处置 |
|---|---|---|---|
autolinktext | 11 | compose.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 方法 | 覆盖内核 |
|---|---|---|
AutoLinkTextDetect | autoLinkTextDetect(req) | TextRuleDefaults 三默认 + 字面量/正则规则 + matcher.apply + urlProvider + 两遍 annotateString |
AutoLinkTextVersion | autoLinkTextVersion() | 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.toml | Kotlin 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.kt | 2 个 C ABI、双通道内省、MiniJson、错误通道 |
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 | 鸿蒙适配主文档(22 节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 引擎 | TextRuleDefaults.webUrl/emailAddress/phoneNumber(ctx) | 三条默认规则(含过滤链与前缀补全),桥直接调用 |
| 引擎 | TextMatcher.apply(text) | 逐规则匹配,返回 SimpleTextMatchResult 列表 |
| 引擎 | TextMatchResult(rule, text, match) | 公有构造,携带 matchedText 与范围 |
| 引擎 | TextRule.Url.urlProvider | URL 解析(裸域名补 https://、mailto:、tel:) |
| 引擎 | Collection<TextRule<T>>.annotateString(text) | 公有扩展:构建带 LinkAnnotation 的 AnnotatedString |
| 引擎 | rememberAutoLinkText | @Composable 入口(组合宿主内使用,不跨 C ABI) |
| Native | AutoLinkTextDetect / AutoLinkTextVersion | C ABI JSON 导出 |
| N-API | autoLinkTextDetect(req) / autoLinkTextVersion() | 向 ArkTS 暴露能力 |
| ArkTS | detect() | 触发检测并重建分段 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 项用例全部通过(上游原测试文件,零改动):
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
TextMatcherTest | 7 | 三种匹配器语义与过滤链 |
TextRuleTest | 4 | 规则默认值、样式注入、annotateString 产物 |
SimpleTextMatchResultTest | 3 | 范围语义(slice / OpenEndRange) |
TextMatchResultTest | 1 | 匹配结果构造 |
依赖换线没有 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 功能用例
- 启动后页脚出现版本握手四元组(上游
2.0.2· fork2.0.2-ohos.1· Kotlin2.4.20-0.1.0-07· Compose1.12.0-0.1.0-07),来自真实 Native 调用; - 首屏自动检测默认文本,
matchCount=6:两条 webUrl(完整 URL 与裸域名)、一条 emailAddress、一条 phoneNumber、两条 pattern(#OpenHarmony/#Compose话题); - 裸域名
atomgit.com的明细行为[网址] 71-82 atomgit.com → https://atomgit.com——MatchUrlProvider.WebUrl的 ohos actual 现场补全协议前缀; - 下标体系对齐:明细行
26-65与结果区着色分段位置一致,KotlinString与 ArkTS string 同为 UTF-16 编码单元,中文混排不错位; - 结果区匹配段蓝色 + 下划线渲染,点击弹窗显示 kind / 范围 / url;
- 关闭"电话"开关重测,电话明细与着色段同步消失;
- 自定义字面量输入
鸿蒙后重测,文中所有"鸿蒙"落成 string 规则匹配; - 自定义正则输入非法串(如
[),返回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 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让
:autolinktext真正编译成ohosArm64/ohosX64产物; settings.gradle.kts里 eazytec 双仓必须排在pluginManagement与dependencyResolutionManagement最前,否则配置期直接Unresolved reference: ohosArm64;- ohos 制品线不发布
*-desktop子制品,jvm 交叉验证目标不能与 ohos 目标共用一条依赖线——commonMain必须零外部依赖、两线各自声明(同 koalaplot-core / compose-imageloader); - material3 是独立版本线:ohos 线跟 compose 同版本号
1.12.0-0.1.0-07,jvm 线在 CMP 1.11.1 代停在1.9.0——一个composeJvm版本号盖两个坐标会解析失败; - ohos 线
ui-textklib 没有AnnotatedString.linkAnnotations属性(klib 符号表 0 命中,spanStyles/getStringAnnotations都在)——内省缺口用 Styleable 探针的spanStyles通道绕行,文档与桥协议同步写明; - 上游钉的
languageVersion 1.9/coreLibrariesVersion 1.9在 Kotlin 2.4.20 线不再被接受,直接解除即可,源码在 2.4 语言级零改动通过; TextRuleDefaults.webUrl(contextData)这组重载带@NotForAndroid(@RequiresOptIn(ERROR))标注,桥侧必须文件级@OptIn(NotForAndroid::class)才能调用非组合版本;- ohos 没有测试 runner:测试统一 JVM 跑 + 根构建全局禁用
Ohos*Test*任务,上游测试文件零改动; - ArkTS 严格模式禁用
any:JSON 解析全部interface定义 +JSON.parse(...) as T,请求对象用 class 构造以省略空数组字段; - Gradle wrapper 从 8.10.2 升 9.6.1 时
gradle-wrapper.jar必须连同替换,旧 jar 过不了新发行版的校验; - HAP 缺当前 ABI 的 so 会报安装失败
9568347,abiFilters必须双 ABI,每个 ABI 三件套(NAPI so / KN so / libc++)缺一不可; - 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-textklib 未暴露该属性,annotatedSpans是 Styleable 探针的替代证明(addLink路径仅以annotatedLength间接验证);- android / js / wasmJs / apple 目标剥离(源文件留在树里),robolectric 单测一并剥离,本 fork 不发 Maven Central;
.githubCI 为上游遗留(构建 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 三条经验
- 先验证生态制品线再定适配路线:Compose ohos 线补齐后,UI 库的正确答案从"隔离 Compose 代码"变成"全量编译 + 补平台 actual"——AutoLinkText 全库只有 3 个 expect,适配面是这一批 Compose UI 库里最小的;路线选择必须基于当天的制品可达性,而不是历史结论;
- API 缺口用运行时证据绕行而不是硬等:
linkAnnotations在 ohos klib 缺失,Styleable 探针的spanStyles通道提供了同等强度的构建管线证明——协议里两条通道独立对账,验证结构反而更强; - 交叉验证是"内核没漂"的唯一可信证明:15 项
jvmTest跑的是上游原测试文件,测试零改动才让"换线不换版"有说服力;文本识别库的验收重点天然落在"识别结果与渲染分段逐条对得上"这条对照闭环上。
9.4 适配成果
当前适配已完成:
- AutoLinkText
2.0.2真实引擎的双 ABI Kotlin/Native 编译(上游commonMain11 个文件零改动,含 material3 默认链接色与AnnotatedString/LinkAnnotation文本栈;仅 3 个 ohos actual 约 44 行); - HarmonyOS Compose 制品线在文本链接库上的完整落地(runtime / ui / material3
1.12.0-0.1.0-07,jvm 交叉验证线1.11.1+ material31.9.0); - 2 个 C ABI 符号和 C++ N-API 包装层(
llvm-nm验证全局 T 符号,双 ABI); - DevEco Stage ArkUI 单页验收页面(规则开关、自定义字面量/正则、分段着色、匹配明细、双通道对照、版本握手);
- 15 项 JVM 交叉验证全绿(上游原测试文件)+ 双 ABI 动态库链接(
libohosautolinktext.sodebug 版 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。
参考文档
更多推荐

所有评论(0)