刚开始接触 Retrofit,翻译了 Retrofit 的官方文档,但是官方文档的代码有些省略,还是有些不是很懂,读了 N 篇博客之后差不多搞明白了,特此记录一下。
这篇文章可能有些长,跨度可能也有些大,尽量写的简单明白一些吧。
按照官方的说法是一个类型安全的网络请求库,但是还需要再加上一句:它把 REST API 返回的数据转化为 Java 对象以供我们操作。
那么问题又来了,什么是 REST API?
在查了一些资料之后,发现有个大神这么概括:用 URL 定位资源,用 HTTP 动词来描述操作。
似乎有些明了了。
我们知道,URL 的基本五元素是:
/
字符区别路径中的每一个目录名称)?
字符为起点,每个参数以 &
隔开,再以 =
分开参数名称与数据,通常以 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 删除一个学生的信息
明白了不?
明白了 REST API 是何物之后,开始使用 Retrofit 来访问网络,使用 LOOPBACK 来创建一套 API,字段如下:
String id,
String name,
int age,
String sex
其中 id 字段为主键,由系统自动生成。
之前说过了,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 中的路径将没有意义(但不能为空)。
在 Retrofit 中使用注解的方式来区分请求类型,其表现形式为 @请求方法(请求地址)
Retrofit 提供了 GET
、POST
、DELETE
、PUT
、HEAD
、OPTIONS
、PATCH
这七种请求方法,我们来依次看一下。
Retrofit 的基本使用步骤:
导包和加网络访问权限,这个就不用说了。
创建请求接口,在这个接口中定义我们需要做什么?
创建 Retrofit “客户端”。
使用客户端生成之前创建的请求接口的实现。
使用实现的接口对象调用其内部方法获得 Call 对象
使用之前定义的接口中的 Call 向远程服务器发出同步或者异步的 HTTP 请求。
废话不多说,上代码:
GET 方法大致可以分为两种,一种是不加参数查询所有内容,一种是加参数进行条件搜索。
不BB,上代码
public interface IPeopleService {
//GET 请求
@GET("people/{id}")
Call<ResponseBody> getPeopleById(@Path("id") String id);
@GET("people")
Call<ResponseBody> getAllPeople();
}
Retrofit retrofit = new Retrofit.Builder().client(MainActivity.okClient()).baseUrl("http://192.168.8.102:3000/api/").build();
IPeopleService service = retrofit.create(IPeopleService.class);
Call<ResponseBody> call = service.getAllPeople();
// 同步执行:直接调用 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);
Retrofit 使用 @POST
来表示发出的是 POST 请求。
例如我们向服务器提交一个新的 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);
当然,我们也可以直接传入一个 实体类 对象作为参数,但是那需要使用到转换器,在后面内容中会讲到。
最早的 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 了,多了不说,看代码:
接口文件:
@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 和 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);
在上面的 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();