11.02 网络编程-Retrofit

2025/4/6 Android 开发基础

11.01 的 OkHttp 已经能发请求了,但每个接口都要手拼 URL、构造 RequestBody、手动解析 JSON——样板代码多且易错。Retrofit 用"注解声明接口"的方式消灭这些样板:接口定义即接口文档,翻译与解析全部自动化。它与 OkHttp 是分工而非竞争:Retrofit 负责声明与转换,OkHttp 负责执行。

# 一、分层架构:声明 → 翻译 → 执行

Retrofit分层架构示意图

理解这张图就理解了 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 基础。

# 五、常见问题与陷阱

  1. baseUrl 必须以 / 结尾,相对路径不以 / 开头——baseUrl("https://a.com/api") + @GET("users") 会变成 https://a.com/users(丢了 api!)。正确写法:baseUrl("https://a.com/api/") + @GET("users")。
  2. @Field/@Part 的必配注解:@FormUrlEncoded、@Multipart 忘写直接运行时异常,报错信息会指出方法名。
  3. @Path 自动 URL 编码:含 / 等特殊字符的参数会被编码,需要原样传时用 @Path(value="id", encoded=true)。
  4. 接口方法参数注解不能缺:没有注解的参数直接编译期报错(Retrofit 尽量把错误提前)。
  5. 泛型与转换器:Call<List<User>> 的泛型由 Gson 反射解析,注意实体要有无参构造;嵌套泛型 List<Map<String, Object>> 可用但类型不安全,推荐定义 DTO。
  6. 返回值类型的选择:Call<T> 之外,Response<T> 可拿到状态码与 headers(需要判断 HTTP 层信息时用)。
  7. 重复请求取消:页面销毁时 call.cancel()(持有 Call 引用);列表页快速切换用 Call.cancel() 防止旧结果覆盖新结果。

# 结束

本节介绍了 Retrofit 的分层架构(声明接口 → 动态代理翻译 → OkHttp 执行 → 转换器解析 → 回调返回,配架构图)、六类核心注解、单例 Retrofit 构建(复用 OkHttp 单例与 Gson 转换器)、回调线程切换方案,以及 baseUrl 斜杠、注解配套、取消请求等高频陷阱。至此"发请求"闭环完成,但生产级网络层还需要长连接、弱网策略、安全加固与统一封装——下一节:11.03 网络进阶。更多内容请参考:Retrofit 官方文档 (opens new window)。