网络请求
源:Ktor Client | OkHttp
网络请求是几乎所有应用的核心功能。KMP 生态中,Ktor Client 是跨平台网络库的首选方案,本文将展示如何使用 Ktor 实现统一的网络请求。
平台差异对比
| 平台 | 推荐引擎 | 底层实现 | 特性支持 |
|---|---|---|---|
| Android | OkHttp | OkHttp3 | HTTP/2、连接池、拦截器 |
| iOS | Darwin | NSURLSession | TLS、Cookie、缓存 |
| Desktop (JVM) | CIO / OkHttp | Coroutine IO / OkHttp3 | 全功能支持 |
| JavaScript | JS | fetch API | 基础 HTTP |
Ktor Client 优势
Ktor Client 提供统一的 API,各平台自动选择最优引擎,开发者无需关心底层差异。
Ktor Client 基础使用
API 核心签名说明
val client = HttpClient(engine)suspend fun HttpClient.get(url: String): HttpResponsesuspend inline fun <reified T> HttpClient.get(url: String): Tsuspend fun HttpClient.post(url: String) { body = ... }fun HttpClient.close()
标准代码块
kotlin
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class User(
val id: Int,
val name: String,
val email: String
)
@Serializable
data class ApiResponse<T>(
val code: Int,
val data: T,
val message: String
)
class ApiClient {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
}
suspend fun getUser(userId: Int): User {
return client.get("https://api.example.com/users/$userId").body()
}
suspend fun createUser(name: String, email: String): User {
return client.post("https://api.example.com/users") {
setBody(mapOf("name" to name, "email" to email))
}.body()
}
fun close() {
client.close()
}
}
// 业务层使用
class UserRepository(private val apiClient: ApiClient) {
suspend fun fetchUserProfile(userId: Int): Result<User> {
return runCatching {
apiClient.getUser(userId)
}
}
}kotlin
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
// Android 使用 OkHttp 引擎
actual fun createHttpClient(): HttpClient {
return HttpClient(OkHttp) {
engine {
config {
followRedirects(true)
connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
}
}
}
}kotlin
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
// iOS 使用 Darwin (NSURLSession) 引擎
actual fun createHttpClient(): HttpClient {
return HttpClient(Darwin) {
engine {
configureRequest {
setAllowsCellularAccess(true)
}
}
}
}kotlin
import io.ktor.client.*
import io.ktor.client.engine.cio.*
// Desktop 使用 CIO 引擎(纯 Kotlin 协程实现)
actual fun createHttpClient(): HttpClient {
return HttpClient(CIO) {
engine {
maxConnectionsCount = 1000
endpoint {
connectTimeout = 30000
socketTimeout = 30000
}
}
}
}代码封装示例
以下是带统一错误处理和 Token 刷新的完整封装:
kotlin
// commonMain
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Error(val code: Int, val message: String) : NetworkResult<Nothing>()
data class Exception(val throwable: Throwable) : NetworkResult<Nothing>()
}
class ApiService(private val baseUrl: String, private val tokenProvider: () -> String?) {
private val client = HttpClient {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = 30000
connectTimeoutMillis = 30000
socketTimeoutMillis = 30000
}
install(HttpRequestRetry) {
retryOnServerErrors(maxRetries = 3)
exponentialDelay()
}
defaultRequest {
url(baseUrl)
// 自动添加 Token
tokenProvider()?.let { token ->
header("Authorization", "Bearer $token")
}
}
}
suspend inline fun <reified T> get(
path: String,
params: Map<String, String> = emptyMap()
): NetworkResult<T> {
return safeApiCall {
client.get(path) {
params.forEach { (key, value) ->
parameter(key, value)
}
}
}
}
suspend inline fun <reified T, reified R> post(
path: String,
body: T
): NetworkResult<R> {
return safeApiCall {
client.post(path) {
contentType(ContentType.Application.Json)
setBody(body)
}
}
}
suspend inline fun <reified T> safeApiCall(
crossinline apiCall: suspend () -> HttpResponse
): NetworkResult<T> {
return try {
val response = apiCall()
when (response.status.value) {
in 200..299 -> {
val data = response.body<T>()
NetworkResult.Success(data)
}
401 -> {
// Token 过期,需要刷新
NetworkResult.Error(401, "Unauthorized")
}
else -> {
NetworkResult.Error(
code = response.status.value,
message = response.bodyAsText()
)
}
}
} catch (e: Exception) {
NetworkResult.Exception(e)
}
}
fun close() {
client.close()
}
}依赖补充
Ktor Client 核心
kotlin
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.7")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.7")
}
jvmMain.dependencies {
implementation("io.ktor:ktor-client-cio:2.3.7")
}
}
}groovy
kotlin {
sourceSets {
commonMain {
dependencies {
implementation "io.ktor:ktor-client-core:2.3.7"
implementation "io.ktor:ktor-client-content-negotiation:2.3.7"
implementation "io.ktor:ktor-serialization-kotlinx-json:2.3.7"
}
}
androidMain {
dependencies {
implementation "io.ktor:ktor-client-okhttp:2.3.7"
}
}
iosMain {
dependencies {
implementation "io.ktor:ktor-client-darwin:2.3.7"
}
}
}
}toml
[versions]
ktor = "2.3.7"
[libraries]
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }最新版本查看链接:https://ktor.io/docs/client-releases.html
kotlinx.serialization
kotlin
plugins {
kotlin("plugin.serialization") version "1.9.21"
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
}
}实战坑点
iOS Darwin 引擎 SSL 证书
SSL Pinning
iOS Darwin 引擎不支持自定义 SSL 证书验证,需使用系统信任的证书。
解决方案:
如需自定义证书,可切换到 CIO 引擎或在 iOS 原生层配置。
Android OkHttp 版本冲突
::: caution 依赖冲突 项目中若已使用 OkHttp,可能与 Ktor 的 OkHttp 引擎版本冲突。 :::
解决方案:
kotlin
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.7") {
exclude(group = "com.squareup.okhttp3")
}
implementation("com.squareup.okhttp3:okhttp:4.12.0") // 统一版本
}序列化异常处理
SerializationException
服务端返回非预期字段或格式错误会导致反序列化失败。
解决方案:
kotlin
Json {
ignoreUnknownKeys = true // 忽略未知字段
coerceInputValues = true // 强制转换非法值为默认值
}客户端未关闭
资源泄漏
HttpClient 是重量级对象,应复用全局单例,应用退出时关闭。
最佳实践:
kotlin
object NetworkModule {
val client = HttpClient { /* ... */ }
fun close() {
client.close()
}
}
// 应用退出时调用
NetworkModule.close()CORS 跨域问题
Web 平台限制
JavaScript 引擎受浏览器 CORS 限制,需服务端配置 Access-Control-Allow-Origin。