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() 在运行时帮你生成了一个代理对象。每次你调用接口方法,代理对象都会拦截这次调用,然后:
- 解析方法上的注解,拿到请求方法和路径
- 解析方法参数上的注解,绑定参数值
- 构建一个
OkHttp的Request对象 - 调用对应的
CallAdapter和ConverterFactory来处理
我曾经在项目里遇到过一个坑:接口方法签名写错了,返回值类型不对,结果动态代理在运行时才报错,编译期完全看不出来。所以说,接口定义一定要小心,参数名和注解别搞混。
三、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)。
ScalarsConverterFactory 放最后,免得它拦截了JSON解析。
五、一次网络请求的完整生命周期
好了,前面每个部分都讲透了。现在咱们把整个流程串起来,看看一次请求到底走了哪些步骤。
- 你调用
service.listRepos("octocat") - 动态代理拦截,解析
@GET("users/{user}/repos")和@Path("user") - 将参数替换到URL中,生成完整URL:
https://api.github.com/users/octocat/repos - 构建
OkHttp Request,包换方法、URL、Headers等 - 调用CallAdapter:把
Call<List<Repo>>适配成你需要的类型(如果没配,直接用Call) - 执行网络请求:OkHttp同步或异步执行
- 获取响应:返回的原始响应体(JSON字符串)
- 调用ConverterFactory:把JSON字符串反序列化成
List<Repo>对象 - 返回结果:你得到的就是
List<Repo>,或者被CallAdapter包装后的Observable
下面这张图把整个流程画得更清楚,你可以保存下来随时回顾。
六、踩坑经验与最佳实践
说几个我实际遇到过的坑,你们提前避开:
- ConverterFactory 顺序 —— 有次我把
ScalarsConverterFactory放在前面,结果所有响应都被当成字符串返回了,解析全崩。记住:专门的处理放前面,通用的放后面。 - CallAdapter 和 ConverterFactory 的依赖 —— 如果你的返回类型是
Observable<Result<User>>,那ConverterFactory处理的是User,不是Result。别搞混了。 - 动态代理的性能 —— 代理本身开销极小,但如果你在频繁调用的热点路径上用反射解析注解,可能会有一点影响。Retrofit其实在第一次创建Service实例时就把所有方法的信息缓存了,所以放心用。
Call<ResponseBody> 然后手动解析虽然灵活,但会让代码难以维护。用 ConverterFactory 才是正道。
好了,这一章的内容就是这些。理解了注解、动态代理、CallAdapter和ConverterFactory,你就掌握了Retrofit的骨架。后面我们在做Mock回放测试的时候,会在这些关键环节插入拦截器,替换真实网络行为。准备好了吗?