网络:Ktor Client
Ktor Client 是 Kotlin Multiplatform 官方推荐的网络库,提供了统一的 API 来处理跨平台 HTTP 请求。
基础配置
kotlin
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
// 创建通用客户端
fun createHttpClient(): HttpClient {
return HttpClient {
// JSON 序列化
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
})
}
// 日志插件
install(Logging) {
level = LogLevel.BODY
logger = object : Logger {
override fun log(message: String) {
println("[HTTP] $message")
}
}
}
// 默认请求配置
defaultRequest {
url("https://api.example.com/")
}
}
}kotlin
import io.ktor.client.*
import io.ktor.client.engine.android.*
actual fun createPlatformHttpClient(): HttpClient {
return HttpClient(Android) {
engine {
connectTimeout = 30_000
socketTimeout = 30_000
}
}
}kotlin
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
actual fun createPlatformHttpClient(): HttpClient {
return HttpClient(Darwin) {
engine {
configureRequest {
setAllowsCellularAccess(true)
}
}
}
}kotlin
import io.ktor.client.*
import io.ktor.client.engine.cio.*
actual fun createPlatformHttpClient(): HttpClient {
return HttpClient(CIO) {
engine {
maxConnectionsCount = 1000
endpoint {
maxConnectionsPerRoute = 100
connectTimeout = 30_000
}
}
}
}依赖配置
toml
[versions]
ktor = "3.0.3"
kotlinx-serialization = "1.7.3"
[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-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
# 平台引擎
ktor-client-android = { module = "io.ktor:ktor-client-android", 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" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
[bundles]
ktor-common = [
"ktor-client-core",
"ktor-client-content-negotiation",
"ktor-serialization-kotlinx-json",
"ktor-client-logging"
]kotlin
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.bundles.ktor.common)
}
androidMain.dependencies {
implementation(libs.ktor.client.android)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
jvmMain.dependencies {
implementation(libs.ktor.client.cio)
}
}
}最新版本查询:https://github.com/ktorio/ktor/releases
HTTP 请求
GET 请求
kotlin
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.client.call.*
// 简单 GET
suspend fun fetchText(): String {
return client.get("https://api.example.com/data").bodyAsText()
}
// 带参数 GET
suspend fun searchUsers(query: String): List<User> {
return client.get("https://api.example.com/users") {
parameter("q", query)
parameter("limit", 20)
}.body()
}
// 带 Header
suspend fun fetchWithAuth(token: String): String {
return client.get("https://api.example.com/protected") {
header("Authorization", "Bearer $token")
}.bodyAsText()
}POST 请求
kotlin
import io.ktor.client.request.*
import io.ktor.http.*
// JSON 请求
suspend fun createUser(user: User): User {
return client.post("https://api.example.com/users") {
contentType(ContentType.Application.Json)
setBody(user)
}.body()
}
// 表单请求
suspend fun login(username: String, password: String): AuthResponse {
return client.post("https://api.example.com/auth/login") {
setBody(FormDataContent(Parameters.build {
append("username", username)
append("password", password)
}))
}.body()
}
// Multipart 文件上传
import io.ktor.client.request.forms.*
import io.ktor.http.content.*
suspend fun uploadFile(file: ByteArray, filename: String): UploadResponse {
return client.post("https://api.example.com/upload") {
setBody(MultiPartFormDataContent(
formData {
append("file", file, Headers.build {
append(HttpHeaders.ContentType, "image/png")
append(HttpHeaders.ContentDisposition, "filename=$filename")
})
}
))
}.body()
}PUT/DELETE 请求
kotlin
// PUT 更新
suspend fun updateUser(id: String, user: User): User {
return client.put("https://api.example.com/users/$id") {
contentType(ContentType.Application.Json)
setBody(user)
}.body()
}
// DELETE 删除
suspend fun deleteUser(id: String): Boolean {
val response = client.delete("https://api.example.com/users/$id")
return response.status.isSuccess()
}插件配置
JSON 序列化
kotlin
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
explicitNulls = false
encodeDefaults = true
})
}
// 使用
@Serializable
data class ApiResponse<T>(
val code: Int,
val message: String,
val data: T?
)
suspend fun getUser(id: String): ApiResponse<User> {
return client.get("/users/$id").body()
}请求/响应拦截器
kotlin
import io.ktor.client.plugins.*
// 添加通用请求头
install(DefaultRequest) {
header("X-Api-Key", "YOUR_API_KEY")
header("User-Agent", "MyApp/1.0")
}
// 响应验证
install(HttpCallValidator) {
validateResponse { response ->
if (response.status.value >= 400) {
val error = response.bodyAsText()
throw ApiException(response.status.value, error)
}
}
}
class ApiException(val code: Int, message: String) : Exception(message)认证
kotlin
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
// Bearer Token
install(Auth) {
bearer {
loadTokens {
BearerTokens(
accessToken = loadAccessToken(),
refreshToken = loadRefreshToken()
)
}
refreshTokens {
val newTokens = refreshAccessToken(oldTokens?.refreshToken)
BearerTokens(
accessToken = newTokens.accessToken,
refreshToken = newTokens.refreshToken
)
}
}
}
// Basic Auth
install(Auth) {
basic {
credentials {
BasicAuthCredentials(
username = "user",
password = "password"
)
}
}
}超时配置
kotlin
import io.ktor.client.plugins import io.ktor.client.plugins.*
install(HttpTimeout) {
requestTimeoutMillis = 30_000
connectTimeoutMillis = 10_000
socketTimeoutMillis = 30_000
}重试机制
kotlin
import io.ktor.client.plugins.*
install(HttpRequestRetry) {
retryOnServerErrors(maxRetries = 3)
exponentialDelay()
modifyRequest { request ->
request.headers.append("X-Retry-Count", retryCount.toString())
}
}高级用法
流式下载
kotlin
import io.ktor.utils.io.*
import java.io.File
suspend fun downloadFile(url: String, outputFile: File) {
client.get(url).bodyAsChannel().copyTo(outputFile.outputStream())
}
// 带进度
suspend fun downloadWithProgress(
url: String,
outputFile: File,
onProgress: (Float) -> Unit
) {
val response = client.get(url)
val contentLength = response.headers["Content-Length"]?.toLong() ?: -1L
var downloaded = 0L
val channel = response.bodyAsChannel()
outputFile.outputStream().use { output ->
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
while (!packet.isEmpty) {
val bytes = packet.readBytes()
output.write(bytes)
downloaded += bytes.size
if (contentLength > 0) {
onProgress(downloaded.toFloat() / contentLength)
}
}
}
}
}并发请求
kotlin
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
suspend fun fetchMultipleUsers(ids: List<String>): List<User> = coroutineScope {
ids.map { id ->
async {
client.get("https://api.example.com/users/$id").body<User>()
}
}.awaitAll()
}WebSocket 支持
kotlin
import io.ktor.client.plugins.websocket.*
import io.ktor.websocket.*
install(WebSockets)
suspend fun connectWebSocket() {
client.webSocket("wss://example.com/ws") {
// 发送消息
send("Hello, Server!")
// 接收消息
for (frame in incoming) {
when (frame) {
is Frame.Text -> {
val text = frame.readText()
println("Received: $text")
}
else -> {}
}
}
}
}最佳实践
✅ 实践 1:封装 API 服务
kotlin
class UserApiService(private val client: HttpClient) {
private val baseUrl = "https://api.example.com"
suspend fun getUsers(): List<User> {
return client.get("$baseUrl/users").body()
}
suspend fun getUser(id: String): User {
return client.get("$baseUrl/users/$id").body()
}
suspend fun createUser(user: User): User {
return client.post("$baseUrl/users") {
setBody(user)
}.body()
}
}
// 使用
val api = UserApiService(createHttpClient())
val users = api.getUsers()✅ 实践 2:统一错误处理
kotlin
sealed class ApiResult<out T> {
data class Success<T>(val data: T) : ApiResult<T>()
data class Error(val code: Int, val message: String) : ApiResult<Nothing>()
}
suspend inline fun <reified T> safeApiCall(
crossinline call: suspend () -> T
): ApiResult<T> {
return try {
ApiResult.Success(call())
} catch (e: ClientRequestException) {
ApiResult.Error(e.response.status.value, e.message)
} catch (e: Exception) {
ApiResult.Error(-1, e.message ?: "Unknown error")
}
}
// 使用
val result = safeApiCall { client.get("/users").body<List<User>>() }
when (result) {
is ApiResult.Success -> println(result.data)
is ApiResult.Error -> println("Error: ${result.message}")
}✅ 实践 3:单例 HttpClient
kotlin
// ✅ 复用 HttpClient 实例
object ApiClient {
val instance: HttpClient by lazy { createHttpClient() }
}
// ❌ 避免频繁创建
suspend fun fetchData() {
val client = HttpClient() // 每次都创建新实例,浪费资源
client.get("/data")
client.close()
}✅ 实践 4:资源释放
kotlin
// 应用关闭时释放
fun onAppShutdown() {
ApiClient.instance.close()
}Ktor Client 提供了强大而灵活的跨平台网络能力,配合 Kotlin 协程和序列化,可以构建高效、类型安全的网络层。