第一章 Retrofit简介与核心概念
老话重提:Retrofit到底是什么?
这几年面试的时候,我发现很多新人一上来就说“Retrofit是个网络请求框架”。嗯……对,但不全对。
我个人的理解更偏底层一点:Retrofit本质上是OkHttp的封装器,它的核心工作是把你用注解写的Java接口,转化成一个个OkHttp的Call对象。
说白了,它只负责把“方法调用”翻译成“网络请求配置”,真正发请求和接响应的脏活累活,全交给OkHttp去干。
你可能要问:那我自己用OkHttp不就行了?为什么还要套一层Retrofit?
举个例子,我早期的一个项目直接拼OkHttp的Request.Builder,每个接口都要手动写URL、Header、Body。后来接口从30个涨到200个,代码里全是重复的模板,改一个公共Header要翻好几个文件。用Retrofit之后,每个接口只需要一个方法注解,参数、路径全在接口声明里,维护成本直接降了一个量级。
1.1 Retrofit是什么
定义:Retrofit是一个类型安全的REST客户端,由Square公司开源。它通过Java注解来描述HTTP请求,并将接口定义自动转换为可执行请求。
一句话总结:你写接口,Retrofit帮你干活——生成代理、拼接URL、序列化参数、发起请求、反序列化响应。
我在实际项目中用它做过一个有意思的事:给一个物联网平台写SDK,需要同时支持JSON和Protocol Buffers两种数据格式。Retrofit的Converter机制让我只改了3行配置就完成了切换——这种灵活性是裸用OkHttp给不了的。
1.2 核心组件
我们一起拆一下Retrofit的“五脏六腑”。记住这四块,后面讲拦截器和缓存就好理解了。
| 组件 | 职责 | 个人经验 |
|---|---|---|
| Retrofit 实例 | 配置基URL、Converter、CallAdapter等,生成接口代理对象 | 我习惯用单例管理Retrofit实例,避免重复创建 |
| 接口定义(Interface) | 用注解声明HTTP方法、路径、参数、Header | 注意路径参数不要拼错,我吃过@Path("id")写反的亏 |
| OkHttpClient | 真正的网络通信层,负责连接、读写、拦截器链 | 一定要复用同一个OkHttpClient,否则缓存和连接池不生效 |
| Converter | 将Java对象转为请求体,将响应体转为Java对象 | GsonConverterFactory最常见,但Kotlin时代我更喜欢Moshi |
| CallAdapter | 将Call对象转换为其他类型(如RxJava的Observable、Kotlin协程的Deferred) | 这个组件常被忽略,但它是实现异步和回调的关键 |
Call和Callback。不过现在协程流行,用suspend方法后连Callback都省了。我这几年新项目直接上协程,接口返回类型直接写ResponseBody或自定义Bean,清爽得很。
1.3 工作流程
Retrofit的工作流程,其实就五步:
- 定义接口:用注解写好每个API的细节
- 创建Retrofit实例:配置baseUrl、OkHttpClient、Converter等
- 生成代理对象:调用
retrofit.create(ApiService.class),Retrofit使用Java动态代理生成实现类 - 构建请求:当调用接口方法时,Retrofit根据注解和方法参数构建一个OkHttp
Request对象 - 执行请求:交给OkHttpClient去真正发送请求,得到响应后通过Converter反序列化返回
我把这个流程画成一张图,方便大家一目了然:
你可能注意到了,中间少了一步——拦截器在哪里?
嗯,拦截器在OkHttpClient.send()这一步介入。你可以把它理解成水管上的阀门,每个请求发出前和响应回来后,都会经过拦截器链。
我们后面会花好几章专门讲这个,别着急。
1.4 一个最小可运行示例
光说不练假把式,咱们写个最简单的Retrofit接口:
public interface GitHubApi {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
// 创建Retrofit实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubApi api = retrofit.create(GitHubApi.class);
Call<List<Repo>> call = api.listRepos("square");
call.enqueue(new Callback<List<Repo>>() {
@Override
public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
// 处理响应
}
@Override
public void onFailure(Call<List<Repo>> call, Throwable t) {
// 处理失败
}
});
/结尾,否则Retrofit会报IllegalArgumentException。如果接口路径以/开头,则baseUrl的结尾斜杠会被忽略——这一点在官方文档里写得很隐晦,我是调试了半小时才发现的。
1.5 核心概念速查表
| 概念 | 说明 | 常见误解 |
|---|---|---|
| @GET / @POST / @PUT / @DELETE | HTTP方法注解,标注在接口方法上 | 有人以为可以混用,实际一个方法只能有一种 |
| @Path | 替换URL中的占位符 | 忘记编码特殊字符?@Path默认不做URL编码,得自己用@Url |
| @Query | 添加查询参数 | 多个同名参数用@QueryMap |
| @Body | 将对象序列化为请求体 | 只能在POST/PUT中使用 |
| @Header / @Headers | 添加请求头 | 静态Header用@Headers,动态用@Header |
这些注解看起来简单,但在和拦截器配合时会产生有趣的化学反应。比如我们在拦截器里添加统一的Authorization头部,再配合@Header的覆盖逻辑,就能实现动态认证的优雅方案——这是后面章节的重头戏,这里先留个念想。