4. 接口定义规范

Retrofit 的接口定义是网络层模块的核心契约。它通过声明式注解将 HTTP 方法、URL 路径、请求参数、请求体以及返回类型映射到 Java 方法上。本章将详细拆解 @GET/@POST/@PUT/@DELETE 注解的语义、参数注解 @Query/@Path/@Body 的使用场景,以及返回类型的设计原则,并给出标准代码模板与常见错误规避方案。

4.1 注解-方法映射原理

Retrofit 在运行时通过动态代理解析接口方法上的注解,自动构建 okhttp3.Request 对象。其核心映射关系如下图所示:

Retrofit 接口注解映射流程 Retrofit 接口注解 → HTTP 请求映射流程 Java 接口方法 @GET("/users/{id}") Call<User> getUser(@Path id) Retrofit 注解解析器 提取 HTTP 方法、URL、参数 构建 Request.Builder OkHttp Request GET /users/42?page=1 Headers, Body 已装配 参数注解解析 @Path / @Query / @Body 注解映射核心要点 • 方法注解决定 HTTP 动词和 URL 模板 • 参数注解决定请求参数、路径变量、请求体

4.2 方法注解对比与使用规范

注解HTTP 方法典型 URL 示例语义说明
@GETGET/api/users?page=1获取资源,幂等、可缓存
@POSTPOST/api/users创建资源,非幂等,通常带 Body
@PUTPUT/api/users/42全量替换资源,幂等
@DELETEDELETE/api/users/42删除资源,幂等
经验原则:用 @GET 获取列表或单条;@POST 新增;@PUT 全量更新;@DELETE 删除。不要用 @GET 传递请求体,OkHttp 会直接忽略。

4.3 参数注解详解与代码示例

参数注解决定了请求中的变量如何填充。以下是最常用的三种注解:

4.3.1 @Path:路径占位符替换

用于替换 URL 模板中的 {...} 占位符。注意:基本类型与 String 可直接传入;自定义对象需提供 toString() 或使用 @Path(encoded = true) 避免自动编码。

// 接口定义
@GET("users/{id}/posts")
Call<List<Post>> getUserPosts(@Path("id") int userId);

// 调用示例(Retrofit 会自动替换 42 → /users/42/posts)
ApiService api = retrofit.create(ApiService.class);
Call<List<Post>> call = api.getUserPosts(42);

4.3.2 @Query:查询参数拼接

追加到 URL 的 ? 之后,支持多次出现或数组/List 自动展开(用 @Query(encoded = true) 控制是否预编码)。

// 接口定义
@GET("search")
Call<SearchResult> search(
    @Query("q") String query,
    @Query("page") int page,
    @Query("size") int size
);

// 实际请求:GET /search?q=Retrofit&page=1&size=20

4.3.3 @Body:请求体填充

将 Java 对象自动序列化为 JSON(需配合 GsonConverterFactory 或 MoshiConverterFactory)。POST/PUT 必须使用 @Body,GET 不应使用。

// 接口定义
@POST("users")
Call<User> createUser(@Body User user);

// 调用示例
User newUser = new User("alice", "alice@example.com");
Call<User> call = api.createUser(newUser);
// 请求体:{"name":"alice","email":"alice@example.com"}
常见错误:多个 @Body 不能并存;@Body 不能和 @Field(表单)混用;若实体字段为 null,转换器默认会跳过该字段,可能导致服务端参数缺失,推荐在实体类上用 @Nullable 或定制适配器。

4.4 返回类型设计

Retrofit 支持多种返回类型,选择取决于解耦需求:

返回类型特点适用场景
Call<T>同步/异步二选一,可取消标准 REST 请求,推荐作为默认选择
Response<T>包裹原始 HTTP 响应,可获取状态码、Headers需要检查 HTTP 状态码、重定向或自定义错误处理
Observable<T>(RxJava)响应式流,支持线程切换、组合操作复杂异步编排,多请求合并
CompletableFuture<T>(Java 8+)Future 风格,可等待或回调Java 协程或线程池场景
自定义 CallAdapter完全自定义返回类型(如 Kotlin suspend)框架集成(如 LiveData、Flow)

推荐最低使用 Call<T>,这样保持通用性且易于单元测试。若需要访问 HTTP 错误码,可改用 Call<Response<T>>。

// 推荐声明方式
public interface ApiService {
    @GET("users/{id}")
    Call<User> getUser(@Path("id") int id);

    // 当需要 HTTP 状态码时
    @GET("users/{id}")
    Call<Response<User>> getUserWithResponse(@Path("id") int id);
}

4.5 常见错误与性能稳定优化

  • URL 模板不匹配:@Path 中的名称必须与 {...} 占位符完全一致,否则运行时抛出 IllegalArgumentException。
  • 忽略查询参数编码:当值含特殊字符(如中文、空格)时,应使用 @Query(encoded = false)(默认)让 Retrofit 自动 URLEncode;若已预编码则设为 true。
  • 返回类型为 void 或 Void:虽然允许,但失去错误处理能力。除非客户端完全不在乎响应,否则应返回 Call<Void>。
  • 性能优化:为常用接口预编译 ServiceMethod 缓存(Retrofit 2.6+ 已自动缓存);避免在循环中动态创建 Call 对象。
  • 调试技巧:添加 HttpLoggingInterceptor 可打印完整请求/响应体,方便验证注解效果。
调试命令示例(Logcat 过滤 Retrofit 日志):
adb logcat -s Retrofit:V OkHttp:V
或在 AndroidManifest 中加 android:debuggable="true" 后在运行时开启日志。