01 · Apollo-Kotlin 的鸿蒙适配本文是「Apollo-Kotlin 鸿蒙适配」系列第一篇讲怎么把上游库改造成能在 HarmonyOS 上编译。阅读前提了解 Kotlin Multiplatform 的expect/actual与 target 概念。〇、环境与上游0.1 环境搭建指引本文不重复官方环境搭建步骤按 CPFCPF-KMP-CMP 社区官方文档准备即可内容链接快速开始15 分钟装工具 → 配签名 → 跑第一个鸿蒙应用CPF 文档 · 快速开始工具链插件安装、工程模板、运行调试CPF 文档 · 工具链介绍工具链插件离线安装包KMP OHOS Support-1.0.0.ziphttps://maven.eazytec-cloud.com/nexus/repository/file-storage/ohos-kmp-toolchain/1.0.0/KMP OHOS Support-1.0.0.zip工具链插件源码 / Issuehttps://gitcode.com/CPF-KMP-CMP/ohos-kmp-toolchain文档总目录CPF 文档 · 目录文档仓库的分支名带版本号当前main-2.2对应 KMP2.2.21/ CMP1.9.2。工具链换代后分支与链接都会变上面的链接若 404从 CPF 组织首页 进docs仓库切到与工具链版本匹配的分支。0.2 环境版本本文验证所用项版本 / 路径说明JDK17Android Studio 的 Gradle JDK 必须切到 17Android Studio2024.2.1KMP 工程侧开发、Gradle SyncDevEco Studio6.0.0本文验证6.1.0.850鸿蒙壳工程harmonyApp/、签名、hvigor 打包HarmonyOS SDKAPI 23工具链要求 API 17在 DevEco 的 SDK Manager 中下载OHOS Kotlin 工具链2.2.21-1.1.0-07当前上限见第二节KMP OHOS Support 插件1.0.0读取local.properties里的local.deveco.path目标设备HarmonyOS API 17 真机或模拟器必须用华为开发者账号完成自动签名才能安装DevEco 装在非默认路径时必须在工程根目录的local.properties里显式声明该文件不入库local.deveco.pathD\:\\Dev\\Huawei\\DevEco Studio插件只认这一个键环境变量TOOL_HOME/OHOS_SDK_HOME不会被读取漏配会报Sync failed: DevEco Studio path not found详见适配问题与解决方案.mdF2。0.3 上游地址项地址上游仓库https://github.com/apollographql/apollo-kotlin本文所基于的 tagv4.3.0适配后源码含 ohos 改动单main分支https://gitcode.com/Goway_Hui/Apollo-Kotlin适配 diff相对v4.3.0adapt/patches/apollo-kotlin-4.3.0-ohos.patch可运行示例KMPCMPOHOS feat/01-apollo-kotlin上游只经codeload.github.com取源github.com:443的 git 协议在本机不可达curl -sL -o apollo.tar.gz https://codeload.github.com/apollographql/apollo-kotlin/tar.gz/refs/tags/v4.3.00.4 CPF 仓库与依赖来源“CPF”即CPF-KMP-CMP 社区本适配从它这里取两样东西鸿蒙定制 Kotlin 工具链以及已适配 ohosArm64 变体的传递依赖。用途地址组织首页文档 / 插件 / 已适配库源码https://gitcode.com/CPF-KMP-CMPMaven 仓库Gradle 里要配的那个https://maven.eazytec-cloud.com/nexus/repository/maven-public/Maven 浏览页查某个库有哪些-1.0.0定制版本https://maven.eazytec-cloud.com/nexus/#browse/browse:maven-releases三方库总览上游版本 ↔ 鸿蒙化版本对照表CPF 文档 · 三方库三方库示例工程kmp-thirdlibdemo · kmp-cmp-exampleGradle 侧只需加一条仓库后文 3.1 会改到maven(https://maven.eazytec-cloud.com/nexus/repository/maven-public/)一、为什么要适配Apollo-Kotlin 是一个标准的多平台库它在build-logic里统一注册编译目标// 上游 build-logic/src/main/kotlin/Mpp.kt改造前if(withJvm){jvm()}if(enableJswithJs){js(IR){...}}if(withWasm){wasmJs{nodejs()}}if(enableLinuxwithLinux){linuxX64(linux)}if(withAndroid){androidTarget{...}}if(enableApple){appleTargets.forEach{...}}没有ohosArm64。HarmonyOS 上跑的是 Kotlin/Native 产物.so/ klib官方 Kotlin 编译器不产出这个目标必须用定制工具链库也必须显式注册该目标并为每个expect声明补上actual实现。所以适配就三件事① 注册目标ohosArm64()② 补 actualexpect 的鸿蒙实现③ 出产物klib / .so二、第一道门槛版本选型这是最容易忽略、也最容易卡死的一步。鸿蒙定制 Kotlin 工具链的版本上限决定了能适配哪个上游版本。查询当前可用版本curl-shttps://maven.eazytec-cloud.com/nexus/repository/maven-public/org/jetbrains/kotlin/kotlin-gradle-plugin/maven-metadata.xml\|grep-oversion[^]*/version|seds/[^]*//g\|grep-E^2\.[0-9]\.|sort-uV|tail结果2026-102.2.21-1.1.0-01 ... 2.2.21-1.1.0-07 2.2.21-KBAT-004 ... 2.2.21-KBAT-009 2.2.21-OH.0.1.0-01 ... 2.2.21-OH.0.1.0-07上限是2.2.21。对照 Apollo 各版本的 Kotlin 依赖Apollo 版本上游 Kotlin结论5.2.0 / 5.1.0 / 5.0.02.3.10✗ 超出上限4.3.02.1.20✓ 选用升级到 2.2.21原则优先升上游的 Kotlin 版本而不是降上游库版本。升级 Kotlin 一般是兼容的降库版本可能丢掉 API、破坏依赖约束。最终把 Apollo 4.3.0 的 Kotlin 从2.1.20升到2.2.21-1.1.0-07。三、改动清单3.1 构建脚本4 个文件gradle/libraries.toml—— 版本升级kotlin-plugin 2.2.21-1.1.0-07 # was 2.1.20 kotlin-plugin-max 2.2.21 # was 2.1.20gradle/repositories.gradle.kts—— 加鸿蒙仓库listOf(pluginManagement.repositories,dependencyResolutionManagement.repositories).forEach{it.apply{// OHOS (OpenHarmony) KMP 定制产物仓库maven(https://maven.eazytec-cloud.com/nexus/repository/maven-public/)mavenCentral()...}}build.gradle.kts根—— 移除 BCVbinary-compatibility-validator0.16.3 不认识自定义目标ohosArm64会在配置阶段抛 NPE必须移除// apply(plugin org.jetbrains.kotlinx.binary-compatibility-validator)// configurekotlinx.validation.ApiValidationExtension { ... } ← 整块删掉build-logic/src/main/kotlin/Mpp.kt—— 注册目标kotlinExtension.apply{// OHOS 适配ohosOnly 模式下只保留 ohosArm64 目标// 便于在 Windows 上编译/发布apple/js/wasm 目标在 Windows 上不可构建valohosOnlyproviders.gradleProperty(apollo.ohosOnly).orNulltrueif(withJvm!ohosOnly){jvm()}if(enableJswithJs!ohosOnly){js(IR){...}}if(withWasm!ohosOnly){wasmJs{nodejs()}}if(enableLinuxwithLinux!ohosOnly){linuxX64(linux)}// ---- 新增鸿蒙目标 ----if(withOhos){ohosArm64{binaries{all{// 工具链自带 sysroot含 libhilog_ndk.z.so只需写库名linkerOpts(-lhilog_ndk.z)}}}}if(withAndroid!ohosOnly){androidTarget{...}}if(enableApple!ohosOnly){appleTargets.forEach{...}}configureSourceSetGraph()configureOhosHierarchy()}关键点linkerOpts只写-lhilog_ndk.z。CPF 官方示例里写的是${System.getenv(OHOS_NDK_HOME)}/native/llvm/.../libunwind.a但本机OHOS_NDK_HOME指向.../hms/native/BiSheng拼出来是死路径。定制工具链自带完整 sysroot145 个系统库不需要手动指 NDK 路径。3.2 源码集层级最容易踩的坑第一版适配时我删掉了手写的ohosArm64Main.dependsOn(...)因为工具链提示Redundant dependsOn edges between Kotlin Source Sets found. * ohosArm64Main.dependsOn(concurrentMain)这个提示具有误导性。它抱怨的是重复挂concurrentMain模板已经挂过了不代表linuxMain也挂好了。删掉后立刻报错e: ApolloAdaptableWith.kt:6:1 Expected ApolloAdaptableWith has no actual declaration in module commonMain for Native而该actual明明存在于linuxMain。结论OHOS 工具链的默认层级模板只把ohosArm64Main挂到concurrentMainnative 通用不会挂linuxMain。所以必须显式挂上鸿蒙同为 Linux 内核POSIX 能力一致OptIn(ExperimentalKotlinGradlePluginApi::class)privatefunKotlinMultiplatformExtension.configureOhosHierarchy(){if(targets.findByName(ohosArm64)null)returnvalohosMainsourceSets.findByName(ohosArm64Main)?:returnvallinuxMainsourceSets.findByName(linuxMain)if(linuxMain!null){if(!ohosMain.dependsOn.contains(linuxMain)){ohosMain.dependsOn(linuxMain)}}else{// ohosOnly 模式下没有 linux 目标直接把源码目录并进来valdirproject.file(src/linuxMain/kotlin)if(dir.isDirectory){ohosMain.kotlin.srcDir(dir)}}}挂了之后ohosArm64Main里就不要再重复写linuxMain已有的 actual否则Conflicting overloads。我最初在apollo-api里手抄了toJson.kt和OhosApolloUnsafeCast.kt后来全部删掉——它们已由linuxMain提供。3.3 依赖升级最高频的坑编译时报Could not resolve com.squareup.okio:okio:3.9.0. No matching variant of com.squareup.okio:okio:3.9.0 was found. The consumer ... attribute org.jetbrains.kotlin.native.target with value ohos_arm64原因传递依赖也没有鸿蒙变体。Apollo 依赖的 okio / uuid / coroutines / atomicfu 都得换成CPF 已适配的定制版本atomicfu 0.31.0-1.0.0 kotlinx-coroutines 1.10.2-1.0.0 kotlinx-datetime 0.7.1-1.0.0 okio 3.16.4-1.0.0 uuid { group com.benasher44, name uuid, version 0.8.4-1.0.0 }通用做法把libraries.toml里所有版本号搜一遍能升的全升到 CPF 的-1.0.0线。但有一个例外build-logic/是 Gradle 的 included build用 embedded Kotlin 2.0 编译。如果把kotlinx-serialization升到 1.9.1它的元数据版本是 2.2.0build-logic直接编译失败e: kotlin-stdlib-2.2.21-0.3.0.jar!/META-INF/kotlin-stdlib.kotlin_module Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 2.2.0, expected version is 2.0.0.所以kotlinx-serialization-runtime保持1.6.2不动确认过只有apollo-ast用它而apollo-ast是构建期工具不需要 ohos。3.4 关闭-Werror升依赖引入了上游的 deprecation 警告而 Apollo 的构建约定里开了allWarningsAsErrors(true)e: warnings found and -Werror specified w: KotlinxTimeAdapters.kt:8:8 class Instant ... is deprecated. Use kotlin.time.Instant instead.适配构建不追求零警告关掉// build-logic/src/main/kotlin/CompilerOptions.ktallWarningsAsErrors(false)// was true3.5 手写 ohos actual6 个文件这是真正的平台实现工作。逐模块清点expect后逐个补模块文件实现要点apollo-mpp-utilsutils.ktcurrentTimeMillis()用 POSIXclock_gettime(CLOCK_REALTIME)apollo-adaptersBigDecimal.kt纯 Kotlin 十进制实现鸿蒙无NSDecimalNumber/java.math.BigDecimalapollo-normalized-cacheLock.ktAtomicFuReentrantLock照抄 appleMainapollo-normalized-cache-api-cache-lock-ohos.kt同上apollo-runtimeDefaultHttpEngine.ohos.ktktor-client-cio 引擎apollo-runtimeWebSocketEngine.ohos.kt×2ktor WebSockets 插件新旧两套 APIBigDecimal是最费心的一个因为鸿蒙没有任何现成的大数实现actualclassBigDecimalinternalconstructor(privatevalunscaled:Long,// 去掉小数点后的整数privatevalscale:Int,// 小数位数):Number(){actualfunadd(augend:BigDecimal):BigDecimal{valsmaxOf(scale,augend.scale)returnBigDecimal(rescaledTo(s)augend.rescaledTo(s),s)}actualfunmultiply(multiplicand:BigDecimal):BigDecimalBigDecimal(unscaled*multiplicand.unscaled,scalemultiplicand.scale)// ...}3.6 手写 HTTP / WebSocket 引擎最实质的适配Apollo 的 native 侧只有 Apple 实现appleMainapollo-runtime连linuxMain都没有。所以鸿蒙的 HTTP / WebSocket 必须新写。这里复用 CPF 已适配的 ktor// apollo-runtime/src/ohosArm64Main/.../DefaultHttpEngine.ohos.ktactualfunDefaultHttpEngine(timeoutMillis:Long):HttpEngineOhosHttpEngine(timeoutMillis)privateclassOhosHttpEngine(privatevaltimeoutMillis:Long):HttpEngine{privatevalclient:HttpClientbylazy{HttpClient(CIO){engine{requestTimeouttimeoutMillis}expectSuccessfalse}}overridesuspendfunexecute(request:HttpRequest):HttpResponse{valktorResponseclient.request(request.url){methodwhen(request.method){HttpMethod.Get-KtorHttpMethod.Get HttpMethod.Post-KtorHttpMethod.Post}request.headers.forEach{headers.append(it.name,it.value)}request.body?.let{body-valbufferBuffer()body.writeTo(buffer)setBody(ByteArrayContent(buffer.readByteArray(),ContentType.parse(body.contentType)))}}valbuilderHttpResponse.Builder(ktorResponse.status.value)ktorResponse.headers.entries().forEach{e-e.value.forEach{builder.addHeader(e.key,it)}}returnbuilder.body(Buffer().write(ktorResponse.readRawBytes())).build()}overridefunclose(){client.close()}}WebSocket 部分要点类似但有两个 ktor API 细节必须注意// ① 取响应体用 readRawBytes()旧名 readBytes 已废弃valbytesktorResponse.readRawBytes()// ② webSocketSession 的 url 要用 lambda 形式client.webSocketSession{url{takeFrom(newUrl)}extraHeaders.forEach{header-this.headers.append(header.name,header.value)}}第 ② 点踩过坑函数参数名headers与HttpRequestBuilder.headers同名lambda 内headers.append(...)被解析成了StringBuilder.append报一堆候选不匹配。解决办法是局部改名 显式this。另外 ktor 版本要单独处理Apollo 4.3.0 自带ktor 3.0.0无 ohos 变体而 CPF 适配的是3.3.3-1.0.0。由于只在ohosArm64Main声明不影响其他平台findByName(ohosArm64Main)?.apply{dependencies{implementation(io.ktor:ktor-client-core:3.3.3-1.0.0)implementation(io.ktor:ktor-client-cio:3.3.3-1.0.0)implementation(io.ktor:ktor-client-websockets:3.3.3-1.0.0)}}四、验证# 7 个模块全部编译通过gradlew :apollo-annotations:compileKotlinOhosArm64\:apollo-mpp-utils:compileKotlinOhosArm64\:apollo-api:compileKotlinOhosArm64\:apollo-adapters:compileKotlinOhosArm64\:apollo-normalized-cache-api:compileKotlinOhosArm64\:apollo-normalized-cache:compileKotlinOhosArm64\:apollo-runtime:compileKotlinOhosArm64结果 Task :apollo-annotations:compileKotlinOhosArm64 Task :apollo-mpp-utils:compileKotlinOhosArm64 Task :apollo-api:compileKotlinOhosArm64 Task :apollo-normalized-cache-api:compileKotlinOhosArm64 Task :apollo-adapters:compileKotlinOhosArm64 Task :apollo-normalized-cache:compileKotlinOhosArm64 Task :apollo-runtime:compileKotlinOhosArm64 BUILD SUCCESSFUL五、小结改动类别文件数说明构建脚本4版本、仓库、移除 BCV、注册目标模块构建脚本4jvmTest安全引用、wasmJsMain!!改?.新增 ohos actual6mpp-utils / adapters / cache / cache-api / runtime新增 ohos 引擎3HTTP ×1、WebSocket ×2三条最重要的经验先查工具链版本上限再决定上游库版本——顺序反了会白做linuxMain不会自动继承必须显式dependsOn传递依赖也要有 ohos 变体否则换 CPF 定制版下一篇讲如何在鸿蒙工程里使用 Apollo。