本文梳理在 Kotlin Multiplatform 项目中,用 Ktor Client 作为底层 HTTP 引擎、Ktorfit 作为声明式网络接口的完整流程。
覆盖范围:架构总览 → 添加依赖 → 初始化 HttpClient(JSON 解析 / 日志 / 401 重试 + token 刷新)→ 双端初始化 → Ktorfit 接口定义与发送请求 → 生成与部署命令。
配套的网络缓存实现方案见 KMP 网络请求缓存,该文在 Ktorfit 网络层之上实现了”先读缓存、未命中再走网络并写回”的网络缓存读写,本文聚焦于网络层本身。
适用版本:Kotlin 2.4.0 · Ktor 3.5.1 · Ktorfit 2.7.5 · KSP 2.4.0-1.0.32 · kotlinx.serialization 1.7.3 · kotlinx.coroutines 1.11.0
架构总览
Ktor + Ktorfit 在整体架构中的位置
本项目采用 MVVM + Clean Architecture。网络层位于 Data Layer,对上是 Repository,对下是平台 HTTP 引擎。Ktor 负责”怎么发请求”,Ktorfit 负责”把接口描述成请求”。
1 | ┌─────────────────────────────────────────────────────────────┐ |
两种 UI 消费方式
网络层(Ktor + Ktorfit)对上是与 UI 无关的纯 Kotlin,因此同一套代码能被两类 UI 复用:
| 维度 | ① Compose UI(标准 KMP) | ② 原生 UI(Android / iOS 壳) |
|---|---|---|
| 入口 | shared 模块的统一 ViewModel(如 RTViewModel) |
各端自己的 ViewModel:Android androidx.lifecycle.ViewModel,iOS ObservableObject(SwiftUI @StateObject) |
| 调用路径 | 共享 ViewModel → UseCase → Repository → Ktorfit |
各端 ViewModel → UseCase → Repository → Ktorfit(与 Compose 路径一致,只是 ViewModel 落在平台侧) |
| 状态驱动 | StateFlow + collectAsState() |
Android StateFlow/LiveData + repeatOnLifecycle;iOS Published 属性 + ObservableObject |
| 网络层差异 | 无差异 | 无差异(Ktorfit 用法完全不变) |
结论:Ktor + Ktorfit 只写在
commonMain/ 平台引擎层,UI 用 Compose 还是原生、ViewModel 是共享还是各端自有,都只是”谁来调Repository“,不影响网络层代码。下方直接暴露一个object NetworkGraph装配好HttpClient与Ktorfit,两类 UI 都从它拿接口。关于在网络层之上叠加缓存读写、实现网络缓存(先读缓存、未命中再走网络并写回)的完整做法,见 KMP 网络请求缓存。
本文范围
- ✅ 依赖配置(含
kotlinx-serialization-json) - ✅ HttpClient 初始化(JSON 解析 / Logging / 401 重试 + token 刷新)
- ✅ 双端引擎(
expect/actual) - ✅ Ktorfit 接口与发送请求
- ✅ 在 Ktorfit 之上叠加网络缓存读写(实现方式见 KMP 网络请求缓存)
一、添加依赖
1.1 版本目录 gradle/libs.versions.toml
在 [versions] 中声明版本:
1 | [versions] |
在 [libraries] 中声明具体库:
1 | [libraries] |
1.2 模块 build.gradle.kts
任意使用 Ktorfit(即定义 @GET/@POST 接口)的模块,必须应用 KSP 插件并引入 ktorfit-ksp:
1 | plugins { |
最小说明:只要你的模块里写了
@GET/@POST这类 Ktorfit 接口,就要像上面一样配 KSP +ktorfit-ksp;纯消费接口(只create<XxxApi>())的模块则不需要 KSP。
二、初始化 HttpClient
这一步是网络层核心:配置 JSON 解析、日志、401 重试与 token 刷新。所有逻辑都写在
commonMain,引擎通过platformHttpEngine()注入。
2.1 kotlinx-serialization-json 配置
1 | // commonMain / NetworkJson.kt |
2.2 Token 刷新的 Manager 接口(需先定义)
401 重试依赖 token 刷新能力。先定义两个接口,由平台或业务层实现:
1 | // commonMain / token/TokenContracts.kt |
2.3 构建 HttpClient(install 插件)
1 | // commonMain / HttpClientFactory.kt |
关键逻辑说明:
Logging:LogLevel.INFO打印请求/响应摘要;DEBUG 看完整 body(注意敏感头)。- 401 处理:请求前自动带
Bearer;收到401先清本地 accessToken,再走TokenRefreshManager.refreshToken()(带 Mutex 去重),成功则用新 token 重发,失败抛TokenRefreshFailedException由上层跳登录。
2.4 构建 Ktorfit(复用同一 HttpClient)
1 | // commonMain / KtorfitProvider.kt |
三、使用逻辑(双端初始化)
3.1 expect / actual 平台引擎
1 | // commonMain / PlatformEngine.kt |
1 | // androidMain / PlatformEngine.android.kt |
1 | // iosMain / PlatformEngine.ios.kt |
3.2 平台入口装配
用一个 object 单例把 HttpClient + Ktorfit 装配好,两种 UI 都从它取接口:
1 | // commonMain / NetworkGraph.kt |
1 | // commonMain / SampleImpls.kt(demo 占位实现,真实项目由平台/业务注入) |
Android 原生 UI 入口:
NetworkGraph.httpClient通过lazy在首次访问时装配(引擎用 OkHttp)。
iOS 原生 UI 入口:Swift/ObjC 侧首次调用NetworkGraph的 Kotlin 函数时同理(引擎用 Darwin)。
四、Ktorfit 接口定义与发送请求
4.1 数据类(@Serializable)
1 | // commonMain / model/User.kt |
4.2 声明式接口(@GET / @POST)
1 | // commonMain / api/UserApi.kt |
4.3 两种获取接口的方式
1 | // ① 旧语法(Ktorfit 2.7.x 默认仍可用,无需额外生成) |
新旧语法差异:
create<T>():运行时反射式,即写即用,适合 demo / 快速验证。createUserApi():KSP 在编译期为每个接口生成的具体扩展函数,类型更安全、无运行时反射,但必须先把 Ktorfit 接口编译生成(./gradlew setupKsp或compileKotlinMetadata)。未生成前 IDE 会标红。
4.4 发送请求(调用示例)
1 | // commonMain 或任意调用方 |
错误处理:网络异常、401 刷新失败(
TokenRefreshFailedException)都会向上抛出,调用方用try/catch或runCatching统一处理;401 刷新成功时会在AuthRetry插件内重发,对调用方透明。
五、生成与部署命令
5.1 KSP 代码生成(Ktorfit 接口 → 实现)
使用 Ktorfit 的 @GET/@POST 接口后,需先生成 KSP 代码(尤其用新语法 createUserApi() 时):
1 | # 方式一:一键生成所有 Ktorfit 接口 |
Windows PowerShell 下用
./gradlew若被拦截,可用gradlew.bat;或用.\gradlew setupKsp。
5.2 Android 构建 / 安装 / 运行
1 | # 打 Debug 包 |
Android 侧引擎为 OkHttp(
platformHttpEngine()返回OkHttp.create()),无需额外原生配置。
5.3 iOS 构建 / 模拟器 / 真机
⚠️ iOS 构建必须在 macOS + Xcode 环境下。Windows 上
iosMaintarget 在 IDE 中标红是正常现象,不影响 Android 编译。
1 | # 产物为 .framework(core / shared),供 iOS 工程(Xcode)集成 |
然后在 Xcode 中:
- 模拟器:直接
Run(SwiftUI 调用NetworkGraph.api<UserApi>()) - 真机:签名后
Run;引擎为 Darwin(Darwin.create())
iOS 侧若用 SwiftUI 原生 UI,直接调用 KMP 暴露的
suspend函数,网络层完全一致。
5.4 CI 说明
| 环境 | 用途 | 注意事项 |
|---|---|---|
| macOS runner | 同时构建 Android + iOS | iOS 必须 macOS;Xcode 版本需匹配 Kotlin 工具链 |
| Linux/Windows runner | 仅 Android / 共用模块校验 | iOS target 会被跳过或标红,属正常 |
| KSP 生成 | 每次 build 自动跑 | 若缓存异常,先 ./gradlew clean 再 setupKsp |
六、常见问题
Q1:Ktorfit 接口标红 / createUserApi() 找不到?
未执行 KSP 生成。先跑 ./gradlew setupKsp 或 :core:compileKotlinMetadata;若只用旧语法 create<T>() 则无需生成。
Q2:401 没触发重发?
确认 AuthRetry 插件已 install、响应状态码为 401、且 TokenManager.getRefreshToken() 非空。TokenRefreshManager 的 Mutex 保证并发只刷新一次。
Q3:JSON 解析报错 “Unexpected JSON token”?
后端字段与数据类不一致时,确认 networkJson 已设 ignoreUnknownKeys = true / isLenient = true,且数据类有 @Serializable。
Q4:Windows 下 iOS target 标红 / 编译失败?
iOS 编译需 macOS + Xcode,Windows 仅做 Android。标红但 Android 正常属预期。
Q5:版本对齐?
本文版本(Ktor 3.5.1 / Ktorfit 2.7.5 / KSP 2.4.0-1.0.32 / serialization 1.7.3)需与项目版本目录一致,改动时同步更新,避免 Kotlin/KSP/Ktor 版本错配。
参考链接
- Ktor Client 官方文档:https://ktor.io/docs/client.html
- Ktorfit GitHub:https://github.com/Foso/Ktorfit
- Ktorfit 文档:https://foso.github.io/Ktorfit/
- kotlinx.serialization:https://github.com/Kotlin/kotlinx.serialization
- KSP 官方:https://kotlinlang.org/docs/ksp-overview.html
- 源码:https://github.com/daynearby/kmpdev