网络编程总结(十一):Retrofit 总结

刚开始接触 Retrofit,翻译了 Retrofit 的官方文档,但是官方文档的代码有些省略,还是有些不是很懂,读了 N 篇博客之后差不多搞明白了,特此记录一下。

这篇文章可能有些长,跨度可能也有些大,尽量写的简单明白一些吧。

什么是 Retrofit?

按照官方的说法是一个类型安全的网络请求库,但是还需要再加上一句:它把 REST API 返回的数据转化为 Java 对象以供我们操作。

那么问题又来了,什么是 REST API?

什么是 REST API?

在查了一些资料之后,发现有个大神这么概括:用 URL 定位资源,用 HTTP 动词来描述操作。

似乎有些明了了。

我们知道,URL 的基本五元素是:

  • 传送协议,例如 http、ftp 等
  • 服务器地址。
  • 端口号。
  • 路径(以 / 字符区别路径中的每一个目录名称)
  • 查询(GET 模式的窗体参数,以 字符为起点,每个参数以 & 隔开,再以 = 分开参数名称与数据,通常以 URF-8 的 URL 编码,避开字符冲突问题)

而 HTTP 动词又是什么东西呢?我们使用 WEB 服务,经常会看到各种各样的动词,当我们需要从服务器查询某些内容时候,我们会发送 GET 请求,当我们要向服务器提交表单时,我们会发送 POST 请求,同样还有 PUT、DELETE,这些被称之为 “HTTP 动词”,通过 POST、DELETE、GET、PUT 来完成对资源的“增删查改”。

举个例子:

GET /student : 获取所有学生信息
GET /student/{id} : 根据 ID 获取某个学生信息
POST /student : 添加一个学生信息
PUT /student/{id} : 根据 ID 更新一个学生的信息
DELETE /student/{id} : 根据 ID 删除一个学生的信息

明白了不?

Retrofit 如何使用

准备工作

明白了 REST API 是何物之后,开始使用 Retrofit 来访问网络,使用 LOOPBACK 来创建一套 API,字段如下:

String id,
String name,
int age,
String sex

其中 id 字段为主键,由系统自动生成。

URL

之前说过了,REST API 是使用 URL 来定位资源,那么 URL 的写法就很重要了。

Retrofit 中通过 @URL 和 baseURL 两部分来确定我们的资源位置,但并不是纯粹的连接组合。Retrofit 2.0 要求 baseURL 必须以 / 结尾,所以我们建议 @URL 中不以 / 开头,因为假如我们的 baseURL 写成 http://www.xxx.com/xxx/yyy/ 而 @URL 写成 /zzz 的话,我们的请求连接会变成 http://www.xxx.com/zzz,所以我们建议 @URL 不要使用 / 开头

假如在 @URL 中将路径写全的话,那么在 baseURL 中的路径将没有意义(但不能为空)。

HTTP 动词

在 Retrofit 中使用注解的方式来区分请求类型,其表现形式为 @请求方法(请求地址)

Retrofit 提供了 GETPOSTDELETEPUTHEADOPTIONSPATCH这七种请求方法,我们来依次看一下。

Retrofit 的基本使用步骤:

  1. 导包和加网络访问权限,这个就不用说了。

  2. 创建请求接口,在这个接口中定义我们需要做什么?

  3. 创建 Retrofit “客户端”。

  4. 使用客户端生成之前创建的请求接口的实现。

  5. 使用实现的接口对象调用其内部方法获得 Call 对象

  6. 使用之前定义的接口中的 Call 向远程服务器发出同步或者异步的 HTTP 请求。

废话不多说,上代码:

GET 请求

GET 方法大致可以分为两种,一种是不加参数查询所有内容,一种是加参数进行条件搜索。

不BB,上代码

  1. 创建接口
public interface IPeopleService {
    //GET 请求
    @GET("people/{id}")
    Call<ResponseBody> getPeopleById(@Path("id") String id);

    @GET("people")
    Call<ResponseBody> getAllPeople();
}
  1. 创建 Retrofit “客户端”。
Retrofit retrofit = new Retrofit.Builder().client(MainActivity.okClient()).baseUrl("http://192.168.8.102:3000/api/").build();
  1. 使用客户端生成之前创建的请求接口的实现。
IPeopleService service = retrofit.create(IPeopleService.class);
  1. 使用实现的接口对象调用其内部方法获得 Call 对象
Call<ResponseBody> call = service.getAllPeople();
  1. 使用 Call 对象执行同步或异步请求。
// 同步执行:直接调用 Call 对象的 execute() 方法,返回一个 Response 对象。
Response<ResponseBody> response = call.execute();
// 异步执行:调用 Call 对象的 enqueue(Callback callback) 方法。
                            call.enqueue(new Callback<ResponseBody>() {
                                @Override
                                public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                                    // 响应成功,在这里操作
                                }

                                @Override
                                public void onFailure(Call<ResponseBody> call, Throwable t) {
                                    // 响应失败,在这里操作
                                }
                            });

正如 getPeopleById 那个请求一样,我们需要动态的更新 ID 来查询不同 ID 的 People 信息,在 Retrofit 中,URL 使用 {xxx} 来动态更新参数,在方法中,使用 @Path 来注释,需要注意的是:注释的参数必须和 URL 中的动态参数名相同

例如我们希望的请求连接是:

https://www.li-xyz.com:1443/content/images/2017/03/volley-request.png

那么 @URL 可以写作:

    @GET("{year}/{month}/{picName}")
    Call<ResponseBody> getPic(@Path("year") int year,@Path("month") String month,@Path("picName") String picName);

baseURL:

https://www.li-xyz.com:1443/content/images/

就 OK 了!

还有一种情况,就是我们的请求连接中带有查询参数的,他们的一般形式为:http://www.xxx.com/xxx?aa=bb

Retrofit 使用 @Query 注解方式来实现这种方式的请求,例如我们想要请求的 URL 是 http://www.xxx.com/xxx?aa=bb&cc=dd 那我们的 @URL 和 baseURL 就可以这样写:

@URL:    
    @GET("xxx")
    Call<ResponseBody> getTest(@Query("aa") String aa,@Query("cc") String cc);

baseURL:
    http://www.xxx.com/

在调用接口方法时候传入参数:
    Call<ResponseBody> call = service.getTest("bb", "dd");

假如说我们请求的参数太多,写这么多的 @Query 岂不是很乱, @QueryMap 是你最好的选择

上面的那个查询连接采用 QueryMap 的形式可以写成:

@URL:
    @GET("xxx")
    Call<ResponseBody> getTest(@QueryMap Map<String,String> queryMap);

baseURL:
    http://www.xxx.com/

在调用接口方法的时候传入 Map:   
    Map<String, String> map = new HashMap<String, String>();
    map.put("aa","bb");
    map.put("cc","dd");
    Call<ResponseBody> call = service.getTest(map);

POST 请求

Retrofit 使用 @POST 来表示发出的是 POST 请求。

form-encoded 提交表单

例如我们向服务器提交一个新的 people 信息。需要使用 @Field 注解来表示表单字段,并且 @Field 必须和 @FormUrlEncoded 搭配使用。:

接口文件中:
    //POST 请求
    @FormUrlEncoded
    @POST("people")
    Call<ResponseBody> createPeople(@Field("name") String name, @Field("age") int age, @Field("sex") String sex);

调用接口中的方法:
    Call<ResponseBody> call = service.createPeople("赵六", 40, "女");

这样,一条数据就提交成功了。

当我们需要提交的参数太多时, @FieldMap 同样也可以想 QueryMap 那样,以一个 Map 作为参数:

接口文件中:
        //POST 请求
    @FormUrlEncoded
    @POST("people")
    Call<ResponseBody> createPeople(@FieldMap Map<String,String> map);

调用接口方法:
    Map<String, String> map = new HashMap<String, String>();
    map.put("name", "孙七");
    map.put("age", "50");
    map.put("sex", "男");

    Call<ResponseBody> call = service.createPeople(map);

当然,我们也可以直接传入一个 实体类 对象作为参数,但是那需要使用到转换器,在后面内容中会讲到。

multipart 上传文件

最早的 HTTP POST 是不支持上传文件的,给变成带来了很多问题,但是后来出台了 rfc1867 用来支持文件上传。所以 Content-Type 的类型扩充了 multipart/form-data 用以支持向服务器发送二进制数据。因此发送post请求时候,表单

<

form>属性enctype共有二个值可选,这个属性管理的是表单的MIME编码:

  • application/x-www-form-urlencoded(默认值)

  • multipart/form-data

Retrafit 使用 @Multipart 来表示上传文件,每个 part 使用 @Part 来注释。

接口文件:
    @Multipart
    @POST("form")
    Call<ResponseBody> upload(@Part MultipartBody.Part file1);

接口调用:
    Retrofit retrofit = new Retrofit.Builder().client(MainActivity.okClient()).baseUrl("http://192.168.8.188:4567/").build();
    IPeopleService service = retrofit.create(IPeopleService.class);

    File file = new File(getFilesDir() + "/uploadFile.txt");
    RequestBody uploadFile = RequestBody.create(MediaType.parse("application/otcet-stream"), file);

    MultipartBody.Part data = MultipartBody.Part.createFormData("txt", "倚天屠龙记.txt", uploadFile);

    Call<ResponseBody> call = service.upload(data);

    Response<ResponseBody> response = call.execute();

可以看到在 RequestBody.create 中指定了 MediaType

详细的类型可以到这里查看:MIME 参考手册

PartMap 多文件上传

多文件上传就需要用到 PartMap 了,多了不说,看代码:

接口文件:
    @Multipart
    @POST("form")
    Call<ResponseBody> uploadFiles(@PartMap Map<String,RequestBody> map);

接口调用:
Map<String, RequestBody> map = new HashMap<String, RequestBody>();
map.put("txt\"; filename=\"txt.txt", txtRequestBody);
map.put("pic\"; filename=\"pic.jpg", picRequestBody);

Call<ResponseBody> call = service.uploadFiles(map);
Response<ResponseBody> response = call.execute();

可以看到,Map 中的 Key 并不是单纯的字符串,通过拼接造出了 MultipartBody.Part 的 name 和 filename。经过测试,直接写一个字符串,是上传不成功的。

PUT 和 DELETE 修改和删除

PUT 和 DELETE 和 GET 差不多,这里就不赘述了。

请求头

Retrofit 使用 @Headers 来注释静态消息头,名称相同的消息头都将包含在请求当中。

有以下两种形式:

@Headers("Cache-Control: max-age=640000")
@GET("widget/list")
Call<List<Widget>> widgetList();  
或者
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);  

Converters 转换器

在上面的 POST 例子中,我们提交用户 people 信息是将每个字段都作为参数来提交的,我们能不能直接使用一个 People 对象来作为参数呢?当然可以了,这里就需要用到 Converters 了。

之前我们在接口中的返回值都是 ResponseBody,现在我们需要将 Response 转换成我们需要的类型,这就需要用到 Converters,使用 Converters 需要引入相关的支持:

Gson:com.squareup.retrofit2:converter-gson

Jackson:Jackson: com.squareup.retrofit2:converter-jackson

Moshi:com.squareup.retrofit2:converter-moshi

Protobuf:com.squareup.retrofit2:converter-protobuf

Wire:com.squareup.retrofit2:converter-wire

Simple XML:com.squareup.retrofit2:converter-simplexml

Scalars (primitives, boxed, and String):com.squareup.retrofit2:converter-scalars

在引入支持之后,我们的接口就可以这样写:

@GET("people")
Call<List<People>> getAllPeople();

在调用接口的时候就可以这样写:

Retrofit retrofit = new Retrofit.Builder().client(MainActivity.okClient()).addConverterFactory(GsonConverterFactory.create(new Gson())).baseUrl("http://192.168.8.102:3000/api/").build();
IPeopleService service = retrofit.create(IPeopleService.class);
Call<List<People>> call = service.getAllPeople();
Response<List<People>> response = call.execute();

更多阅读

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1