Retrofit核心原理:从注解到网络请求的完整生命周期

说实话,很多人用Retrofit好几年,却没真正搞懂它背后到底干了什么。我刚开始也是照着文档写个接口定义,加几个注解就完事了。直到有一次线上接口莫名其妙返回空数据,排查了半天才发现是ConverterFactory配错了——嗯,从那以后我下定决心把源码啃了一遍。

这节课,咱们就来扒一扒Retrofit的核心原理。从你写的那行 @GET("users") 开始,到最终拿到JSON数据,中间到底发生了什么。

一、注解:你的接口声明,就是Retrofit的配置清单

你写的每一个接口方法,其实就是在告诉Retrofit三件事:

  • 请求方法 — GET、POST、PUT、DELETE 等
  • 请求路径 — 相对URL,可以带路径参数 {id}
  • 请求参数 — Query、Body、Header、Path等

举个例子:

public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

这个 @GET 注解,表面上看只是个标记。但实际上,Retrofit会在编译期或运行时把它解析成一个 请求构建器。我个人习惯把注解理解成“元数据”,它告诉框架:你要发GET请求,路径里有个变量叫user,它来自方法参数。

小提示:记得看官方文档里的 @Headers、@FormUrlEncoded 等注解。你想想看,一个接口可能有十几二十个参数,用注解管理比手写HttpURLConnection优雅太多了。

二、动态代理:为什么你写个接口就能直接调用?

你肯定好奇过:

我明明只定义了一个 interface,没有写实现类,为什么 service.listRepos(...) 就能发起网络请求?

答案就是——动态代理。

Retrofit通过 Proxy.newProxyInstance() 在运行时帮你生成了一个代理对象。每次你调用接口方法,代理对象都会拦截这次调用,然后:

  1. 解析方法上的注解,拿到请求方法和路径
  2. 解析方法参数上的注解,绑定参数值
  3. 构建一个 OkHttp 的 Request 对象
  4. 调用对应的 CallAdapter 和 ConverterFactory 来处理

我曾经在项目里遇到过一个坑:接口方法签名写错了,返回值类型不对,结果动态代理在运行时才报错,编译期完全看不出来。所以说,接口定义一定要小心,参数名和注解别搞混。

核心要点:动态代理让Retrofit不需要写任何实现类。你只管定义接口,剩下的交给代理。

三、CallAdapter:把Call转成你想要的类型

默认情况下,Retrofit返回的是 Call<T>,比如 Call<List<Repo>>。但很多项目想用 RxJava 的 Observable,或者 Kotlin协程 的 suspend 函数,怎么办呢?

这时候 CallAdapter 就派上用场了。它是个适配器,能把 Call<T> 转换成你想要的任意类型。比如:

CallAdapter 实现 转换结果 适用场景
RxJava2CallAdapter Observable<T> / Single<T> 响应式编程
CoroutineCallAdapter suspend 函数 Kotlin协程
GuavaCallAdapter ListenableFuture<T> Guava异步

我个人更推荐用 Kotlin协程,因为代码更简洁,而且天然支持取消。但如果你项目里还在用RxJava,也别急着改,稳定优先。

四、ConverterFactory:把JSON/XML变成Java对象

网络返回的数据都是字符串或字节流。怎么变成你的 Repo 对象?这就要靠 ConverterFactory。

常见的实现:

  • GsonConverterFactory — 用Gson解析JSON,最常用
  • MoshiConverterFactory — Square出的,性能更好
  • JacksonConverterFactory — 功能全,但包大
  • ScalarsConverterFactory — 直接返回原始字符串

我曾经因为 GsonConverterFactory 和 MoshiConverterFactory 混用,导致同一个接口解析出来的字段名大小写不同——惨痛教训。记住:一个Retrofit实例只能添加一个JSON转换工厂,但可以同时添加多个不同类型(比如一个处理JSON,一个处理String)。

警告:添加ConverterFactory的顺序很重要!Retrofit会按添加顺序遍历,找到第一个能处理该类型的工厂就停止。所以一般先把 ScalarsConverterFactory 放最后,免得它拦截了JSON解析。

五、一次网络请求的完整生命周期

好了,前面每个部分都讲透了。现在咱们把整个流程串起来,看看一次请求到底走了哪些步骤。

  1. 你调用 service.listRepos("octocat")
  2. 动态代理拦截,解析 @GET("users/{user}/repos") 和 @Path("user")
  3. 将参数替换到URL中,生成完整URL:https://api.github.com/users/octocat/repos
  4. 构建 OkHttp Request,包换方法、URL、Headers等
  5. 调用CallAdapter:把 Call<List<Repo>> 适配成你需要的类型(如果没配,直接用Call)
  6. 执行网络请求:OkHttp同步或异步执行
  7. 获取响应:返回的原始响应体(JSON字符串)
  8. 调用ConverterFactory:把JSON字符串反序列化成 List<Repo> 对象
  9. 返回结果:你得到的就是 List<Repo>,或者被CallAdapter包装后的Observable

下面这张图把整个流程画得更清楚,你可以保存下来随时回顾。

Retrofit 一次网络请求生命周期 ① 调用接口方法 ② 动态代理解析注解 ③ 构建OkHttp Request ④ CallAdapter适配类型 ⑤ OkHttp执行网络请求 ⑥ ConverterFactory反序列化 ⑦ 返回结果给调用方 注解描述请求 → 动态代理生成实现 → CallAdapter/Apapter工厂处理 → OkHttp执行 → ConverterFactory解析 最终返回给调用方的类型由CallAdapter决定,数据格式由ConverterFactory决定

六、踩坑经验与最佳实践

说几个我实际遇到过的坑,你们提前避开:

  • ConverterFactory 顺序 —— 有次我把 ScalarsConverterFactory 放在前面,结果所有响应都被当成字符串返回了,解析全崩。记住:专门的处理放前面,通用的放后面。
  • CallAdapter 和 ConverterFactory 的依赖 —— 如果你的返回类型是 Observable<Result<User>>,那 ConverterFactory 处理的是 User,不是 Result。别搞混了。
  • 动态代理的性能 —— 代理本身开销极小,但如果你在频繁调用的热点路径上用反射解析注解,可能会有一点影响。Retrofit其实在第一次创建Service实例时就把所有方法的信息缓存了,所以放心用。
我的建议:写接口时,尽量让返回值类型明确。比如直接用 Call<ResponseBody> 然后手动解析虽然灵活,但会让代码难以维护。用 ConverterFactory 才是正道。

好了,这一章的内容就是这些。理解了注解、动态代理、CallAdapter和ConverterFactory,你就掌握了Retrofit的骨架。后面我们在做Mock回放测试的时候,会在这些关键环节插入拦截器,替换真实网络行为。准备好了吗?