Mark Xu 的博客

记录精彩的程序人生

Retrofit 使用总结及源码分析

本篇文章对 Retrofit 用法进行简单记录,并进行相关的源码分析。

整体说明

Retrofit 对网络请求接口进行了封装,实际执行网络请求的依然是 OkHttp。Retrofit 接口层实际是对 OkHttp 中 Request 的封装,采用注解的形式来描述网络请求参数。

Retrofit 使用

在学习 Retrofit 使用之前我们先回顾下 OKHttp 的工作方式,一个标准的 OkHttp 请求格式为 mClient.newCall(request).enqueue(callback);。可以看到,首先构造出 Request 对象(经过封装的 http 请求信息)和 OkHttpClient 对象,然后调用 OkHttpRequest 的 newCall 方法,传入封装的 request 对象后,就得到了一个 RealCall 对象,然后就可以操作 RealCall 对象执行请求、取消等一系列操作。

而 Retrofit 对于 http 请求信息并不是直接构造成 Request 对象,而是声明为接口的形式,相关请求信息(地址、方法、请求头等)使用注解和参数的形式传入。同样会有一个 Retrofit 客户端对象,调用其 create 方法会帮我们创建出请求接口的实例对象,接着调用请求接口实例对象的方法发起请求。流程如下:

  • 创建请求接口
  • 构建 Retrofit 实例
  • 创建网络请求接口实例
  • 发起网络请求

可以看到,Retrofit 对于 OkHttp 中的 Request 进行了抽象化为接口,在使用时首先构造出请求对象。一个标准的 Retrofit 请求格式为 mRetrofit.create(RequestService.class).doGet("params").enqueue(callback),其中 doGet 方法返回的是一个 Call 对象。

demo

注解说明

网络请求方法

网络请求方法注解对应于 Http 请求方式,包括:

注解名称 说明
@GET 从服务器取出资源(一项或多项)
@POST 在服务器新建一个资源
@PUT 在服务器更新资源(客户端提供改变后的完整资源)
@DELETE 从服务器删除资源
@HEAD 获取资源的元数据
@OPTIONS 获取信息,关于资源的哪些属性是客户端可以改变的

标记类

标记类注解,作用于网络请求接口的方法,包括:

注解名称 说明
@FormUrlEncoded 表示请求体是一个 Form 表单
@Multipart 表示请求体是一个 Multipart 表单
@Streaming 表示返回的数据以流的形式返回

提交表单

代码示例:

public interface Add2GankApi {
    @POST("add2gank")
    @FormUrlEncoded
    Call<ResponseBody> add(@Field("url") String url, @Field("desc") String desc, @Field("who") String who,
                           @Field("type") String type, @Field("debug") boolean debug);
}

提交 Multipart

代码示例:

public interface Add2GankApi {
    @POST("add2gank")
    @Multipart
    Call<ResponseBody> addFile(@Part("name") RequestBody name, @Part("age") RequestBody age,
                               @Part MultipartBody.Part file);
}

其中 @Part 后面括号内的字符用于生成每个 Part 请求头 Content-Disposition 字段的信息,接收的参数则用于生成请求体。生成的 Content-Disposition 字段示例如下:

Content-Disposition: form-data; name=”name”
或
Content-Disposition: form-data; name=”test”; filename=”test.txt”

调用:

RequestBody name = RequestBody.create(mediaType, "Tom");
RequestBody age = RequestBody.create(mediaType, "12");
MultipartBody.Part file= MultipartBody.Part.createFormData("test", "test.txt",
        RequestBody.create(mediaType, new File("test.txt")));

RetrofitUtil.getInstance()
        .createAdd2GankApiRequest()
        .addFile(name, age, file);

网络请求参数注解

网络请求参数注解包括:

注解名称 说明
@Headers 添加固定的请求头
@Header 添加不固定值的 Header

示例:

@GET("user")
Call<ResponseBody> getUser(@Header("Authorization") String authorization);

@GET("user")
@Headers("Authorization: authorization")
Call<ResponseBody> getUser();
注解名称 说明
@Body 发送自定义数据类型(非表单数据)给服务器
注解名称 说明
@Field 表单字段
@FieldMap 表单字段

示例:

@POST("add2gank")
@FormUrlEncoded
Call<ResponseBody> add(@Field("username") String name, @Field("age") int age);
                       
@POST("add2gank")
@FormUrlEncoded
Call<ResponseBody> add(@FieldMap Map<String, Object> map);

使用:

RetrofitUtil.getInstance()
        .createAdd2GankApiRequest()
        .add("Tom", 18);

Map<String, Object> map = new HashMap<>();
map.put("username", "Tom");
map.put("age", 18);
RetrofitUtil.getInstance()
        .createAdd2GankApiRequest()
        .add(map);
注解名称 说明
@Part Multipart 表单字段
@PartMap Multipart 表单字段

示例:

public interface Add2GankApi {
    @POST("add2gank")
    @Multipart
    Call<ResponseBody> addFile(@Part("name") RequestBody name, @Part("age") RequestBody age,
                               @Part MultipartBody.Part file);
                               
    @POST("add2gank")
    @Multipart
    Call<ResponseBody> addFile(@PartMap Map<String, RequestBody> args, @Part MultipartBody.Part file);
}

使用

// Part 使用
RequestBody name = RequestBody.create(mediaType, "Tom");
RequestBody age = RequestBody.create(mediaType, "12");
MultipartBody.Part file= MultipartBody.Part.createFormData("test", "test.txt",
        RequestBody.create(mediaType, new File("test.txt")));

RetrofitUtil.getInstance()
        .createAdd2GankApiRequest()
        .addFile(name, age, file);
        
// PartMap 使用
Map<String, RequestBody> map = new HashMap<>();
map.put("name", name);
map.put("age", age);
RetrofitUtil.getInstance()
        .createAdd2GankApiRequest()
        .addFile(map, file);
注解名称 说明
@Query 作用于 GET 方法的查询参数,作用于 Url
@QueryMap 作用于 GET 方法的查询参数,作用于 Url

示例:

@GET("user")
Call<ResponseBody> getUser(@Query("age") int age, @Query("gender") int gender);

在 baseUrl = "http://gank.io/api/",调用 getUser 方法传参为 (18, 0) 时,请求 url 为 "http://gank.io/api/?age=18&gender=0"

注解名称 说明
@Path URL 地址的缺省值
@Url 直接设置 Url 变量

示例:

@GET("users/{user}/photos")
Call<ResponseBody> getUserPhotos(@Path("user") String user);

当传参为 "tom" 时,请求 url 为 "http://gank.io/api/users/tom/photos"。

示例:

@GET
Call<ResponseBody> getUSer(@Url String url, @Query("age") int age);

当有 @Url 注解时,@GET 传入的 Url 可以忽略。

源码分析

具体分析

Retrofit 的构造

我们首先来从 Retrofit 对象的构建说起。和 OkHttpClient 一样,Retrofit 对象我们也建议项目中只维护一个。一个标准的 Retrofit 构造代码如下:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

可以看到 Retrofit 也使用了建造者模式,

未完待续...

参考

这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解)

RESTful API 设计指南

留下你的脚步