开源鸿蒙平台 KMP 三方库「Ktorfit」适配全流程
本文记录
Ktorfit接入 OpenHarmony 的完整过程,覆盖 KSP 编译期代码生成在鸿蒙线的落地、Ktor ohos 制品线选型、真实 HTTP 客户端编译、ohosArm64目标、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建和真机验收(验收应用 11/11 PASS + 天气 Demo 真实数据)。
Ktorfit是 Retrofit 风格的 Kotlin Multiplatform 声明式 HTTP 客户端:在接口上标注@GET/@POST/@PUT/@DELETE/@PATCH,用@Path/@Query/@Header/@Body/@Field/@FieldMap/@Headers/@FormUrlEncoded描述请求,KSP 在编译期生成实现类,运行时把调用转交给 KtorHttpClient执行,并通过转换器工厂支持Call<T>/Response<T>/Flow<T>/suspend等返回形态。本次适配与纯运行时库不同:Ktorfit是"编译器 + 运行时"复合形态(注解模块、KSP 处理器、核心运行时、三个转换器、Gradle 插件),适配面不止"库能编成 KLIB",还必须证明 KSP 生成的代码在 ohosArm64 上能编译、能链接、能发起真实网络请求。适配采用与已审核仓库一致的互补模式(-PopenharmonyOnly=true只配置 JVM/Android/ohosArm64,省略时保留上游全部目标),社区 Ktor 构建3.3.3-1.1.0-04提供全部 ohosArm64 klib,CIO 引擎作为 ohos 默认引擎随库链接,ArkUI 页面作为验收宿主。
项目地址: AtomGit/oh-tpc/Ktorfit.git
上游仓库: Foso/Ktorfit
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
Ktorfit 为 Kotlin Multiplatform 应用提供"接口即 HTTP 客户端"的完整答案:Ktorfit.Builder().baseUrl(...).httpClient(...).converterFactories(...) 一行建客户端,ktorfit.createXxxApi() 拿到生成的实现,请求组装(路径替换、查询编码、表单序列化、请求头注入)全部由生成代码与运行时完成,业务代码只声明接口。KMP 项目把这些能力放在 commonMain,由 Android、iOS、桌面、JS 共享。OpenHarmony 应用同样需要这套声明式网络层,但上游构建(全平台目标 + AGP + BCV 校验链)不会生成 OpenHarmony 变体,ohos 上也没有可直接消费的制品。
如果只把示例页面改成 ArkTS,页面可以显示按钮,却无法证明三件事:KSP 处理器在鸿蒙工具链上正常生成代码、生成的代码能链接成 ohosArm64 动态库、运行时能在真实设备上发起 HTTP 请求。适配需要同时验证编译期(处理器)与运行期(引擎)两侧。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 必须增加 ohosArm64() 才能生成 OpenHarmony KLIB。 |
| 工具链不一致 | 上游全目标构建含 wasm/ios/watchos 等 20 余个目标;鸿蒙工具链需要 Kotlin 2.2.21-1.0.0 + Gradle 8.14.3 + JDK 21,与 BCV、新版发布插件存在互斥。 |
| 依赖矩阵 | 运行时依赖 ktor-client-core 系列与 kotlinx-coroutines,每个都要有 ohos 变体才能解析;社区 Ktor 构建没有 js/wasm 变体,js 源集必须回退上游版本。 |
| KSP 接线 | KSP2 下 commonMain 接口的实现类由元数据任务生成,目标级任务默认不消费它,必须手工三件套接线。 |
| 语言边界不同 | ArkTS 不能直接持有 Kotlin 对象,需要 C ABI、C++ N-API 和 UTF-8 JSON。 |
| 网络语义跨不了桥 | Ktor CIO 引擎在 Kotlin/Native 上不支持 TLS;挂起函数过不了 C 签名。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在:六个库模块(annotations、lib-core、lib、flow/call/response 转换器)的 ohos 编译、示例工程的 KSP 端到端验证和 C ABI/N-API 桥接层。请求组装语义完全由上游原版实现负责,ArkTS 只负责调用 Native、解析 JSON 和展示结果。
1.2 库提供的能力
验收应用提供 11 项检查,覆盖桥接的全部入口:
- 第 1~10 项(进程内库场景,MockEngine):一个
AcceptanceApi接口声明 12 个方法——@GET基本 GET、@Path路径替换、@Query查询参数、@Body+@Header请求体与请求头、@PUT/@DELETE/@PATCH全动词、@FormUrlEncoded+@Field+@FieldMap表单、@Headers静态请求头,以及Call<String>/Response<String>/Flow<String>三种转换器返回形态。接口在 commonMain 声明、KSP 生成实现,在设备上用 MockEngine 执行并核对请求行、路径、查询串、请求头与请求体; - 第 11 项(CIO 引擎真实网络请求):通过 ohos 默认的 CIO 引擎向
http://www.baidu.com/发起真实请求,校验状态码 200——证明链接进 so 的网络引擎在设备上真正可用,而不只是 MockEngine 自说自话。
响应为一条 JSON 数组:
| 字段 | 作用 |
|---|---|
name | 检查项名称(中文双语)。 |
passed | 是否通过。 |
detail | 通过时是断言摘要(如实际请求行、编码后的查询串),失败时是错误原文。 |
天气 Demo 另外提供一个真实的消费场景:@GET("api/weather/city/{code}") + @Path 请求公开天气服务,四城市切换(北京 / 上海 / 广州 / 深圳)、温度 / 空气质量 / 感冒提示 / 四日预报展示,页面同时明示"CIO 引擎 native 无 TLS,仅明文 HTTP"的限制。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 六个库模块的全部 commonMain 文件原样编译,不提取、不重写;适配改动集中在构建脚本(目标声明、依赖换线、BCV 互补),库源码零修改、零新增。 |
| 平台目标 | 互补模式:-PopenharmonyOnly=true 时 jvm() + android + ohosArm64(),省略时保留上游全部目标(含 wasm、ios、watchos、linux、mingw 等),上游 CI 行为不变。 |
| 构建隔离 | BCV(binary-compatibility-validator)与 ohosArm64 注册互斥(目标注册期 NPE),只在非 ohos 模式应用;发布插件降级到与 Gradle 8.14.3 内嵌 Kotlin 兼容的版本。 |
| 桥接稳定 | 使用 2 个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址;异常永远不跨 FFI。 |
| 依赖纪律 | 版本目录整体切到社区 Ktor 线 3.3.3-1.1.0-04;js/wasm 源集显式回退上游 3.5.0;协程在 ohos 源集固定 1.10.2-1.0.0。 |
| UI 完整 | 验收页覆盖 11 项检查 + 重新运行按钮;Demo 页覆盖四城市天气 + HTTPS 限制说明卡。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。
Ktorfit本身不碰 UI,适配面集中在编译期代码生成与运行时网络栈;ArkTS 页面只是把桥暴露的验收结果画出来。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点模块与依赖边界、确认 Ktor/coroutines ohos 制品线、确定交付物
第 2 阶段:目标与依赖打通 ── 互补模式改造、eazytec Maven 置前、Kotlin 2.2.21-1.0.0 钉版、BCV 互斥处置、vanniktech 降版
第 3 阶段:桥协议建模 ── AcceptanceApi 12 方法 + NetworkApi、MockEngine 场景契约、检查结果 JSON
第 4 阶段:原生桥接 ── @CName C ABI、C++ N-API、参数校验和内存释放
第 5 阶段:ArkUI 宿主 ── 验收 11 项页面 + 天气 Demo 页面(城市切换、预报列表、限制说明)
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机 11/11 验收和 Demo 效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
ktorfit-annotations/ 注解模块(@GET/@POST/@Path/... 进 ohos 构建)
ktorfit-lib-core/ 核心运行时(Ktorfit.Builder、请求构建、内置转换,进 ohos 构建)
ktorfit-lib/ 面向用户的运行时入口(ohosArm64Main 声明 CIO 引擎依赖)
ktorfit-converters/{call,flow,response}/ Call/Response/Flow 转换器(进 ohos 构建)
ktorfit-ksp/ KSP 处理器(JVM 编译,产物消费方覆盖 ohos)
ktorfit-gradle-plugin/ ktorfit-compiler-plugin/ 上游 Gradle/K1 插件(不进 ohos 构建)
example/OpenHarmonyExample/ 验收工程(shared + nativeApp + ohosApp,11 项检查)
example/OpenHarmonyDemo/ 天气 Demo 工程(shared + nativeApp + ohosApp)
example/{MultiplatformExample,AndroidOnlyExample}/ 上游示例,留盘不进 ohos 构建
scripts/ build-openharmony.sh / prepare-signing-project.py / build-hap.sh / check-native-deps.py
README.OpenHarmony.md / _CN.md 鸿蒙适配说明(中英双份)
docs/openharmony/VALIDATION.md 可复现验证记录
构建拓扑走互补模式:根构建与 build-logic 约定插件保持上游原状,只在 -PopenharmonyOnly=true 时收敛目标集合。上游的其他示例工程(MultiplatformExample、AndroidOnlyExample)原样留盘,不影响 ohos 构建。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | HarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven) |
| KSP | 2.2.21-2.0.5(与 Kotlin 版本前缀对齐) |
| JDK | 21 |
| OpenHarmony 目标 | ohosArm64(jvm + android 始终保留) |
| Gradle | 8.14.3 |
| Ktor | 社区构建 3.3.3-1.1.0-04(core/cio/mock/okhttp/darwin/content-negotiation 均含 ohosArm64 klib);js/wasm 回退上游 3.5.0 |
| kotlinx-coroutines | 普通平台用常规线;ohos 源集 strictly 1.10.2-1.0.0 |
| 发布插件 | gradle-maven-publish-plugin 0.34.0(从 0.36.0 降级,见 8.1) |
| BCV | 0.18.1,仅非 ohos 模式应用 |
| DevEco product | 6.0.0(20) 兼容 |
| DevEco Studio | 6.0.0 Release |
| ABI | arm64-v8a |
| Bundle | com.example.ktorfit(验收)/ com.example.ktorfitdemo(Demo) |
export JAVA_HOME="/path/to/jdk-21"
java -version
./gradlew --version
1.3 依赖制品线盘点
| 依赖 | ohos 变体 | 处置 |
|---|---|---|
io.ktor:ktor-client-core | 3.3.3-1.1.0-04 有 ohosArm64 klib | commonMain 统一换线 |
io.ktor:ktor-client-cio | 同上 | ktorfit-lib ohosArm64Main 声明(默认引擎) |
io.ktor:ktor-client-mock | 同上 | 验收场景使用 |
io.ktor:ktor-client-{js,...}(js/wasm) | 社区线无 js/wasm 变体 | jsMain/wasmJsMain 显式回退上游 3.5.0 |
kotlinx-coroutines-core | 1.10.2-1.0.0 | ohos 源集 strictly 钉版 |
KSP processor(本仓 ktorfit-ksp) | JVM 产物即可 | 消费方(shared)经元数据任务消费 |
关键结论:社区 Ktor 构建覆盖了 ohosArm64 上 HTTP 客户端所需的全部引擎与工具 klib,但不含 js/wasm 变体——版本目录不能只换一行,js 源集必须保留上游坐标,否则 js 目标解析失败。Gradle 的版本仲裁只升不降(strictly 无法把 3.5.0 压回 3.3.3),所以正确姿势是目录整体切社区线、js/wasm 单独升回上游线。
1.4 页面边界
验收应用单页滚动:标题区(Ktorfit OpenHarmony 验收 + 工具链版本行)、11 项检查列表(名称 + PASS/FAIL 徽标 + 详情)、重新运行按钮。天气 Demo 页:城市四按钮(北京/上海/广州/深圳,蓝色高亮当前项)、天气卡(温度大字、空气质量、更新时间、感冒提示、四日预报行)、刷新按钮、HTTPS 限制说明卡(说明 CIO 引擎 native 无 TLS、数据源为明文 HTTP、域名失败时自动回退 CDN IPv4)。
第 2 阶段:目标与依赖打通
2.1 互补模式(本适配的成败手)
build-logic/src/main/kotlin/ktorfit.kmp.gradle.kts 是全仓 KMP 模块的约定插件,目标集合由一个 Gradle 属性控制:
val openharmonyOnly = providers.gradleProperty("openharmonyOnly")
.map(String::toBoolean).getOrElse(false)
kotlin {
android { compileSdk = 36; minSdk = 21 }
jvm()
if (openharmonyOnly) {
ohosArm64()
} else {
// 上游原状:wasmJs、js、androidNative×4、ios×3、watchos×4、
// tvos×2、macosArm64、linux×2、mingwX64 全部保留
}
applyDefaultHierarchyTemplate()
}
这样一条命令就能在"鸿蒙专注构建"与"上游完整构建"之间切换,上游 CI 与本地开发不需要任何额外改动。
2.2 根构建的互斥处置
BCV 在注册 ohosArm64 目标时直接 NPE(KlibValidationPipelineBuilder.configureTargets 无法识别该目标),enabled=false 都拦不住——它在目标注册期就炸。处置是互补应用:
if (!openharmonyOnly) {
pluginManager.apply("org.jetbrains.kotlinx.binary-compatibility-validator")
extensions.configure<ApiValidationExtension>("apiValidation") {
ignoredProjects.addAll(listOf("sandbox", "ktorfit-ksp", "ktorfit-compiler-plugin"))
@OptIn(kotlinx.validation.ExperimentalBCVApi::class)
klib { enabled = true }
}
}
发布插件同理:gradle-maven-publish-plugin 0.36.0 的 0.36 元数据要求更高的内嵌 Kotlin,Gradle 8.14.3(内嵌 Kotlin 2.0.21)读不了,目录钉 0.34.0(与已审核仓库同款)。
2.3 依赖换线
gradle/libs.versions.toml 关键行:
kotlin = "2.2.21-1.0.0"
kspVersion = "2.2.21-2.0.5"
ktorfit = "2.7.5-ohos.1"
ktorVersion = "3.3.3-1.1.0-04" # 社区 ohos 制品线
ktorJsVersion = "3.5.0" # js/wasm 回退上游
coroutinesOhos = "1.10.2-1.0.0"
vannikMavenPublish = "0.34.0"
ktorfit-lib-core 的 js/wasm 源集显式回退:
jsMain {
dependencies { implementation("io.ktor:ktor-client-core:${libs.versions.ktorJsVersion.get()}") }
}
wasmJsMain {
dependencies { implementation("io.ktor:ktor-client-core:${libs.versions.ktorJsVersion.get()}") }
}
ktorfit-lib 的 ohos 源集声明默认引擎:
ohosArm64Main {
dependencies {
// The pure-Kotlin CIO engine is the default engine on ohosArm64.
implementation(libs.ktor.client.cio)
}
}
实测六个模块全部通过:
./gradlew -PopenharmonyOnly=true \
:ktorfit-annotations:compileKotlinOhosArm64 \
:ktorfit-lib-core:compileKotlinOhosArm64 \
:ktorfit-lib:compileKotlinOhosArm64 \
:ktorfit-converters:flow:compileKotlinOhosArm64 \
:ktorfit-converters:call:compileKotlinOhosArm64 \
:ktorfit-converters:response:compileKotlinOhosArm64 # 全部 BUILD SUCCESSFUL
2.4 KSP2 + KMP 的三件套接线(编译期命门)
KSP2 下,commonMain 接口的实现类由 kspCommonMainKotlinMetadata 任务生成,目标级 kspOhosArm64 / kspJvm 任务默认不会为 commonMain 源文件再生成实现。示例工程的 shared/build.gradle.kts 用三件套接线:
kotlin {
sourceSets {
commonMain {
// ① 把元数据任务的生成目录挂进 commonMain 编译
kotlin.srcDir("build/generated/ksp/metadata/commonMain")
dependencies { implementation(libs.ktorfit.lib) }
}
}
}
dependencies {
// ② common 元数据任务 + 目标级任务都注册处理器
add("kspCommonMainMetadata", libs.ktorfit.ksp)
add("kspJvm", libs.ktorfit.ksp)
add("kspOhosArm64", libs.ktorfit.ksp)
}
// ③ 所有目标级 KSP 任务依赖元数据任务,避免各自重复生成
tasks.matching { it.name.startsWith("ksp") && it.name != "kspCommonMainKotlinMetadata" }
.configureEach { dependsOn("kspCommonMainKotlinMetadata") }
少任何一件的症状都是同一个:ktorfit.createWeatherApi() / createAcceptanceApi() 解析不到生成类。
第 3 阶段:桥协议建模
3.1 验收契约
shared/commonMain(AcceptanceApi 12 方法 + NetworkApi,KSP 生成实现)
↓ Ktorfit 请求组装(路径/查询/请求头/请求体/表单)
nativeApp(MockEngine 场景核对 + CIO 真实请求)
↓ [{name,passed,detail} × 11] UTF-8 JSON
N-API KtorfitSampleRunChecks() / KtorfitSampleFree()
↓
ArkUI Index.ets(检查列表 + 重新运行)
AcceptanceApi 的 12 个方法按能力分组覆盖:
| 分组 | 方法 | 钉住什么 |
|---|---|---|
| 基本 | getPost(): String | suspend + String 返回(内置转换) |
| 路径 | getPostsOfUser(@Path id) | 路径段替换 |
| 查询 | search(@Query q, @Query limit) | 查询编码(空格转 +) |
| 请求体/头 | submit(@Body, @Header) | Content-Type 与自定义头 |
| 动词 | putItem / deleteItem / patchItem | PUT/DELETE/PATCH 请求行 |
| 表单 | login(@Field, @FieldMap) + @FormUrlEncoded | 表单编码与字段合并 |
| 静态头 | getConfig() + @Headers | 声明式请求头 |
| 转换器 | getCallBody(): Call / getResponse(): Response / stream(): Flow | 三个转换器工厂 |
| 真网络 | NetworkApi.get(): HttpResponse | CIO 引擎 + 状态码(免转换器) |
第 1~10 项在设备上用 MockEngine 断言请求行、路径、查询串、请求头、请求体逐项正确;第 11 项走真实网络:
private suspend fun runNetworkCheck(): CheckResult = try {
val client = HttpClient {
install(HttpTimeout) { // 默认 15s 在真机上不够
connectTimeoutMillis = 10_000
requestTimeoutMillis = 30_000
}
}
val ktorfit = Ktorfit.Builder()
.baseUrl("http://www.baidu.com/") // 明文:CIO 在 Native 不支持 TLS
.httpClient(client)
.build()
val response = ktorfit.createNetworkApi().get()
val code = response.status.value
if (code == 200) CheckResult(name, true, "HTTP $code(状态码 200)")
else CheckResult(name, false, "HTTP $code")
} catch (error: Throwable) {
CheckResult(name, false, error.message ?: error.toString())
}
3.2 天气 Demo 契约
请求 : GET http://t.weather.itboy.net/api/weather/city/{code}
响应 : {"cityInfo":{"city":"北京市",...},"data":{"wendu":"23.2","quality":"轻度",
"ganmao":"...","forecast":[{"ymd":"2026-10-15","week":"星期四","type":"晴",
"high":"高温 24℃","low":"低温 12℃","fx":"西北风"},...]}}
报告 : {"ok":true,"city":"北京市","updateTime":"12:13","temperature":"23.2",
"quality":"轻度","coldTip":"...","forecast":[{date,week,type,low,high,wind}×4]}
Demo 故意不引序列化依赖:WeatherParser 用正则提取字段、括号计数切分数组(commonMain 手写),把"纯 String 响应也能消费"作为示范;reportJson() 把解析结果重新序列化为扁平 JSON 给 ArkTS。
3.3 错误通道
每个导出整体 try/catch,Kotlin 异常永远不跨 FFI:
@CName("KtorfitSampleRunChecks")
fun runNativeChecks(): CPointer<ByteVar> {
val results = try {
runBlocking { runAcceptanceChecks() } + runBlocking { runNetworkCheck() }
} catch (error: Throwable) {
listOf(CheckResult("Native acceptance runner", false, error.message ?: error.toString()))
}
val json = results.joinToString(prefix = "[", postfix = "]") { it.toJson() }
/* nativeHeap 分配、NUL 结尾、返回 CPointer */
}
非法参数、网络失败、解析失败全部走同一个 passed:false / ok:false 通道,ArkTS 侧统一渲染。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string / cityCode
▼
C++ N-API entry(libentry.so)
│ const char*
▼
Kotlin/Native C ABI(libktorfit.so / libktorfitdemo.so)
│ KtorfitSampleRunChecks / KtorfitDemoFetch
▼
UTF-8 JSON + KtorfitSampleFree / KtorfitDemoFree
直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制请求语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 动态库
nativeApp 以 sharedLib 出库,导出符号由 version map 钉死:
ohosArm64 {
binaries.sharedLib {
baseName = "ktorfitdemo" // 验收工程为 "ktorfit"
linkerOpts("--entry=0", "--version-script=${project.file(".../shared-library.map")}")
linkerOpts("-lace_napi.z", "-luv", "-lhilog_ndk.z")
}
}
prepareOhos 任务把 libktorfitdemo.so 拷到 ohosApp/entry/libs/arm64-v8a/、生成头文件 libktorfitdemo_api.h 拷到 entry/src/main/cpp/include/。注意头文件名带 lib 前缀(baseName ktorfit 的头文件是 libktorfit_api.h),CMake 与 N-API 层的 include 都要写对。
| C ABI 符号 | ArkTS 方法 | 覆盖内核 |
|---|---|---|
KtorfitSampleRunChecks / KtorfitSampleFree | runChecks() | 11 项验收检查 + 缓冲区释放 |
KtorfitDemoFetch / KtorfitDemoFree | fetchWeather(cityCode) | 天气请求 + 解析 + 报告 JSON |
4.3 C++ N-API 方法分发
napi_property_descriptor desc[] = {
{"fetchWeather", nullptr, FetchWeather, nullptr, nullptr, nullptr,
static_cast<napi_property_attributes>(napi_default | napi_enumerable), nullptr},
};
回调统一三步:napi_typeof 校验参数类型、napi_get_value_string_utf8 取城市码、调 KN 函数并把返回的 UTF-8 缓冲包装成 napi 字符串。调用方(ArkTS)在拿到字符串后立即调 KtorfitDemoFree 归还 nativeHeap 缓冲,绝不跨帧持有 C 指针。
4.4 CMake 和类型声明
add_library(ktorfit SHARED IMPORTED)
set_target_properties(ktorfit PROPERTIES
IMPORTED_LOCATION ${NATIVE_SO} # entry/libs/arm64-v8a/libktorfitdemo.so
IMPORTED_NO_SONAME TRUE)
add_library(entry SHARED napi_init.cpp)
target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so ktorfit)
types/libentry/index.d.ts 向 ArkTS 声明方法签名。so 与头文件是构建产物,进 .gitignore(example/*/ohosApp/entry/libs/ 与 cpp/include/)。
第 5 阶段:ArkUI 宿主
5.1 验收页面
aboutToAppear(): void {
this.results = JSON.parse(runChecks()) as CheckResult[];
}
页面自上而下:标题与工具链版本行、11 项检查(PASS 绿 / FAIL 红 + 错误详情展开)、通过率汇总(11 / 11 PASS)、重新运行按钮。任何一项失败都展示原始错误文本——真机排障时这一行就是第一现场。
5.2 天气 Demo 页面
private loadWeather(): void {
this.loading = true;
try {
const raw: string = fetchWeather(this.current.code);
this.report = JSON.parse(raw) as WeatherReport;
} catch (error) {
this.report = { ok: false, error: String(error) } as WeatherReport;
}
this.loading = false;
}
城市四按钮切换即触发 loadWeather;天气卡显示温度大字、空气质量、更新时间、感冒提示与四日预报行;页面底部是 HTTPS 限制说明卡,明示数据源为明文 HTTP 及引擎限制。
5.3 网络回退链(真机踩出来的设计)
天气服务域名解析出 AAAA(IPv6)记录,但该 IPv6 节点的 TCP 80 端口在移动网络不可达——ICMP ping 通不等于 TCP 通,而 CIO 没有跨协议栈的 happy-eyeballs 回退。Demo 的最终设计是域名优先、CDN IPv4 兜底:
interface WeatherApi {
@GET("api/weather/city/{code}")
suspend fun weather(@Path("code") code: String): String
/** 域名解析到不可达 IPv6 时的兜底:CDN IPv4 + Host 头保持虚拟主机路由 */
@Headers("Host: t.weather.itboy.net")
@GET("api/weather/city/{code}")
suspend fun weatherViaCdnIp(@Path("code") code: String): String
}
Kotlin 侧先走域名,连接失败再走 http://36.150.185.18/ + Host 头的第二个 Ktorfit 实例,两次都带 HttpTimeout(连接 10s / 请求 30s)保证兜底依然跟手。CDN 按虚拟主机路由、CIO 尊重请求级 Host 头,这两点都经过独立验证(原始 socket 探测 + JVM CIO 探测)。
第 6 阶段:示例与验证
6.1 工程结构
example/OpenHarmonyExample/ 验收工程(11 项检查)
├── shared/ AcceptanceApi + AcceptanceChecks + KSP 接线
├── nativeApp/ libktorfit.so(baseName=ktorfit)+ KtorfitSampleRunChecks
└── ohosApp/
├── build-profile.json5 signingConfigs 留空(签名由 DevEco 自动生成回填)
├── AppScope/app.json5 bundleName com.example.ktorfit
└── entry/ CMake + N-API + Index.ets 验收页
example/OpenHarmonyDemo/ 天气 Demo(结构同上,bundleName com.example.ktorfitdemo)
scripts/build-openharmony.sh 发布 → 测试 → 编译 → 示例,一条命令
scripts/prepare-signing-project.py 复制到 DevEco 可接受路径(排除构建缓存与证书)
scripts/build-hap.sh 未签名 HAP 构建
scripts/check-native-deps.py 对照 OHOS SDK 审计 HAP 内 ELF 依赖
6.2 构建、签名和安装
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh # 库发布 + 测试 + so + 示例
python3 scripts/prepare-signing-project.py "$HOME/ktorfit_ohos_signing"
./scripts/build-hap.sh "$HOME/ktorfit_ohos_signing"
签名在 DevEco Studio 的 File → Project Structure → Signing Configs 中勾选自动生成,证书写入本机 ~/.ohos/config,不进入仓库(仓库内 build-profile.json5 的 signingConfigs 保持空数组)。注意调试 profile 与 bundleName 绑定——把验收签名挪给其他包名会报 install_type_error。
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 com.example.ktorfit -m entry
四、完整代码对照
4.1 整体架构
de.jensklingenberg.ktorfit/**(六个库模块原样编译)
│ @GET/@POST/... 注解 + Ktorfit.Builder + 请求组装 + 内置转换
shared/commonMain(AcceptanceApi / WeatherApi,KSP 生成 Impl)
│ kspCommonMainMetadata → commonMain srcDir(三件套接线)
nativeApp(MockEngine 断言 + CIO 真实请求 + JSON 报告)
│ @CName × 2(RunChecks/Free 或 Fetch/Free)
libentry.so(C++ N-API)
│ napi 字符串进出
Index.ets
│ JSON.parse + @State 更新
▼
ArkUI 真机页面(验收 11 项 / 天气 Demo)
4.2 文件清单
| 文件 | 职责 |
|---|---|
build-logic/.../ktorfit.kmp.gradle.kts | 互补模式目标集(openharmonyOnly → ohosArm64) |
build.gradle.kts(根) | BCV 互补应用(ohos 模式跳过)、buildscript 插件 classpath |
gradle/libs.versions.toml | Kotlin 2.2.21-1.0.0 + Ktor 3.3.3-1.1.0-04 + coroutines 1.10.2-1.0.0 + vannik 0.34.0 |
ktorfit-lib/build.gradle.kts | ohosArm64Main 声明 CIO 默认引擎 |
ktorfit-lib-core/build.gradle.kts | js/wasm 回退上游 ktor 3.5.0 |
example/*/shared/build.gradle.kts | KSP 三件套接线(元数据任务 + srcDir + dependsOn) |
example/*/nativeApp/build.gradle.kts | sharedLib(version map + ace_napi/uv/hilog)+ prepareOhos 拷贝 |
nativeApp/.../NativeChecks.kt | 11 项检查执行器 + C ABI(验收) |
nativeApp/.../NativeDemo.kt | 天气请求 + 回退链 + C ABI(Demo) |
ohosApp/entry/src/main/cpp/napi_init.cpp | N-API 导出、参数检查、缓冲释放 |
ohosApp/entry/src/main/ets/pages/Index.ets | ArkUI 验收/Demo 页面 |
scripts/*.sh / *.py | 一键构建、签名工程准备、HAP、ELF 审计 |
README.OpenHarmony_CN.md | 鸿蒙适配主文档(含网络限制章节) |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| 运行时 | Ktorfit.Builder().baseUrl().httpClient().build() | 构建客户端实例 |
| 运行时 | .converterFactories(Call/Response/FlowConverterFactory()) | 注册返回形态转换 |
| 生成 | ktorfit.createAcceptanceApi() / createWeatherApi() | 拿到 KSP 生成的实现 |
| 注解 | @GET @Path @Query @Header @Body @FormUrlEncoded @Field @FieldMap @Headers | 声明请求(本适配全部覆盖) |
| 引擎 | HttpClient { install(HttpTimeout) {...} } | CIO + 超时(真机必配) |
| Native | KtorfitSampleRunChecks / KtorfitDemoFetch | C ABI JSON 导出 |
| N-API | runChecks() / fetchWeather(cityCode) | 向 ArkTS 暴露能力 |
| ArkTS | JSON.parse(...) + @State | 结果渲染 |
4.4 ArkTS 与 Kotlin 的边界
// ArkTS:城市码进、天气报告出
const raw: string = fetchWeather('101010100');
const report: WeatherReport = JSON.parse(raw);
// report.city === '北京市' && report.temperature === '23.2'
// Kotlin:域名优先、CDN IPv4 兜底,异常全部收进 ok:false
private fun fetchWeatherReport(cityCode: String): String = try {
val body = runBlocking {
try {
ktorfit.createWeatherApi().weather(cityCode)
} catch (first: Throwable) {
try {
ktorfitCdnIp.createWeatherApi().weatherViaCdnIp(cityCode)
} catch (second: Throwable) {
throw IllegalStateException("domain: ${first.message}; cdn ipv4: ${second.message}")
}
}
}
WeatherParser.parse(body)?.reportJson() ?: errorReport("Unparsable response: ${body.take(120)}")
} catch (error: Throwable) {
errorReport(error.message ?: error.toString())
}
两者之间只传输扁平 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构。
五、关键决策说明
决策 1:互补模式而不是根构建替换
上游是活跃维护的多目标仓库,CI 与发布流程依赖完整目标集。互补模式(-PopenharmonyOnly=true 收敛到 JVM/Android/ohosArm64,默认保留全部目标)让鸿蒙构建成为叠加项而不是替换项:上游 gradlew projects 原样可跑(已验证含 sandbox 的完整配置),BCV 的互斥问题也顺势只留在非 ohos 模式。代价是版本目录必须同时满足两条线——这引出决策 2。
决策 2:版本目录整体切社区线,js/wasm 显式回退
最初尝试"commonMain 保持上游 3.5.0 + ohosArm64Main strictly 3.3.3",被 Gradle 仲裁规则否决:解析结果只取最高版本,strictly 能拒绝降级但不能实施降级。正确姿势是整个目录切社区线 3.3.3-1.1.0-04(升级方向一致),再给 jsMain/wasmJsMain 单独升回上游 3.5.0——社区线没有 js/wasm 变体,这一步同时是必需而不是权宜。
决策 3:接受 CIO 明文 HTTP,把 TLS 留给引擎升级
Ktor CIO 引擎在 Kotlin/Native 上游就硬编码不支持 TLS(TLS sessions are not supported on Native platform);ohos sysroot 没有 libcurl(curl 引擎链接期 cannot find -lcurl),但带 libssl_ohos.so(OpenSSL 引擎是后续方向)。因此验收与 Demo 都选明文 HTTP 端点 + HttpTimeout,并在页面与 README 里明示限制——库适配不修改上游引擎行为,把"能做什么、不能做什么"讲清楚比绕开更重要。
决策 4:KSP 接线做成可复制模板
kspCommonMainMetadata + kotlin.srcDir(...) + 目标任务 dependsOn 的三件套写进两个示例工程,README 与本文都完整给出。这是 KSP2 时代 KMP 消费方的通用姿势,不这么做时 createXxxApi() 解析失败的报错毫无指向性。
决策 5:JVM 交叉验证 + 真机真网双闸门
处理器逻辑由 ktorfit-lib-core 的 29 项 JVM 测试(含 KSP 生成代码的编译执行)端到端验证;示例侧 JVM 测试用 MockEngine 覆盖 10 个场景。ktorfit-ksp 自身的测试套件因 kctfork 0.13.0(读不了 Kotlin 2.4 元数据)暂跳过——处理器的正确性由 lib-core 测试与两个示例的实际生成结果背书,VALIDATION 里如实记录。真机侧,第 11 项真实请求证明引擎在设备上活着:TLS、DNS、IPv6、超时这些坑只有真机会暴露。
六、测试与验证
6.1 测试环境
本次验证使用 macOS(arm64)、JDK 21、DevEco Studio 6.0.0 Release、OpenHarmony 命令行工具(hvigor / hdc)与 HarmonyOS 6.0.0(API 20)鸿蒙真机(arm64)。
6.2 JVM 交叉验证
./gradlew -PopenharmonyOnly=true \
:ktorfit-lib-core:jvmTest \
:ktorfit-converters:call:jvmTest \
:example:OpenHarmonyExample:shared:jvmTest \
:example:OpenHarmonyDemo:shared:jvmTest
| 测试类 | 用例数 | 钉住什么 |
|---|---|---|
lib-core jvmTest | 29 | 请求构建核心:路径替换、查询编码、表单、请求头、KSP 生成代码执行 |
converters/call jvmTest | 1 | Call 转换器(response/flow 上游无 JVM 测试源) |
Example shared jvmTest | 1(内部 10 场景) | MockEngine 逐项断言 AcceptanceApi 全部 12 方法 |
Demo shared jvmTest | 4 | 天气解析字段提取 + 报告 JSON 合法性(含转义)严格校验 |
适配过程中 JVM 测试拦下过三个真问题:转换器工厂未注册(Add a ResponseConverter...)、查询编码断言(空格应编码为 +)、表单 Content-Type 应从请求体读取——全部按库的真实语义修正断言与装配。
6.3 HAP 与 ELF 验证
六个库模块 compileKotlinOhosArm64: BUILD SUCCESSFUL
七个模块 publishToMavenLocal: Passed(JVM、Android AAR、OHOS ARM64 klib)
linkDebugSharedOhosArm64 + prepareOhos: libktorfit.so + libktorfit_api.h 生成
check-native-deps.py: 3 packaged libraries, 0 unresolved strong imports
hvigor assembleHap: entry-default-unsigned.hap 构建通过
HAP manifest: bundleName com.example.ktorfit;target/min 60000020;
含 ohos.permission.INTERNET
6.4 功能用例(真机)
- 验收应用启动,11 项检查依次执行,首页显示
11 / 11 PASS; - 点"重新运行"结果一致;前后台切换页面与按钮仍可用;
- 第 1~10 项详情可见 MockEngine 断言的实际请求行、编码后的查询串、表单体;
- 第 11 项详情显示
HTTP 200(状态码 200)——CIO 引擎真实外网请求; - 天气 Demo 启动默认北京:卡片显示"北京市 23.2℃ 空气 轻度"、更新时间、感冒提示与四日预报(晴/阴/大雨/晴);
- 切换上海/广州/深圳,城市码经
@Path替换,数据即时刷新; - 断网/不可达场景下页面显示原始错误文本(域名 + CDN IPv4 两次尝试的结果),不崩溃;
- 底部说明卡明示 CIO 引擎 native 无 TLS、数据源为明文 HTTP。
6.5 验证结论
注解处理、KSP 生成、请求组装、转换器、CIO 引擎、Native 动态库、C ABI、N-API、ArkUI 页面和 HAP 已完成链路验证。验收应用在鸿蒙真机上 11/11 PASS,天气 Demo 展示真实气象数据(见下节效果图)。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/Ktorfit.git
cd Ktorfit
export JAVA_HOME="/path/to/jdk-21"
./scripts/build-openharmony.sh # 发布+测试+so+示例一条龙
python3 scripts/prepare-signing-project.py "$HOME/ktorfit_ohos_signing"
./scripts/build-hap.sh "$HOME/ktorfit_ohos_signing" # DevEco 自动签名后重建即得签名 HAP
hdc -t <设备序列号> install -r \
<签名工程>/entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start -a EntryAbility -b com.example.ktorfit -m entry
八、遗留问题与改进方向
8.1 踩坑复盘
- 只改 ArkTS 页面不算 KMP 适配,必须让六个库模块真正编译成
ohosArm64KLIB,并让 KSP 生成的实现在目标上可编译可执行; gradle-maven-publish-plugin 0.36.0的元数据 Gradle 8.14.3(内嵌 Kotlin 2.0.21)读不了,配置期直接失败——钉0.34.0解决,与已审核仓库一致;- BCV 对
ohosArm64在目标注册期 NPE,enabled=false无效,只能互补应用(ohos 模式不加载 BCV); - Gradle 版本仲裁只取最高:commonMain
3.5.0与 ohosArm64Mainstrictly 3.3.3不可共存,正确姿势是目录整体换线 + js/wasm 显式回退; kctfork 0.13.0依赖 Kotlin 2.4 元数据,使ktorfit-ksp测试编译失败——套件暂跳过,处理器由 lib-core 29 项测试与示例生成结果背书,记入 VALIDATION;- 示例工程
plugins { kotlin("multiplatform") version libs.versions.kotlin }编译不过——version catalog 引用在plugins {}块里不可用,写字面量"2.2.21-1.0.0"; ktorfit.createXxxApi()解析不到:KSP2 下 commonMain 实现类只由元数据任务生成,三件套接线(元数据任务注册 + 生成目录挂载 + 目标任务 dependsOn)缺一不可;- 接口写在 nativeApp 没有 KSP 处理器——API 接口必须放 shared 的 commonMain;
- 生成头文件名带 lib 前缀(
libktorfit_api.h),CMake include、N-API 源文件与脚本校验都要按真实文件名写; - 真机第 11 项三连坑:
TLS sessions are not supported on Native platform(换明文端点)→Expected response body of the type Response but was SourceByteReadChannel(Response<String>需要转换器,改用免转换器的HttpResponse+status.value)→Request timeout has expired(默认 15s 不够,HttpTimeout30s/10s); - 设备侧诊断要先验网络再怪引擎:DNS 配置(114/8.8.8.8)、域名 ping、外网连通全部正常之后,TLS 报错才能定位到引擎限制本身;
- ohos shell 不能执行
/data/local/tmp下的二进制(SELinux 拒绝),诊断只能用系统自带工具(ping/nslookup/hidumper); - 天气 CDN 域名解析出 AAAA 但 IPv6 TCP 80 不可达,CIO 无跨栈回退——域名优先 + CDN IPv4 +
@Headers("Host: ...")兜底;CDN 按虚拟主机路由(Host 错误返回 400)、CIO 尊重请求级 Host 头,两点都用独立探测验证过; - 手写 JSON 序列化用 Kotlin raw string 拼引号极易少一个值的开引号——编译通过但产出非法 JSON,真机页面报
SyntaxError: Invalid Token;修复后给报告序列化加了严格 JSON 校验器测试,此类 bug 在 JVM 侧即可拦截; - DevEco 调试 profile 与 bundleName 绑定,跨包名复用签名报
install_type_error;build-profile.json5的 products 还必须显式引用"signingConfig": "default",否则 SignHap 一毫秒跳过; - 签名材料由 DevEco 自动写入
build-profile.json5,仓库内保持signingConfigs: [],证书与 hap/so/include 等产物全部进.gitignore,提交前干跑git add -A -n复核入库清单; - 部分系统版本的
snapshot_display只接受.jpeg后缀,.display会报 invalid suffix——自动化截图时注意。
8.2 已知问题
- CIO 引擎在 Kotlin/Native 不支持 TLS,全部网络场景限明文 HTTP;页面与 README 已明示;
- ohos sysroot 无 libcurl,curl 引擎路线(社区 klib 存在)链接失败,已记录排除;sysroot 带
libssl_ohos.so,OpenSSL 引擎是可行的后续方向; ktorfit-ksp测试套件因 kctfork 元数据问题暂跳过(处理器由 lib-core 测试端到端覆盖);- js/wasm 目标回退上游 Ktor
3.5.0,与 ohos 线3.3.3-1.1.0-04双线并存(仅在非 ohos 模式构建); - 仅适配
ohosArm64,ohosX64未包含; - 验收与 Demo 的网络依赖公网端点(baidu / 天气服务),离线环境下第 11 项与 Demo 数据不可用(错误会如实显示)。
8.3 未来优化方向
- 评估基于
libssl_ohos.so的 OpenSSL 引擎接入,补齐 native TLS; - 增加
ohosX64目标与模拟器验收矩阵; - 评估
.har形态发布,让 ArkTS 工程直接依赖而不是走 so 三件套; - 在 CI 中加入 ohos 编译、ELF 符号检查和 HAP 构建;
- 把天气 Demo 的回退链沉淀为文档模式(域名优先 + IPv4 兜底 + Host 头),供消费方网络场景参考。
九、总结
9.1 核心难点回顾
上游 Ktorfit(编译器 + 运行时复合形态)
→ 验证 Ktor/coroutines ohos 制品线(3.3.3-1.1.0-04 / 1.10.2-1.0.0)
→ 工具链对齐 Kotlin 2.2.21-1.0.0 + KSP 2.2.21-2.0.5(Gradle 8.14.3)
→ 互补模式(openharmonyOnly,BCV 互斥处置,js/wasm 回退)
→ 六个库模块 ohosArm64 KLIB(CIO 默认引擎)
→ KSP2 三件套接线(commonMain 生成代码可编译)
→ C ABI(@CName × 2)+ UTF-8 JSON
→ C++ N-API(缓冲释放纪律)
→ ArkTS JSON 解析
→ ArkUI 真机验收(MockEngine 10 场景 + CIO 真实请求 = 11/11 PASS)
→ 天气 Demo(@Path 多城市 + IPv6/IPv4 回退链 + 真实数据效果图)
9.2 封装层次
- 编译期:注解模块 + KSP 处理器(JVM 编译),生成代码进 commonMain;
- 运行时:
Ktorfit.Builder、请求组装、内置转换 + 三个转换器工厂,ohos 默认 CIO 引擎; - Native 层:
sharedLib出动态库并经 version map 导出 2 个 C ABI; - N-API 层:完成参数检查、字符串转换和 nativeHeap 缓冲释放;
- ArkTS 层:管理页面状态、JSON 解析、错误文本与说明卡;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 编译器类库的适配闸门在"生成代码"而不是"库源码":Ktorfit 的 commonMain 一行没改,真正的命门是 KSP2 下 commonMain 实现类的生成与消费链路——三件套接线做对之前,任何构建都是假绿;
- 依赖解析规则决定换线策略:Gradle 只升不降,
strictly拦得住冲突拦不住降级;正确姿势是目录整体切线保证方向一致,再给缺失变体的源集(js/wasm)显式回退上游——"换线不换版"的前提是两条线都解析得到; - 网络库验收必须真机真网:MockEngine 证明请求组装正确,第 11 项真实请求证明引擎在设备上活着——TLS 限制、默认超时、CDN IPv6 不可达、DNS 行为这四个坑,任何一个 JVM 测试都发现不了,全部由真机暴露并沉淀为页面提示与回退设计。
9.4 适配成果
当前适配已完成:
- Ktorfit
2.7.5-ohos.1六个库模块的 ohosArm64 Kotlin/Native 编译(库源码零修改、零新增,构建脚本适配); - Ktor 社区构建
3.3.3-1.1.0-04与协程1.10.2-1.0.0在声明式 HTTP 客户端上的完整落地(js/wasm 显式回退上游3.5.0); - 两个示例工程的 KSP 端到端验证(验收 12 方法 + 天气
@Path,KSP2 三件套接线模板化); - 2 个 C ABI × 2 工程 + C++ N-API 包装层(ELF 审计 3 库 0 未解析符号);
- DevEco Stage ArkUI 验收页面(11 项检查 + 重新运行)与天气 Demo 页面(四城市、预报、限制说明、网络回退链);
- 29 + 1 + 1 + 4 项 JVM 测试全绿 + 7 模块本地发布(含 OHOS klib)+ HAP 构建、签名、设备安装;
- 真机验收
11 / 11 PASS与天气 Demo 真实数据展示(见效果图); - AtomGit 项目文档、OpenHarmony README(中英双份)、VALIDATION 验证记录和本地效果图。
适配过程 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)