第3章 注解使用进阶:@Path、@Query、@QueryMap、@Field、@FieldMap、@Body、@Headers、@HeaderMap、@Url动态地址

本章深入讲解 Retrofit 中与请求参数构建相关的核心注解。掌握这些注解是设计灵活、可维护 API 接口的基础。你将了解每个注解的定位、参数规则、适用场景,并通过代码示例与调试技巧快速应用到实际项目中。

3.1 注解全景概览

注解作用对象对应 HTTP 元素是否必须配合 @URL
@Path方法参数URL 路径占位符替换否
@Query方法参数URL 查询参数(?key=value)否
@QueryMap方法参数多个查询参数(Map)否
@Field方法参数表单编码正文(x-www-form-urlencoded)否(需 @FormUrlEncoded)
@FieldMap方法参数多个表单字段(Map)否(需 @FormUrlEncoded)
@Body方法参数请求体(JSON 或自定义序列化)否
@Headers方法静态请求头否
@HeaderMap方法参数动态请求头(Map)否
@Url方法参数动态替换完整 URL是(单独使用或与 @GET/@POST 配合)

关键记忆点:@Field/@FieldMap 必须配合 @FormUrlEncoded 使用;@Path 替换 {...} 占位符;@Url 允许运行时改变请求的目标地址。

3.2 注解分步详解与代码实战

3.2.1 @Path – 路径参数替换

用于将 URL 中的 {placeholder} 替换为实际值。注意:值会被自动 URL 编码(如空格转为 %20)。

public interface ApiService {
    @GET("users/{id}/repos")
    Call> listRepos(@Path("id") int userId);
}

// 调用时生成:GET /users/42/repos

常见错误:@Path 参数类型如果为 String 且包含 / 等特殊字符,可能意外改变路径层级。建议路径参数使用数字或安全字符串。

3.2.2 @Query 与 @QueryMap – 查询参数

@Query 用于单个查询参数;@QueryMap 接收 Map<String, String> 或 Map<String, Object> 批量添加查询参数。

@GET("search")
Call search(
    @Query("q") String query,
    @Query("page") int page,
    @Query("per_page") @Nullable Integer perPage
);

// 方式二:动态 Map
@GET("search")
Call search(@QueryMap Map<String, String> options);

// 使用
Map<String, String> opts = new HashMap<>();
opts.put("q", "retrofit");
opts.put("page", "1");
api.search(opts);

性能优化:当查询参数数量固定且不超过 6 个时,坚持使用 @Query 避免 Map 装箱开销;参数个数变化或由业务动态组合时使用 @QueryMap。

3.2.3 @Field 与 @FieldMap – 表单字段

必须配合方法上的 @FormUrlEncoded 注解。字段值自动 URL 编码,适合模拟 HTML 表单提交。

@FormUrlEncoded
@POST("login")
Call login(
    @Field("username") String username,
    @Field("password") String password
);

@FormUrlEncoded
@POST("register")
Call register(@FieldMap Map<String, String> fields);

容易踩坑:@Field 如果传 null,Retrofit 默认会忽略该字段(不会发送)。若需要明确发送空值,请使用 @Field("key") @Nullable String val 且配合 OkHttp 的 addFormDataPart 处理。

3.2.4 @Body – 请求体对象

用于发送非表单的请求体(最常见的是 JSON)。Retrofit 自动使用注册的 Converter(如 Gson、Moshi)序列化对象。

@POST("users")
Call createUser(@Body User user);

// 使用
User newUser = new User("Alice", "alice@example.com");
api.createUser(newUser);  // 发送 JSON:{"name":"Alice","email":"alice@example.com"}

调试建议:开启 OkHttp 日志拦截器查看实际请求体内容,确认序列化结果符合预期。

3.2.5 @Headers 与 @HeaderMap – 请求头控制

@Headers 是方法注解,用于添加固定请求头;@HeaderMap 是参数注解,用于动态添加多个请求头。

@Headers("Cache-Control: no-cache")
@GET("profile")
Call getProfile();

@GET("endpoint")
Call dynamicHeaders(@HeaderMap Map<String, String> headers);

// 使用
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + );
headers.put("X-Device-Id", deviceId);
api.dynamicHeaders(headers);

3.2.6 @Url – 动态完整地址

允许在运行时指定完整的请求 URL(包括 scheme、host、port、path)。@Url 参数如果以 http:// 或 https:// 开头,则忽略接口上的 baseUrl;否则拼接 baseUrl。

@GET
Call download(@Url String fileUrl);

// 示例:直接传完整地址
api.download("https://cdn.example.com/files/report.pdf");

// 示例:相对路径,自动拼接 baseUrl
api.download("documents/terms.pdf");  // 实际请求 baseUrl/documents/terms.pdf

注意:@Url 不能与 @Path、@Query 同时作用在同一方法参数上,且 @GET/@POST 的括号内无需写路径(或写空字符串)。

3.3 整体调用流程与注解关系(核心 SVG)

下图展示了 Retrofit 如何将各种注解解析为 HTTP 请求的各组成部分,帮助理解注解与协议元素的映射关系。

Retrofit 注解 → HTTP 请求元素映射 HTTP 请求 @Path @Query / @QueryMap @Field / @FieldMap @Body @Headers @HeaderMap @Url 各注解对应 HTTP 请求的不同部分 左列:地址/路径/查询/表单/请求体   右列:请求头   @Url 整体替换基地址 箭头指向 HTTP 请求图中对应的构建阶段

3.4 常见错误与调试

  • @Field 未加 @FormUrlEncoded:编译不报错,运行时抛出 IllegalArgumentException:@Field parameters can only be used with form encoding。
  • @Path 参数为 null:抛出 IllegalArgumentException,提示路径参数不能为空。
  • @Body 序列化失败:检查 Converter 是否注册(如 GsonConverterFactory),以及复杂对象中是否有循环引用或不可序列化字段。
  • @Url 与 @Path 共用:编译可通过,但运行时行为未定义。应避免混用。
  • @QueryMap 传空 Map:请求 URL 上不会附加任何查询参数,不会报错。若需要强制添加,请使用 @Query。

调试利器:在 OkHttpClient 上添加 HttpLoggingInterceptor,设置级别为 BODY,可以清晰看到经过注解转换后的最终 URL、请求头、请求体。

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(logging)
    .build();
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

3.5 性能与稳定性优化

  • 避免重复创建 Map:若多处使用 @QueryMap/@HeaderMap,可在数据层缓存固定参数 Map,减少对象分配。
  • @Body 对象复用:若请求体结构相同,可提前构建对象实例,避免每次调用重新 new。
  • @Url 与 BaseUrl 一致性:动态 URL 频繁变化时,建议统一使用 @Url 且传入完整地址,避免拼接错误导致 404。
  • @Headers 性能:固定请求头尽量使用方法级 @Headers(编译期确定),比 @HeaderMap 在运行时创建 Map 更高效。

部署提醒:生产环境下请始终开启混淆(ProGuard/R8),并保留 Retrofit、OkHttp 相关类和注解的使用。混淆规则示例:

-keepattributes *Annotation*
-keep class retrofit2.** { *; }
-keepclassmembers class * {
    @retrofit2.http.* <methods>;
}

否则注解在运行时可能被移除,导致接口调用失败。