25、网络请求之Retrofit:注解、动态代理、Converter、与RxJava结合
说实话,Retrofit 是我在 Android 项目里用得最多的网络库。没有之一。
为什么?因为它够简单,够优雅。你只需要定义一个接口,加几个注解,网络请求就搞定了。底层它帮你封装了 OkHttp,上层又提供了和 RxJava、协程的无缝衔接。今天我们就把它拆开揉碎了讲清楚。
25.1 Retrofit 的核心思想:用注解定义接口
Retrofit 的核心思路,说白了就是:你只管定义“要什么”,它负责“怎么要”。
你写一个 Java 接口,方法上加上 @GET、@POST 这些注解,Retrofit 就自动帮你生成实现类,发起网络请求,解析返回数据。
核心三要素:
- 注解:描述请求方式、路径、参数、头信息
- 动态代理:运行时生成接口的实现
- Converter:把 JSON 转成 Java 对象
我个人习惯把 API 接口定义放在一个单独的 api 包下,每个模块一个接口文件。这样后期维护起来,你想想看,是不是一目了然?
25.2 常用注解详解
Retrofit 的注解分两类:请求方法注解 和 参数注解。
请求方法注解
| 注解 | 说明 | 示例 |
|---|---|---|
@GET |
GET 请求 | @GET("users/list") |
@POST |
POST 请求 | @POST("users/create") |
@PUT |
PUT 请求 | @PUT("users/update") |
@DELETE |
DELETE 请求 | @DELETE("users/delete") |
@HEAD |
HEAD 请求 | @HEAD("users/check") |
参数注解
| 注解 | 说明 | 示例 |
|---|---|---|
@Path |
替换 URL 路径中的占位符 | @Path("id") int userId |
@Query |
添加查询参数 | @Query("page") int page |
@QueryMap |
多个查询参数(Map) | @QueryMap Map<String, String> options |
@Body |
请求体(自动序列化) | @Body User user |
@Header |
单个请求头 | @Header("Authorization") String token |
@Headers |
多个请求头 | @Headers({"Accept: application/json"}) |
// 一个完整的接口定义示例
public interface ApiService {
@GET("users/{id}")
Call<User> getUser(@Path("id") int userId);
@POST("users/create")
Call<User> createUser(@Body User user);
@GET("users/list")
Call<List<User>> listUsers(@Query("page") int page, @Query("size") int size);
}
我的经验: 路径中的占位符尽量用语义化的名字,比如 {userId} 而不是 {id}。我在项目中遇到过好几次,因为占位符名字太泛,导致多人协作时搞混了参数。
25.3 动态代理:Retrofit 的魔法
你有没有想过,为什么只写一个接口,Retrofit 就能发起网络请求?
答案就是 动态代理。
Retrofit 在运行时,通过 Proxy.newProxyInstance() 为你的接口生成一个代理对象。当你调用接口方法时,代理对象会拦截调用,解析方法上的注解,构建出一个 OkHttp 的 Request 对象,然后发起请求。
// 动态代理的核心逻辑(简化版)
public <T> T create(final Class<T> service) {
return (T) Proxy.newProxyInstance(
service.getClassLoader(),
new Class<?>[] { service },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 解析方法注解,构建请求
ServiceMethod<?> serviceMethod = loadServiceMethod(method);
OkHttpCall<?> okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.adapt(okHttpCall);
}
}
);
}
说白了,动态代理就是 Retrofit 的“发动机”。没有它,注解就只是一堆标记,毫无意义。
注意: 动态代理只能代理接口,不能代理类。所以你的 API 定义必须是 interface,不能是 class。我曾经见过有人写成抽象类,结果运行时报错,排查了半天。
25.4 Converter:数据转换的桥梁
网络请求返回的通常是 JSON 或 XML 字符串。但我们的代码里需要的是 Java 对象。谁来做这个转换?
就是 Converter。
Retrofit 本身不负责序列化/反序列化,它只定义了 Converter 接口。你可以选择 Gson、Jackson、Moshi 等任意库来实现。
// 添加 Gson 转换器
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
// 添加 Jackson 转换器
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(JacksonConverterFactory.create())
.build();
我的建议: 团队项目里统一用一个序列化库。我见过一个项目里 Gson 和 Jackson 混用,结果日期格式不统一,排查问题花了一整天。统一用 Gson 吧,社区支持好,坑也少。
25.5 与 RxJava 结合:响应式编程
Retrofit 默认返回 Call<T> 对象。但如果你用了 RxJava,就可以返回 Observable<T>、Single<T>、Flowable<T> 等。
这样做的好处是什么?
- 链式调用:可以用
.map()、.flatMap()做数据转换 - 线程切换:
.subscribeOn(Schedulers.io())和.observeOn(AndroidSchedulers.mainThread()) - 错误处理:
.onErrorResumeNext()、.retry()等操作符
// 添加 RxJava 适配器
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
// 接口定义
public interface ApiService {
@GET("users/{id}")
Single<User> getUser(@Path("id") int userId);
}
// 调用
apiService.getUser(123)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(user -> {
// 更新 UI
}, throwable -> {
// 处理错误
});
避坑指南: 我曾经在 .subscribe() 里忘记处理 Disposable,导致 Activity 销毁后网络回调还在执行,引发了内存泄漏。记得用 CompositeDisposable 统一管理,在 onDestroy() 里调用 clear()。
25.6 知识体系总览
下面这张图,帮你把 Retrofit 的核心脉络理清楚:
25.7 完整示例:从定义到调用
最后,我们串起来跑一个完整的例子。假设我们要获取用户列表:
// 1. 定义接口
public interface UserApi {
@GET("users")
Single<List<User>> getUsers(@Query("page") int page);
}
// 2. 构建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.build();
// 3. 创建接口实例
UserApi userApi = retrofit.create(UserApi.class);
// 4. 发起请求
CompositeDisposable disposable = new CompositeDisposable();
disposable.add(
userApi.getUsers(1)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(users -> {
// 更新 RecyclerView
adapter.setData(users);
}, error -> {
// 显示错误提示
Toast.makeText(context, "加载失败", Toast.LENGTH_SHORT).show();
})
);
// 5. 别忘了在 onDestroy 中清理
@Override
protected void onDestroy() {
super.onDestroy();
disposable.clear();
}
重要提醒: baseUrl 必须以 / 结尾。否则 Retrofit 会抛出 IllegalArgumentException。我刚开始用的时候踩过这个坑,排查了半小时才发现是少了个斜杠。
好了,Retrofit 的核心内容就这些。注解定义请求,动态代理生成实现,Converter 做数据转换,RxJava 做异步处理。这四个点吃透了,Retrofit 你就掌握了八成。
剩下的两成,是在实战中慢慢积累的。比如统一错误处理、添加拦截器、配合协程使用等等。那些我们后面再聊。
公众号:蓝海资料掘金营,微信deep3321