公司一直在用 retrofit,我一直没有好好的研究,以前只是按照别人的格式写,也没出什么错误,今天准备好好研究一下,以加深印象,更好的理解。
Retrofit 是什么?
Retrofit 其实我们可以理解成为 OKHttp 的加强,它也是一种网络加载框架。底层是 OKHttp 封装的。而 Retrofit 只负责网络请求接口的封装。它的一个特点是包含了特别多的注解,方便简化你的代码量。并且还支持很多的开源库(例如:Retrofit+RxJava)。
Retrofit 的好处?
- 超级解耦合
我们在请求接口数据的时候,API接口定义和API接口使用总是相互影响,什么传参、回调等,耦合在一块。有时候我们会考虑一下怎么封装我们的代码让这两个东西不那么耦合,这个就是Retrofit的解耦目标,也是它的最大的特点。
Retrofit为了实现解耦,使用了特别多的设计模式,这里附上一片很好的文章,里面讲的就是实现原理:
Retrofit分析-漂亮的解耦套路 - 可以配置不同 HttpClient 来实现网络请求,例如 OKHttp、HttpClient 等等
- 支持同步、异步和 RxJava
- 可以配置不同的反序列化工具来解析数据。如 json、xml...
- 请求速度快,使用非常方便灵活
Retrofit 注解
- 请求方法
注解代码 | 请求格式 |
---|---|
@GET | GET请求 |
@POST | POST请求 |
@DELETE | DELETE请求 |
@HEAD | HEAD请求 |
@OPTIONS | OPTIONS请求 |
@PATCH | PATCH请求 |
- 请求参数
注解代码 | 说明 |
---|---|
@Headers | 添加请求头,结合方法 |
@Header | 通过参数动态添加请求头 |
@Path | 替换路径 |
@Query | 替代参数值,通常结合get 请求 |
@FormUrlEncoded | 用表单数据提交 |
@Field | 替换参数值,结合post 请求 |
@QueryMap | 替换请求参数 (例如:HashMap),结合get 请求 |
@Body | 替换请求参数 (例如:HashMap、实体类等),结合 post 请求 |
@Part | 单个文件上传 |
@PartMap | 多个文件上传 |
Retrofit请求的简单用法:
- 添加依赖
由于 Retrofit 是基于 OkHttp 的,所以还需要添加 OkHttp 库依赖
在 build.gradle添加如下依赖:
dependencies {
// Okhttp库
compile 'com.squareup.okhttp3:okhttp:3.1.2'
// Retrofit库
compile 'com.squareup.retrofit2:retrofit:2.0.2'
//日志拦截器
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
//添加retrofit gson转换会自动下载gson
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
//RxJava依赖
implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
//RxJava2 Adapter
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
}
- 添加网络权限
<uses-permission android:name="android.permission.INTERNET"/>
- 创建服务器返回的数据类
public class News {
// 根据返回数据的格式和数据解析方式(Json、XML等)定义
...
}
- 创建用于描述网络请求的接口
public interface APi {
// @GET注解的作用:采用Get方法发送网络请求
// getNews(...) = 接收网络请求数据的方法
// 其中返回类型为Call<News>,News是接收数据的类(即上面定义的News类)
// 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为Call<ResponseBody>
@Headers("apikey:81bf9da930c7f9825a3c3383f1d8d766")
@GET("word/word")
Call<News> getNews(@Query("num") String num,@Query("page")String page);
}
- Retrofit 将 Http 请求抽象成 Java 接口,并在接口里面采用注解来配置网络请求参数。用动态代理将接口的注解“翻译”成一个 Http 请求,最后执行 Http 请求。
注意:接口中的每个方法的参数都需要使用注解标注,否则会报错
2.API 接口中的最后一个注解,ResponseBody 是 Retrofit 网络请求回来的原始数据类,没经过 Gson 转化什么的,如果你不想转换,比如我就想看看接口返回的 json 字符串,那就像注释中说的,把 Call 的泛型定义为 Response:Call<Response>
3.GET 注解
说白了就是我们的 GET 请求方式。
这里涉及到 Retrofit 创建的一些东西,Retrofit 在创建的时候,有一行代码:
baseUrl("http://apis.baidu.com/txapi/")
这个http://apis.baidu.com/txapi/是我们要访问的接口的 BaseUrl,而我们现在用 GET 注解的字符串“word/word”会追加到 BaseUrl 中变为:http://apis.baidu.com/txapi/world/world
4.@Query 简单点来说呢
@Query("num")String num, @Query("page")String page;
就是键值对,Retrofit 会把这两个字段一块拼接到接口中,追加到 http://apis.baidu.com/txapi/world/world后面,变为http://apis.baidu.com/txapi/world/world?num=10&page=1,这个带着响应头的接口就是我们最终请求网络的完整接口。
注意:@GET("")里面一定要有数据,不能为@GET 或者@GET("")
@POST("")里面一定要有数据,不能为@ POST 或者@ POST("")
** @FormUrlEncoded ,post请求必须加上 **
** @POST("/") ,没有数据就填 . 或者 / **
public interface GetWeatherData2 {
@GET("/") //没有数据就填 . 或者 /
public Call<WeatherModel> getWeather(@QueryMap Map<String ,String> map); //@GET请求参数对用 @QueryMap ,POST请求参数对应 @FieldMap
@FormUrlEncoded //post请求必须加上
@POST("/") //没有数据就填 . 或者 /
public Call<WeatherModel> getWeather(@FieldMap Map<String,String> map); //POST请求参数对应 @FieldMap ,@GET请求参数对用 @QueryMap
}
这里补充一点哈,GET 请求方式,如果携带的参数不是以
?num=10&page=1
拼接到接口中(就是不带?分割符&),那就不用 Query 注解了,而是使用 Path 注解,像我们项目中的 Get 请求:
@GET(URL.CLAIM_APPLICATION_BOOKINFO + "{claimId}")
Observable<PublicResponseEntity<ClaimApplicationBookInfo>> getClaimApplicationBookInfo(@Header("Authorization") String authorization, @Path("claimId") String claimId);
上面的 GET 注解的接口通过{}占位符来标记的 claimId,就用@Path 注解在传入 claimId 的值。
@Query 与@Path 功能相同,但区别明显不一样。像@Query 的例子,我如果使用@Path 来注解,那么程序就会报错。
还有一点哈,有的 url 既有“{}”占位符,又有“ ?”后面的键值对(key-value),那 Retrofit既得使用@Query 注解有得使用@Path 注解,也就是说,两者可以同时使用。
5.@Headers
@Headers("apikey:81bf9da930c7f9825a3c3383f1d8d766")
这个很好,这个接口需要添加的 header:
apikey:81bf9da930c7f9825a3c3383f1d8d766
@Headers 就是把接口的 header 注解进去。还有很多添加 header 的方式,比如:
public interface APi {
@GET("word/word")
Call<News> getNews(@Header("apikey")String apikey, @Query("num")String num, @Query("page")String page);
}
这个就是在代码中动态添加header,用法如下:
Call<News> news = mApi.getNews("81bf9da930c7f9825a3c3383f1d8d766", "1", "10");
关于header的其他添加方式,大家可以看看下面的文章:
Retrofit之请求头
这里再补充一点:@Header与@Headers的区别
举个例子:
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
@Headers("Authorization:authorization")
@GET("user")
Call<User> getUser()
以上两个方法的效果是一致的。
区别就在于使用场景和使用方式
使用场景:@Header 用于添加不固定的请求头,@Headers 用于添加固定的请求头
使用范围:@Header 作用于方法的参数,@Headers 作用于方法
- 创建 Retrofit 对象
Retrofit retrofit = new Retrofit.Builder()
//设置数据解析器
.addConverterFactory(GsonConverterFactory.create())
//设置 RxJava
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//设置网络请求的Url地址
.baseUrl("http://apis.baidu.com/txapi/")
.build();
// 创建网络请求接口的实例
mApi = retrofit.create(APi.class);
这一块知识点有三个:
①此处特意说明一下这个网络请求的 URL 的组成:Retrofit 把网络请求的 URL 分成两部分设置:
第一部分:在创建 Retrofit 实例时通过.baseUrl()设置,就是上面的
.baseUrl("http://apis.baidu.com/txapi/")
第二部分:在网络请求接口的注解设置,就是在上面的 API 接口中用 GET 注解的字符串:
@GET("word/word")
Retrofit 的网络请求的完整 Url = 创建 Retrofit 实例时通过.baseUrl
+网络请求接口的注解设置(下面称“path”)
建议采用第三种方式来配置,并尽量使用同一种路径形式。
②关于数据解析器(Converter)
//设置数据解析器
.addConverterFactory(GsonConverterFactory.create())
这个有啥用?这句话的作用就是使得来自接口的json结果会自动解析成定义好了的字段和类型都相符的json对象接受类。在Retrofit 2.0中,Package 中已经没有Converter了,所以,你需要自己创建一个Converter, 不然的话Retrofit只能接收字符串结果,你也只能拿到一串字符,剩下的json转换的活还得你自己来干。所以,如果你想接收json结果并自动转换成解析好的接收类,必须自己创建Converter对象,然后使用addConverterFactory把它添加进来!
Retrofit支持多种数据解析方式,在使用时注意需要在Gradle添加依赖:
数据解析器 | Gradle依赖 |
---|---|
Gson | com.squareup.retrofit2:converter-gson:2.0.2 |
FastJson | com.alibaba:fastjson:1.2.57 |
Jackson | com.squareup.retrofit2:converter-jackson:2.0.2 |
Simple XML | com.squareup.retrofit2:converter-simplexml:2.0.2 |
Protobuf | com.squareup.retrofit2:converter-protobuf:2.0.2 |
Moshi | com.squareup.retrofit2:converter-moshi:2.0.2 |
Wire | com.squareup.retrofit2:converter-wire:2.0.2 |
Scalars | com.squareup.retrofit2:converter-scalars:2.0.2 |
像上面代码中,就是使用了第一种Gson数据解析器
③再来引入另一个方法:addCallAdapterFactory()
要知道它的作用
看一下我们的接口返回:
Call<News> news = mApi.getNews("1", "10");
返回的Call<News>可以理解成源生的了,默认就这么写。但像很多很多项目都是结合着RXJava来使用这个Retrofit的,那么这个接口返回就会被定义为(伪代码):
Observable<News> news = mApi.getNews("1", "10").subscribeOn(...).observeOn(...);
它返回的是一个 Observable 类型(观察者模式)。从上面可以看到,Retrofit 接口返回值可以分为两部分,第一部分返回值类型:Call 或者 Observable,另一部分是泛型:News
addCallAdapterFactory()影响的就是第一部分:Call 或者 Observable。Call 类型是 Retrofit 默认支持的(Retrofit内部有一个DefaultCallAdapterFactory),所以你如果不用 RxJava+Retrofit 结合使用,那就自动忽略这个方法,而如果你想支持 RxJava(就是想把返回值定义为 Observable 对象),就需要我们自己用 addCallAdapterFactory()添加:
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
项目中 OKHttp 和Retrofit 创建的代码:
HttpInterceptor interceptor = new HttpInterceptor();
okHttpClient = new OkHttpClient.Builder().
connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS).
readTimeout(READ_TIMEOUT, TimeUnit.SECONDS).
writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS).
//添加拦截器
// addInterceptor(interceptor).
retryOnConnectionFailure(true).
build();
rofit = new Retrofit.Builder()
.baseUrl(URL.SERVICE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
同理,Retrofit不光支持多种数据解析器,也支持多种网络请求适配器:Guava、Java8、RXJava ,使用时也需要在Gradle添加依赖:
网络请求适配器 | Gradle依赖 |
---|---|
Guava | com.squareup.retrofit2:adapter-guava:2.0.2 |
Java8 | com.squareup.retrofit2:adapter-java8:2.0.2 |
RXJava | com.squareup.retrofit2:adapter-rxjava:2.0.2 |
像上面的代码,就是用的RXJava
- 发起网络请求
//对发送请求进行封装
Call<News> news = mApi.getNews("1", "10");
//发送网络请求(异步)
news.enqueue(new Callback<News>() {
//请求成功时回调
@Override
public void onResponse(Call<News> call, Response<News> response) {
//请求处理,输出结果-response.body().show();
}
@Override
public void onFailure(Call<News> call, Throwable t) {
//请求失败时候的回调
}
});
上面是一个简单的GET请求的全过程。
补充一点,Retrofit还有个发起同步网络请求的方式:
//对发送请求进行封装
Call<News> news = mApi.getNews("1", "10");
//发送网络请求(同步)
Response<Reception> response = news.execute();
Retrofit 的 POST 请求
POST请求与GET请求算是我们日常开发中最最常用的两种网络访问方式,Retrofit的POST请求在用法上与GET区别不算大。
拿我早期写过的一个比较不合格的代码举个例子就能看出来(先声明,这种写法是不合格的,但是接口能跑通,看下去你就知道了):
①首先都是定义一个API接口:
public interface IServiceApi {
@POST("/claims/preclaims")
Observable<PublicResponseEntity<PreclaimsResponseEntity>> postClaimPreclaims(@Header("Authorization") String authorization, @QueryMap HashMap<String, String> deviceInfo, @Body RequestBody body);
}
①和GET请求相比,流程的开头都是创建了一个API的接口,然后用@POST注释,指定了对应的接口地址,我的返回值需要把获取到的Json字符串转成PublicResponseEntity<PreclaimsResponseEntity>,所以方法返回值要写成Call<PublicResponseEntity<PreclaimsResponseEntity>>
但是我项目中用到的是RxJava + Retrofit,所以把返回值定义为了Observable<PublicResponseEntity<PreclaimsResponseEntity>>
②方法中的第一个参数:我是在代码中动态的添加了一个header,这没啥可说的,上面的GET请求中说完了已经,看第二个。
③方法中的第二个参数:通过@QueryMap往接口中注解很多个参数,看到这里很容易联想到@Query,在上面的GET请求中@Query是一个一个往接口中注入参数的,而@QueryMap从名字也能看出来,如果Query参数比较多,那么可以通过@QueryMap方式将所有的参数集成在一个Map统一传递。
③第三个参数:通过@Body注解了一个RequestBody,
好!又出来一个新的注解@Body,它的源码中对他的注释大体意思是:使用这个注解可以把参数放到请求体中,适用于 POST/PUT请求,一脸懵逼呀,只知道它适用于对于POST/PUT。
其实,@Body可以注解很多东西的,HashMap、实体类等,例如:
public interface IServiceApi {
@POST("/claims/preclaims")
Observable<Item> postClaimUser(@Body User user);
}
那这么一看,@Body和@QueryMap差别不是很大哈,都可以对很多参数进行封装传递。话是这么说,但是它俩还是有差别的:
@QueryMap注解会把参数拼接到url后面,所以它适用于GET请求;
@Body会把参数放到请求体中,所以适用于POST请求。
如果你的项目是采用POST请求方式,不管是使用实体类还是使用HashMap最好采用@Body注解。虽然你使用QueryMap 可能也不会有什么问题(PS:这种共用的情况只适用于POST请求,GET请求不能使用@Body注解,否则会报错),就像上面我的不合格代码一样,POST请求中一直采用@QueryMap,虽然也能拿到接口数据,但是这么写是不合格的。
引以为戒吧~~
接下来就是调用了:
一样的创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(URL.SERVICE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
// 创建网络请求接口的实例
IServiceApi mApi = retrofit.create(IServiceApi .class);
一样的发起网络请求:
//对发送请求进行封装
Observable<PublicResponseEntity<PreclaimsResponseEntity>> news = mApi.postClaimPreclaims("你的Header信息", "你要传到接口中的HashMap参数", "你的实体类");
//发送网络请求(异步)
news.enqueue(new Callback<News>() {
//请求成功时回调
@Override
public void onResponse(Call<News> call, Response<News> response) {
//请求处理,输出结果-response.body().show();
}
@Override
public void onFailure(Call<News> call, Throwable t) {
//请求失败时候的回调
}
});
Retrofit下载文件
其实用Retrofit下载文件方式与其他请求几乎无异,拿我用到下载PDF的程序来举例子
step1:编写API,执行下载接口功能
public interface IServiceApi {
····
//PDF文件Retrofit下载
@Streaming
@GET
Observable<ResponseBody> retrofitDownloadFile(@Url String fileUrl);
...
}
上面的代码有几个注意的点:
①@Streaming 是注解大文件的,小文件可以忽略不加注释,但是大文件一定需要注释,不然会出现OOM。
②fileUrl就是PDF的下载地址,通过参数形式传进来
③正常来讲,API接口的返回类型是Call<ResponseBody>,即:
public interface IServiceApi {
····
//PDF文件Retrofit下载
@Streaming
@GET
Call<ResponseBody> retrofitDownloadFile(@Url String fileUrl);
...
}
但是我项目中是Retrofit结合RXJava来使用的,我把它的返回值类型定义为Observable<ResponseBody>,强烈推荐这种写法,便利于后续的数据处理