Retrofit基本使用与常用注解
Retrofit 这玩意儿,说白了就是一个把 HTTP 请求封装成 Java 接口的工具。你不需要手动拼 URL、写 HttpURLConnection,只需要在接口上加几个注解,它就能自动把请求发出去,再把响应解析成你想要的 Java 对象。
我最早接触 Retrofit 的时候,觉得它不过是个封装好的网络库。后来在实际项目里重构了一堆网络代码,才发现它的注解系统才是真正的灵魂。那咱们今天就聊聊这些核心注解怎么用。
一个最简单的请求怎么搭
先搭个最简单的架子。我习惯把这个步骤分成三步:
- 写一个接口,里面定义好你要调用的 API 方法。
- 创建 Retrofit 实例,告诉它 base URL 和转换器(比如 Gson)。
- 拿到接口实例,然后直接调用方法。
举个栗子,我要请求 GitHub 的用户信息,URL 是 https://api.github.com/users/octocat。那么接口可以写成:
public interface GitHubService {
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
}
看到 @GET 了吗?它告诉 Retrofit 这是个 GET 请求。@Path 注解把 {username} 替换成你传入的参数值。
然后构造 Retrofit 实例:
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<User> call = service.getUser("octocat");
嗯,这里要注意,baseUrl 必须以 / 结尾,否则 Retrofit 会报奇怪的错误。我踩过这个坑,调试了半天才发现是少了个斜杠。
常用注解逐个聊
@GET / @POST / @PUT / @DELETE
这些注解代表请求方法。你写 @GET,Retrofit 就知道发 GET 请求。同理,@POST 用于提交数据。@PUT 用于全量更新,@DELETE 用来删除资源。
我个人习惯在接口上直接写明方法类型,这样代码一看就懂。比如:
@POST("users")
Call<User> createUser(@Body User user);
@DELETE("users/{id}")
Call<Void> deleteUser(@Path("id") int userId);
路径替换:@Path
@Path 用来替换 URL 中的占位符 {xxx}。你想想看,如果 API 里用户 ID 是路径的一部分,用 @Path 最方便。我在项目里经常遇到这样的 RESTful 接口:
@GET("api/v1/orders/{orderId}")
Call<Order> getOrder(@Path("orderId") String orderId);
注意:@Path 的参数值会被 URL 编码。如果参数里包含特殊字符,比如中文或空格,Retrofit 会自动帮你转义,这点很省心。
查询参数:@Query
很多 GET 接口后面跟着一长串查询参数,比如 ?page=1&size=20。这时候用 @Query:
@GET("users")
Call<List<User>> searchUsers(@Query("name") String name,
@Query("page") int page);
它会自动拼装成 /users?name=xxx&page=1。如果你有多个查询参数,直接加多个 @Query 就行。也可以用一个 Map:
@GET("users")
Call<List<User>> searchUsers(@QueryMap Map<String, String> options);
我曾经在一个分页列表里遗漏了 @Query,结果接口每次都返回第一页数据。排查发现是参数没传进去,补上注解就好了。
POST 请求的请求体:@Body
如果你要提交 JSON 数据,用 @Body 注解。Retrofit 会自动把对象转成 JSON(前提是你配了 Gson 或 Moshi 转换器)。示例:
@POST("users")
Call<User> createUser(@Body User user);
这里要注意,@Body 不能为 null,否则 Retrofit 会抛异常。我习惯在调用前做一次判空处理。
动态路径和查询:组合使用
实际项目中经常需要路径和查询参数并存。比如:
@GET("products/{category}")
Call<List<Product>> getProductsByCategory(@Path("category") String category,
@Query("sort") String sort,
@Query("page") int page);
这样就会请求 /products/electronics?sort=price&page=1。
表:常用注解速查
| 注解 | 用途 | 参数位置 | 示例 |
|---|---|---|---|
| @GET | GET 请求 | 方法上 | @GET("user/{id}") |
| @POST | POST 请求 | 方法上 | @POST("user") |
| @PUT | PUT 请求 | 方法上 | @PUT("user/{id}") |
| @DELETE | DELETE 请求 | 方法上 | @DELETE("user/{id}") |
| @Path | 替换路径占位符 | 方法参数 | @Path("id") int id |
| @Query | 添加查询参数 | 方法参数 | @Query("page") int page |
| @QueryMap | 批量查询参数 | 方法参数 | @QueryMap Map params |
| @Body | POST/PUT 请求体 | 方法参数 | @Body User user |
| @Headers | 设置固定请求头 | 方法上 | @Headers("Authorization: Bearer xxx") |
| @Header | 动态请求头 | 方法参数 | @Header("Authorization") String |
关于 @Headers 和 @Header
有些接口需要固定的 header,比如 Content-Type。你可以在方法上直接加 @Headers:
@Headers("Content-Type: application/json")
@POST("upload")
Call<ResponseBody> upload(@Body RequestBody body);
如果 header 的值是动态的(比如 ),就用 @Header 注解参数:
@POST("secure/data")
Call<Data> getData(@Header("Authorization") String );
我个人更喜欢把 这样的全局信息放在拦截器里统一添加,而不是每个方法都传一次参数。后面讲拦截器的时候会细说。
整个调用流程
咱们把上面这些串起来,画一张图看看 Retrofit 是怎么工作的。
关于 @QueryMap 的坑
如果你有一堆不确定的查询参数,用 @QueryMap 确实方便。但要注意,Map 的 key 和 value 都必须是 String,否则 Retrofit 会报类型转换异常。我曾经传了个 Integer 进去,直接 crash 了。正确做法:
Map<String, String> params = new HashMap<>();
params.put("page", String.valueOf(1));
params.put("size", String.valueOf(20));
Call<List<Product>> call = service.getProducts(params);
小结一下
Retrofit 的注解系统其实不复杂。记住几点:
- 方法上写请求类型(@GET / @POST 等)和路径
- 路径里的动态部分用 @Path
- 查询参数用 @Query 或 @QueryMap
- POST 请求体用 @Body
- 请求头用 @Headers 或 @Header
这些基础打好了,后面聊拦截器和缓存的时候你才能游刃有余。嗯,今天就到这儿,你现在可以打开 IDE 写几个接口试试了。