第一篇:Ktor Client 到底是什么?从 Retrofit 迁移理解 Ktor 网络请求架构

在 Android 开发中,我们通常使用:

复制代码
Retrofit + OkHttp

来完成网络请求。

但进入 KMP(Kotlin Multiplatform)之后,会发现一个新的网络框架:

复制代码
Ktor Client

第一次接触 Ktor 时,很容易产生几个疑问:

复制代码
Ktor 是不是相当于 Retrofit?

HttpClient 是不是相当于 OkHttpClient?

Engine 又是什么?

为什么 Android 用 OkHttp,
iOS 却可以使用 Darwin?

ContentNegotiation、Logging、Auth
这些 install(...) 又是什么?

实际上,Ktor 和 Retrofit 的设计思路并不完全一样

理解 Ktor 最关键的一步,不是先记住 get()post() 怎么写,而是先理解它的整体结构:

复制代码
HttpClient
    +
Plugin
    +
Engine

只要这一层理解清楚,后面的 JSON 解析、公共 Header、Token、日志、超时、重试等功能都会很好理解。


一、先从熟悉的 Retrofit 说起

传统 Android 项目中,我们可能会这样定义接口:

复制代码
interface UserApi {

    @GET("user/{id}")
    suspend fun getUser(
        @Path("id") id: Long
    ): User
}

然后创建 Retrofit:

复制代码
val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .client(okHttpClient)
    .addConverterFactory(...)
    .build()

这里实际上存在几个不同角色。

复制代码
Retrofit
    │
    ├── 定义接口
    ├── 拼装请求
    ├── Converter
    │
    ↓
OkHttp
    │
    ↓
真正执行 HTTP 请求

可以简单理解为:

Retrofit 更偏向于"HTTP 接口描述和封装",OkHttp 才是底层真正执行网络请求的客户端。

例如:

复制代码
@GET("user/{id}")

Retrofit 根据注解帮我们生成真正的 HTTP 请求。

所以 Android 开发者长期使用 Retrofit 后,很容易形成一种思维:

复制代码
定义 Interface
        ↓
写 @GET / @POST
        ↓
Retrofit 自动生成实现

而 Ktor 并不是这种设计。


二、Ktor 没有 Retrofit 那种 Interface + 注解模式

Ktor Client 最直接的网络请求是这样的:

复制代码
val response = client.get(
    "https://example.com/user/1001"
)

POST 也是类似:

复制代码
client.post(
    "https://example.com/user"
) {
    setBody(request)
}

你会发现:

复制代码
没有 @GET
没有 @POST
没有 Retrofit Interface

而是直接:

复制代码
client.get()
client.post()
client.put()
client.delete()

这是一种 DSL 风格的网络请求方式。

因此,从 Retrofit 迁移到 Ktor 时,第一件需要改变的事情就是:

复制代码
Retrofit:

Interface
    ↓
Annotation
    ↓
生成请求


Ktor:

HttpClient
    ↓
DSL
    ↓
直接构造请求

这并不意味着正式项目里所有地方都直接写:

复制代码
client.get(...)

后面我们仍然可以自己封装:

复制代码
ApiService
    ↓
HttpRequest
    ↓
HttpClient

只是 Ktor 本身并不强制你使用 Retrofit 那种接口声明式结构。


三、Ktor 的核心:HttpClient

Ktor Client 最核心的对象就是:

复制代码
HttpClient

例如:

复制代码
val client = HttpClient()

或者指定一个 Engine:

复制代码
val client = HttpClient(OkHttp)

官方文档也明确指出,HttpClient 创建时可以显式传入 Engine;如果不传,则会根据项目中引入的 Engine 依赖选择合适的实现。

可以先粗略理解为:

HttpClient 是整个 Ktor 网络系统的入口。

以后所有:

复制代码
GET
POST
PUT
DELETE

JSON
Header
Token
Logging
Timeout
Retry

最终都会围绕这个 HttpClient 工作。

例如:

复制代码
val client = HttpClient(OkHttp) {

    expectSuccess = true

    install(ContentNegotiation) {
        json()
    }

    install(Logging)
}

这时候我们已经开始看到 Ktor 的整体设计了:

复制代码
HttpClient
 │
 ├── 基础配置
 │
 ├── Plugin
 │
 └── Engine

四、Engine 是什么?

这是刚接触 Ktor 时最容易困惑的概念。

假设我们写:

复制代码
HttpClient(OkHttp)

这里:

复制代码
HttpClient

和:

复制代码
OkHttp

不是一回事。

其中:

复制代码
HttpClient

负责提供统一的 Ktor 网络 API。

而:

复制代码
OkHttp

是一个 Engine

Engine 可以理解成:

真正负责和操作系统网络能力交互、执行 HTTP 请求的底层实现。

Ktor Client 本身是多平台的,目前可以运行在 JVM、Android、JavaScript/Wasm 和 Kotlin/Native 等目标上,而不同平台可以使用不同 Engine。官方文档例如列出了 Android/JVM 可使用 OkHttp,Apple 平台可使用 Darwin,JavaScript 可使用 Js,同时 CIO 也覆盖多个平台。

于是整个结构变成:

复制代码
你的业务代码
     ↓
Ktor HttpClient
     ↓
Engine
     ↓
系统网络能力

五、为什么 KMP 需要 Engine?

这其实正是 Ktor 非常适合 KMP 的原因。

假设现在有一个 KMP 项目:

复制代码
commonMain
androidMain
iosMain

我们希望大量网络代码都放在:

复制代码
commonMain

例如:

复制代码
suspend fun getUser(): User {
    return client
        .get("/user")
        .body()
}

问题来了。

Android 和 iOS 底层网络环境完全不同。

Android 可以使用:

复制代码
OkHttp

iOS 则可以使用:

复制代码
Darwin Engine

因此 Ktor 在中间做了一层抽象:

复制代码
             commonMain

             HttpClient
                 │
          client.get(...)
                 │
        ┌────────┴────────┐
        ↓                 ↓
    Android              iOS
        ↓                 ↓
    OkHttp              Darwin
        ↓                 ↓
 Android 网络          Apple 网络

官方也直接给出了类似的 KMP 用法:Android source set 可以添加 Android/OkHttp 等 Engine,iOS source set 使用 Darwin,运行时由对应平台的 Engine 执行请求。

这就是 Ktor 在 KMP 中非常重要的一层设计:

上层 API 跨平台,底层网络实现平台化。


六、这其实很像 KMP 的 expect / actual 思想

虽然 Ktor Engine 并不等于 expect / actual,但是从架构思想上非常像。

KMP 经常解决这样的问题:

复制代码
commonMain
定义统一能力

        ↓

androidMain
Android 实现

iosMain
iOS 实现

Engine 也是类似:

复制代码
                 HttpClient

                     ↓

              统一网络 API

                     ↓

       ┌─────────────┴────────────┐

       ↓                          ↓

    OkHttp                     Darwin

       ↓                          ↓

   Android                       iOS

因此业务层根本不需要关心:

复制代码
Android 到底怎么发请求?

iOS 到底怎么发请求?

它只需要:

复制代码
client.get(...)

这就是抽象层的意义


七、那么 Plugin 又是什么?

理解 Engine 后,再看第二个重要概念:

复制代码
Plugin

假设我们要处理 JSON。

Ktor 可以:

复制代码
install(ContentNegotiation) {
    json()
}

想打印网络日志:

复制代码
install(Logging)

想处理认证:

复制代码
Auth

想配置超时:

复制代码
HttpTimeout

想自动重试:

复制代码
HttpRequestRetry

这些横切能力在 Ktor Client 中主要通过 Plugin 提供。官方将序列化、日志、认证等通用能力都归入 Client Plugins,并通过 install(...) 安装到 HttpClient 中。

所以:

复制代码
val client = HttpClient(OkHttp) {

    install(ContentNegotiation) {
        json()
    }

    install(Logging)

    install(HttpTimeout)

    install(Auth)

    install(HttpRequestRetry)
}

可以理解为:

复制代码
HttpClient
 │
 ├── ContentNegotiation
 │      ↓
 │     JSON
 │
 ├── Logging
 │      ↓
 │     日志
 │
 ├── Auth
 │      ↓
 │     Token
 │
 ├── HttpTimeout
 │      ↓
 │     超时
 │
 └── HttpRequestRetry
        ↓
       重试

因此:

Plugin 就是在 HttpClient 上安装各种网络能力。


八、这和 OkHttp Interceptor 有点像,但不能完全画等号

Android 开发中我们非常熟悉:

复制代码
OkHttpClient.Builder()
    .addInterceptor(...)

比如:

复制代码
TokenInterceptor
LoggingInterceptor
HeaderInterceptor
RetryInterceptor

所以看到 Ktor:

复制代码
install(Logging)
install(Auth)
install(DefaultRequest)

会觉得:

这不就是 Interceptor 吗?

从"解决横切逻辑"的角度来看确实很像:

复制代码
请求
 ↓
公共 Header
 ↓
Token
 ↓
Logging
 ↓
真正请求

但是概念上不能直接说:

复制代码
Plugin = Interceptor

因为 Ktor Plugin 的能力范围更广。

它不仅可以处理请求,还可以参与:

复制代码
请求生命周期
响应生命周期
序列化
认证
异常处理
日志
重试

官方还允许创建自定义 Client Plugin,通过请求和响应阶段实现可复用能力。

因此更准确的理解应该是:

复制代码
Interceptor
    ↓
可以帮助理解 Plugin

但:

Plugin > 单纯请求拦截

九、把 Retrofit + OkHttp 映射到 Ktor

现在可以做一个非常重要的对照。

Android 传统方案

复制代码
Retrofit
 │
 ├── @GET
 ├── @POST
 ├── Converter
 │
 ↓
OkHttpClient
 │
 ├── Interceptor
 ├── Timeout
 ├── Connection
 │
 ↓
HTTP

而到了 Ktor:

复制代码
Ktor HttpClient
 │
 ├── get / post / put / delete
 │
 ├── ContentNegotiation
 ├── DefaultRequest
 ├── Logging
 ├── Auth
 ├── HttpTimeout
 ├── Retry
 │
 ↓
Engine
 │
 ├── OkHttp
 ├── Darwin
 ├── CIO
 └── Js
 │
 ↓
HTTP

因此千万不要简单地理解为:

复制代码
Ktor = Retrofit

更合适的理解是:

Ktor Client 自己提供了比较完整的 HTTP Client 抽象,并通过 Engine 对接各个平台。

在 Android 上:

复制代码
Ktor HttpClient
        ↓
OkHttp Engine
        ↓
OkHttp

也就是说:

Ktor 并没有要求 Android 放弃 OkHttp。

反而可以把 OkHttp 作为 Android 平台下的执行 Engine。


十、为什么这一套特别适合 KMP?

现在来看问题就很清楚了。

如果直接使用 Retrofit:

复制代码
commonMain
    ↓
?

Retrofit 本身主要面向 JVM/Android 生态,并不能直接作为完整的 KMP 跨平台网络抽象。

而 Ktor 的设计天然就是:

复制代码
            commonMain

             HttpClient

                 ↓

          公共网络逻辑

                 ↓

      ┌──────────┼──────────┐
      ↓          ↓          ↓
   Android      iOS        Web
      ↓          ↓          ↓
   OkHttp     Darwin     Js/CIO

因此我们可以把大量逻辑写在 commonMain

复制代码
ApiService
Request
Response
DTO
JSON
异常处理
Token 逻辑
Repository

只有真正和平台网络实现有关的部分留给各个平台。

这也是 KMP 的核心思想:

能共享的逻辑尽量共享,需要平台能力时再下沉到平台层。


十一、一个最简单的 Ktor 网络结构

先不用考虑复杂架构。

最简单可以只有:

复制代码
val client = HttpClient()

然后:

复制代码
suspend fun getUser() {
    val response = client.get(
        "https://example.com/user/1001"
    )
}

此时结构只有:

复制代码
业务代码
   ↓
HttpClient
   ↓
Engine
   ↓
HTTP

但正式项目肯定不会停留在这里。

我们还需要:

复制代码
JSON
公共 BaseUrl
公共 Header
Token
Logging
Timeout
Retry
异常转换
统一 Response

于是最终会慢慢演化成:

复制代码
              ApiService
                  ↓
              HttpRequest
                  ↓
              HttpClient
                  │
      ┌───────────┼───────────┐
      ↓           ↓           ↓
DefaultRequest  Logging     Auth
      ↓           ↓           ↓
ContentNegotiation       Timeout / Retry
                  │
                  ↓
                Engine
                  │
        ┌─────────┴─────────┐
        ↓                   ↓
     Android               iOS
        ↓                   ↓
     OkHttp               Darwin

这就是后面整个 Ktor 网络系列真正要搭起来的东西。


十二、HttpClient 应该每次请求都创建吗?

不要。

例如下面这种方式并不好:

复制代码
suspend fun getUser() {
    val client = HttpClient()

    client.get(...)
}

然后另一个接口:

复制代码
suspend fun getOrder() {
    val client = HttpClient()

    client.get(...)
}

HttpClient 本身持有连接、线程以及协程相关资源,官方也明确指出创建 HttpClient 并不是一个廉价操作,存在多次请求时应该复用实例,并在整个 Client 不再使用时再调用 close() 释放资源。

因此正式项目一般会:

复制代码
创建一个 HttpClient
        ↓
统一配置
        ↓
整个网络层复用

例如:

复制代码
class HttpClientFactory {

    fun create(): HttpClient {
        return HttpClient {
            // 网络配置
        }
    }
}

然后通过依赖注入等方式复用。

这和 Android 项目通常只维护一个:

复制代码
OkHttpClient
Retrofit

的思想是一样的。


十三、现在重新理解 Ktor

学到这里,可以先忘掉具体 API。

只记住三个核心词:

复制代码
HttpClient
Plugin
Engine

它们之间的关系:

复制代码
                HttpClient
                    │
          统一的网络请求入口
                    │
      ┌─────────────┴─────────────┐
      ↓                           ↓
    Plugin                      Engine
      ↓                           ↓
扩展网络能力                 真正执行网络请求
      ↓                           ↓
JSON                         OkHttp
Logging                      Darwin
Auth                         CIO
Timeout                      Js
Retry
...

简单概括:

HttpClient

负责:

复制代码
整个 Ktor Client 的入口

Plugin

负责:

复制代码
给 HttpClient 增加能力

例如:

复制代码
JSON
Logging
Auth
Timeout
Retry
DefaultRequest

Engine

负责:

复制代码
真正执行网络请求

例如:

复制代码
Android → OkHttp
iOS     → Darwin
Web     → Js / CIO 等

十四、从 Retrofit 思维迁移到 Ktor

如果之前长期使用:

复制代码
Retrofit + OkHttp

那么学习 Ktor 最容易卡住的地方,其实不是 Kotlin 代码。

而是思维方式发生了变化。

以前:

复制代码
定义 Retrofit Interface
        ↓
通过注解描述 HTTP
        ↓
Retrofit 创建实现
        ↓
OkHttp 请求

现在:

复制代码
HttpClient
        ↓
DSL 构造请求
        ↓
Plugin 处理公共能力
        ↓
Engine 执行请求

最终可以记成一句话:

Ktor Client 提供跨平台统一网络 API,Plugin 提供网络层公共能力,Engine 负责适配不同平台并真正执行 HTTP 请求。

理解了这一句话,Ktor 的整体框架基本就立住了。


十五、本篇总结

这篇没有急着学习:

复制代码
GET 怎么写
POST 怎么写
JSON 怎么解析

而是先回答了一个更重要的问题:

Ktor Client 到底是什么?

整个 Ktor 网络体系可以先记成:

复制代码
业务层
   ↓
ApiService
   ↓
HttpClient
   │
   ├── Plugin
   │     ├── JSON
   │     ├── Logging
   │     ├── Auth
   │     ├── Timeout
   │     └── Retry
   │
   ↓
Engine
   │
   ├── Android → OkHttp
   ├── iOS → Darwin
   └── Web → 对应 Engine
   ↓
HTTP

如果从 Retrofit 迁移过来,可以暂时这样理解:

复制代码
Retrofit + OkHttp 的很多网络层职责

        ↓

在 KMP 中重新组织为

        ↓

Ktor HttpClient
     +
Plugin
     +
Engine

这也是后面学习所有 Ktor Client 能力的基础。


下一篇

《Ktor 网络请求基础:GET、POST、参数与请求体》

下一篇开始真正写请求,依次讲清楚:

复制代码
GET
POST
PUT
DELETE

Path 参数
Query 参数
Header
Request Body
HttpResponse

并开始建立从:

复制代码
client.get(...)

到正式项目网络层封装的第一步。

相关推荐
zlinear数据采集卡1 小时前
D223的PWM电机控制:6路独立脉冲+加减速算法深度解析
arm开发·stm32·嵌入式硬件·算法·fpga开发·架构
数据知道1 小时前
反序列化漏洞:Java、PHP、Python 三条线各讲透
java·网络·python·安全·网络安全·php
香菜TTT1 小时前
主从架构锁丢失问题-----RedLock
架构
江畔柳前堤1 小时前
Function Calling 与 Tool Calling:从认知到工程的全景深度解析
开发语言·网络·人工智能·深度学习·算法·机器学习·php
LabVIEW开发2 小时前
LabVIEW 通过以太网控制 Yokogawa WT3000 功率分析仪:tmctl.dll 与原始套接字方案
网络·labview·labview知识·labview功能·labview程序
这个DBA有点耶2 小时前
DBA进阶之路:从“修数据库”到“管架构债”
数据库·架构·dba
xiaoxiangsiyan3 小时前
企业日常运维高频应用服务全解
运维·网络·云原生·容器·dns
珠***格3 小时前
通信链路全打通:西格电力四可装置的 4G/5G + 加密认证技术详解
大数据·数据库·分布式·5g·架构·能源
那年窗外下的雪.3 小时前
AIDC 学习日志 Day 3:OSPF Underlay 实验、故障排查与深入解析
服务器·网络·php