2、Retrofit基础回顾:注解、接口定义、OkHttp集成

好,咱们正式开始第二讲。这一节我会把 Retrofit 最核心的三个东西串一遍:注解体系、接口定义方式、以及它怎么跟 OkHttp 勾搭上的。你可能会觉得这些是基础,但说实话,很多 Mock 方案的坑都出在对这三个部分的误解上。先扎扎实实过一遍,后面再讲拦截和回放,你会感觉特别顺。

2.1 注解:Retrofit 的灵魂

Retrofit 本质上就是个注解处理器。你写一个接口,加上几个注解,它就能自动帮你把 HTTP 请求拼好。我自己刚开始用的时候也觉得挺神奇——“这不就是动态代理嘛?” 对,但注解才是真正驱动它工作的 DNA。

注解一共分三类,我按使用频率给大家排个序:

分类 常用注解 作用
方法注解 @GET、@POST、@PUT、@DELETE 指定 HTTP 方法和请求路径
参数注解 @Query、@Path、@Body、@Header 绑定请求参数、路径变量、请求体或头部
标记注解 @FormUrlEncoded、@Multipart、@Streaming 声明请求的编码格式或特殊行为

举个例子,咱们定义一个获取用户信息的接口:

public interface UserService {
    @GET("users/{id}")
    Call<User> getUser(@Path("id") int userId);
}

上面这段代码,Retrofit 会怎么处理?它会解析 @GET("users/{id}"),然后把 {id} 替换成你传入的 userId,最终发起一个 GET 请求到 /users/123。这是最标准的用法。

📌 实战小贴士:我个人习惯把 URL 路径常量定义在接口的静态字段里,比如 String BASE_USERS = "users/{id}"。这样做的好处是避免硬编码散落在各个调用点,后面做 Mock 匹配路由时也方便统一管理。

2.2 接口定义:不仅仅是写个 interface

Retrofit 的接口定义,说白了就是你跟后端的一次“合约”声明。我在项目中见过不少人把接口写得很随意,结果后期维护像噩梦。来,看看规范的写法应该长什么样。

先看返回类型。Retrofit 支持好几种:

  • Call<T> —— 最经典,支持同步/异步切换
  • Response<T> —— 直接拿到完整响应,包含状态码、头部、body
  • Observable<T> / Single<T> —— 配合 RxJava 做响应式
  • suspend fun —— Kotlin 协程时代的首选

我个人现在在 Kotlin 项目里几乎只用协程,代码简洁太多了:

interface UserService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") int userId): User
}

写接口的时候,还有两个容易忽视的点:

  • 泛型要精确:不要为了省事写成 Call<Object>,后面反序列化会出问题。
  • 接口粒度要适中:一个接口别塞太多方法,我习惯按业务模块拆分,比如 UserService、OrderService、PaymentService。这样测试的时候可以单独 Mock,隔离性好。
⚠️ 曾经踩过的坑:我曾经把一个返回 Call<ResponseBody> 的接口用在文件下载上,结果忘了加 @Streaming 注解。小文件没事,一遇到大文件直接 OOM。原因很简单——Retrofit 默认会把整个 response body 读进内存。如果你要做二进制数据流的 Mock,一定要记住 @Streaming 这个标记。

2.3 OkHttp 集成:Retrofit 的底层引擎

有一种常见的误解,觉得 Retrofit 是独立工作的。其实它只是一个“翻译层”,真正的网络请求是交给 OkHttp 执行的。你想想看,Retrofit 本身没有连接池、没有拦截器链、没有超时控制,这些全是 OkHttp 的活儿。

在构建 Retrofit 实例的时候,我们通常会传入一个自定义的 OkHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .addInterceptor(new LoggingInterceptor())
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

看到这里没?.client(client) 这一行就是集成的关键。Retrofit 拿到接口定义后,会生成一个 OkHttpCall 对象,这个对象内部直接调用了 OkHttp 的 RealCall 去执行网络请求。换句话说,你在 OkHttp 上加的所有拦截器,都会作用到 Retrofit 发出的每个请求上。

对我来说,这就是实现 Mock 回放的“手术刀口”。咱们后面的课程里,AddInterceptor 会是我们插入 Mock 逻辑的核心位置。你只需要在拦截器里判断:是走真实请求,还是走本地录制好的数据。

收个尾,我整理了一张图,把这三者的关系画出来了。你看完应该会清晰很多:

Retrofit + OkHttp 完整请求流程 Retrofit + OkHttp 完整请求流程 App / Client Retrofit 层 接口注解解析 动态代理生成 OkHttp 层 拦截器链 (可插入 Mock) 网络 Executor 真实网络 / Mock 数据源 核心要点 • 注解驱动请求拼接 • 动态代理+OkHttpCall • 拦截器是 Mock 入口

这张图里其实已经暗示了咱们后面的 Mock 回放要怎么做。你看,拦截器链就是那扇门——你可以选择让它走真实的网络,也可以把本地录好的数据直接塞进去。Retrofit 上层完全感知不到差异。

这一章就到这。你只需要记住三个层次:注解定义接口、Retrofit 生成代理、OkHttp 执行请求。后面每一节都会回来反复引用这个结构。