本文记录 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 在编译期生成实现类,运行时把调用转交给 Ktor HttpClient 执行,并通过转换器工厂支持 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 MultiplatformHarmonyOS Kotlin 2.2.21-1.0.0(eazytec Maven)
KSP2.2.21-2.0.5(与 Kotlin 版本前缀对齐)
JDK21
OpenHarmony 目标ohosArm64(jvm + android 始终保留)
Gradle8.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)
BCV0.18.1,仅非 ohos 模式应用
DevEco product6.0.0(20) 兼容
DevEco Studio6.0.0 Release
ABIarm64-v8a
Bundlecom.example.ktorfit(验收)/ com.example.ktorfitdemo(Demo)
export JAVA_HOME="/path/to/jdk-21"
java -version
./gradlew --version
1.3 依赖制品线盘点
依赖ohos 变体处置
io.ktor:ktor-client-core3.3.3-1.1.0-04 有 ohosArm64 klibcommonMain 统一换线
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-core1.10.2-1.0.0ohos 源集 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(): Stringsuspend + String 返回(内置转换)
路径getPostsOfUser(@Path id)路径段替换
查询search(@Query q, @Query limit)查询编码(空格转 +)
请求体/头submit(@Body, @Header)Content-Type 与自定义头
动词putItem / deleteItem / patchItemPUT/DELETE/PATCH 请求行
表单login(@Field, @FieldMap) + @FormUrlEncoded表单编码与字段合并
静态头getConfig() + @Headers声明式请求头
转换器getCallBody(): Call / getResponse(): Response / stream(): Flow三个转换器工厂
真网络NetworkApi.get(): HttpResponseCIO 引擎 + 状态码(免转换器)

第 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 / KtorfitSampleFreerunChecks()11 项验收检查 + 缓冲区释放
KtorfitDemoFetch / KtorfitDemoFreefetchWeather(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.tomlKotlin 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.ktsohosArm64Main 声明 CIO 默认引擎
ktorfit-lib-core/build.gradle.ktsjs/wasm 回退上游 ktor 3.5.0
example/*/shared/build.gradle.ktsKSP 三件套接线(元数据任务 + srcDir + dependsOn)
example/*/nativeApp/build.gradle.ktssharedLib(version map + ace_napi/uv/hilog)+ prepareOhos 拷贝
nativeApp/.../NativeChecks.kt11 项检查执行器 + C ABI(验收)
nativeApp/.../NativeDemo.kt天气请求 + 回退链 + C ABI(Demo)
ohosApp/entry/src/main/cpp/napi_init.cppN-API 导出、参数检查、缓冲释放
ohosApp/entry/src/main/ets/pages/Index.etsArkUI 验收/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 + 超时(真机必配)
NativeKtorfitSampleRunChecks / KtorfitDemoFetchC ABI JSON 导出
N-APIrunChecks() / fetchWeather(cityCode)向 ArkTS 暴露能力
ArkTSJSON.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 jvmTest29请求构建核心:路径替换、查询编码、表单、请求头、KSP 生成代码执行
converters/call jvmTest1Call 转换器(response/flow 上游无 JVM 测试源)
Example shared jvmTest1(内部 10 场景)MockEngine 逐项断言 AcceptanceApi 全部 12 方法
Demo shared jvmTest4天气解析字段提取 + 报告 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 功能用例(真机)

  1. 验收应用启动,11 项检查依次执行,首页显示 11 / 11 PASS;
  2. 点"重新运行"结果一致;前后台切换页面与按钮仍可用;
  3. 第 1~10 项详情可见 MockEngine 断言的实际请求行、编码后的查询串、表单体;
  4. 第 11 项详情显示 HTTP 200(状态码 200)——CIO 引擎真实外网请求;
  5. 天气 Demo 启动默认北京:卡片显示"北京市 23.2℃ 空气 轻度"、更新时间、感冒提示与四日预报(晴/阴/大雨/晴);
  6. 切换上海/广州/深圳,城市码经 @Path 替换,数据即时刷新;
  7. 断网/不可达场景下页面显示原始错误文本(域名 + CDN IPv4 两次尝试的结果),不崩溃;
  8. 底部说明卡明示 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 踩坑复盘

  1. 只改 ArkTS 页面不算 KMP 适配,必须让六个库模块真正编译成 ohosArm64 KLIB,并让 KSP 生成的实现在目标上可编译可执行;
  2. gradle-maven-publish-plugin 0.36.0 的元数据 Gradle 8.14.3(内嵌 Kotlin 2.0.21)读不了,配置期直接失败——钉 0.34.0 解决,与已审核仓库一致;
  3. BCV 对 ohosArm64 在目标注册期 NPE,enabled=false 无效,只能互补应用(ohos 模式不加载 BCV);
  4. Gradle 版本仲裁只取最高:commonMain 3.5.0 与 ohosArm64Main strictly 3.3.3 不可共存,正确姿势是目录整体换线 + js/wasm 显式回退;
  5. kctfork 0.13.0 依赖 Kotlin 2.4 元数据,使 ktorfit-ksp 测试编译失败——套件暂跳过,处理器由 lib-core 29 项测试与示例生成结果背书,记入 VALIDATION;
  6. 示例工程 plugins { kotlin("multiplatform") version libs.versions.kotlin } 编译不过——version catalog 引用在 plugins {} 块里不可用,写字面量 "2.2.21-1.0.0";
  7. ktorfit.createXxxApi() 解析不到:KSP2 下 commonMain 实现类只由元数据任务生成,三件套接线(元数据任务注册 + 生成目录挂载 + 目标任务 dependsOn)缺一不可;
  8. 接口写在 nativeApp 没有 KSP 处理器——API 接口必须放 shared 的 commonMain;
  9. 生成头文件名带 lib 前缀(libktorfit_api.h),CMake include、N-API 源文件与脚本校验都要按真实文件名写;
  10. 真机第 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 不够,HttpTimeout 30s/10s);
  11. 设备侧诊断要先验网络再怪引擎:DNS 配置(114/8.8.8.8)、域名 ping、外网连通全部正常之后,TLS 报错才能定位到引擎限制本身;
  12. ohos shell 不能执行 /data/local/tmp 下的二进制(SELinux 拒绝),诊断只能用系统自带工具(ping/nslookup/hidumper);
  13. 天气 CDN 域名解析出 AAAA 但 IPv6 TCP 80 不可达,CIO 无跨栈回退——域名优先 + CDN IPv4 + @Headers("Host: ...") 兜底;CDN 按虚拟主机路由(Host 错误返回 400)、CIO 尊重请求级 Host 头,两点都用独立探测验证过;
  14. 手写 JSON 序列化用 Kotlin raw string 拼引号极易少一个值的开引号——编译通过但产出非法 JSON,真机页面报 SyntaxError: Invalid Token;修复后给报告序列化加了严格 JSON 校验器测试,此类 bug 在 JVM 侧即可拦截;
  15. DevEco 调试 profile 与 bundleName 绑定,跨包名复用签名报 install_type_error;build-profile.json5 的 products 还必须显式引用 "signingConfig": "default",否则 SignHap 一毫秒跳过;
  16. 签名材料由 DevEco 自动写入 build-profile.json5,仓库内保持 signingConfigs: [],证书与 hap/so/include 等产物全部进 .gitignore,提交前干跑 git add -A -n 复核入库清单;
  17. 部分系统版本的 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 三条经验

  1. 编译器类库的适配闸门在"生成代码"而不是"库源码":Ktorfit 的 commonMain 一行没改,真正的命门是 KSP2 下 commonMain 实现类的生成与消费链路——三件套接线做对之前,任何构建都是假绿;
  2. 依赖解析规则决定换线策略:Gradle 只升不降,strictly 拦得住冲突拦不住降级;正确姿势是目录整体切线保证方向一致,再给缺失变体的源集(js/wasm)显式回退上游——"换线不换版"的前提是两条线都解析得到;
  3. 网络库验收必须真机真网: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。

参考文档

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐