11.02 网络编程-Retrofit
11.01 的 OkHttp 已经能发请求了,但每个接口都要手拼 URL、构造 RequestBody、手动解析 JSON——样板代码多且易错。Retrofit 用"注解声明接口"的方式消灭这些样板:接口定义即接口文档,翻译与解析全部自动化。它与 OkHttp 是分工而非竞争:Retrofit 负责声明与转换,OkHttp 负责执行。
# 一、分层架构:声明 → 翻译 → 执行
理解这张图就理解了 Retrofit 的全部:你只写①接口声明,②动态代理把注解翻译成 OkHttp Request,③OkHttp 执行(拦截器照常生效),④转换器把响应体解析成对象,⑤回调给你。所以 11.01 的所有 OkHttp 知识(拦截器、连接池、超时)在 Retrofit 项目里原样适用——Retrofit 的 OkHttpClient 就是你传入的那个单例。
# 二、基本使用
# 2.1 依赖
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // Gson转换器(12章数据解析详讲Gson)
implementation 'com.squareup.okhttp3:okhttp:4.10.0' // Retrofit基于OkHttp
# 2.2 数据实体与接口声明
/**
* 用户数据实体(与后端JSON字段对应).
*/
public class User {
private long id;
private String name;
private int age;
// getter/setter省略(Gson通过反射赋值,需有无参构造与getter)
}
/**
* 用户接口:注解声明即接口文档.
*/
public interface UserApi {
// GET /api/users/{id} —— @Path替换URL占位符
@GET("users/{id}")
Call<User> getUser(@Path("id") long id);
// GET /api/users?page=1&size=20 —— @Query拼接查询参数
@GET("users")
Call<List<User>> getUsers(@Query("page") int page, @Query("size") int size);
// POST /api/users —— @Body提交JSON对象(自动序列化)
@POST("users")
Call<User> createUser(@Body User user);
// POST /api/login —— @Field表单提交(需@FormUrlEncoded)
@FormUrlEncoded
@POST("login")
Call<LoginResult> login(@Field("username") String username,
@Field("password") String password);
// 请求头与多参数 —— @Header/@HeaderMap
@GET("users")
Call<List<User>> search(@Header("X-Channel") String channel,
@QueryMap Map<String, String> filters);
// 文件上传 —— @Multipart
@Multipart
@POST("upload")
Call<UploadResult> upload(@Part MultipartBody.Part file,
@Part("desc") RequestBody desc);
}
# 2.3 构建 Retrofit 实例并发起请求
public class RetrofitManager {
private static volatile Retrofit sRetrofit;
// 单例:Retrofit实例线程安全且重量级,全应用一份
public static Retrofit getRetrofit() {
if (sRetrofit == null) {
synchronized (RetrofitManager.class) {
if (sRetrofit == null) {
sRetrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/") // 必须以/结尾
.client(OkHttpHolder.getClient()) // 复用11.01的单例OkHttpClient
.addConverterFactory(GsonConverterFactory.create()) // JSON→对象
.build();
}
}
}
return sRetrofit;
}
public static UserApi getUserApi() {
return getRetrofit().create(UserApi.class); // 动态代理生成实现
}
}
// 发起异步请求(呼应10章:回调在子线程,切主线程更新UI)
RetrofitManager.getUserApi().getUser(1).enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful() && response.body() != null) {
User user = response.body(); // 已是解析好的对象!
new Handler(Looper.getMainLooper()).post(() ->
textView.setText(user.getName()));
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
// 网络异常(不含业务错误)
}
});
对比 11.01 手动拼请求 + 手动解析 JSON 的代码量,Retrofit 的价值一目了然:接口方法即请求,返回值即数据对象。
# 三、核心注解速查
| 分类 | 注解 | 作用 |
|---|---|---|
| 请求方法 | @GET @POST @PUT @DELETE | 对应 HTTP 方法 |
| URL | @Path | 替换 URL 中的 {} 占位符 |
@Query / @QueryMap | URL 查询参数(?key=value) | |
| 请求体 | @Body | 对象序列化成 JSON(需转换器) |
@Field / @FieldMap | 表单键值对(必须配 @FormUrlEncoded) | |
@Part / @PartMap | Multipart 文件/字段(必须配 @Multipart) | |
| 头部 | @Header / @HeaderMap | 单次请求的请求头 |
# 四、线程切换:回调执行器与封装
enqueue 的回调在 OkHttp 子线程。两种切主线程方案:
方案一:给 Retrofit 配主线程回调执行器(.callbackExecutor),所有回调自动切主线程:
new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(OkHttpHolder.getClient())
.addConverterFactory(GsonConverterFactory.create())
.callbackExecutor(new HandlerExecutor(Looper.getMainLooper())) // 回调切主线程
.build();
// 简单实现
static class HandlerExecutor implements Executor {
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
mHandler.post(command);
}
}
方案二:统一封装一层 BaseCallback(处理业务码归一化,见 11.03 封装思路)。Kotlin 项目则用协程 suspend 函数(Retrofit 原生支持,自动切线程)——见 20 Kotlin 基础。
# 五、常见问题与陷阱
- baseUrl 必须以
/结尾,相对路径不以/开头——baseUrl("https://a.com/api")+@GET("users")会变成https://a.com/users(丢了 api!)。正确写法:baseUrl("https://a.com/api/")+@GET("users")。 - @Field/@Part 的必配注解:
@FormUrlEncoded、@Multipart忘写直接运行时异常,报错信息会指出方法名。 - @Path 自动 URL 编码:含
/等特殊字符的参数会被编码,需要原样传时用@Path(value="id", encoded=true)。 - 接口方法参数注解不能缺:没有注解的参数直接编译期报错(Retrofit 尽量把错误提前)。
- 泛型与转换器:
Call<List<User>>的泛型由 Gson 反射解析,注意实体要有无参构造;嵌套泛型List<Map<String, Object>>可用但类型不安全,推荐定义 DTO。 - 返回值类型的选择:
Call<T>之外,Response<T>可拿到状态码与 headers(需要判断 HTTP 层信息时用)。 - 重复请求取消:页面销毁时
call.cancel()(持有 Call 引用);列表页快速切换用Call.cancel()防止旧结果覆盖新结果。
# 结束
本节介绍了 Retrofit 的分层架构(声明接口 → 动态代理翻译 → OkHttp 执行 → 转换器解析 → 回调返回,配架构图)、六类核心注解、单例 Retrofit 构建(复用 OkHttp 单例与 Gson 转换器)、回调线程切换方案,以及 baseUrl 斜杠、注解配套、取消请求等高频陷阱。至此"发请求"闭环完成,但生产级网络层还需要长连接、弱网策略、安全加固与统一封装——下一节:11.03 网络进阶。更多内容请参考:Retrofit 官方文档 (opens new window)。