Square公司开发的一款针对Android 网络请求的框架(底层默认是基于OkHttp 实现),确切的讲,Retrofit是对okhttp的进一步封装,它功能强大,支持同步和异步、支持多种数据的解析(默认使用Gson),也支持RxJava。
Retrofit还有诸如性能好,处理速度快,代码简化。同时Retrofit简洁易用,它通过注解配置网络请求的参数,采用大量的设计模式来简化我们的使用。而且它的拓展性也做的相当的好,Retrofit的功能模块高度封装,高内聚低耦合,我们可以自定义自己想要的组件,比如说我们可以自己选择解析工具而不用默认的Gson。
其中解耦做的非常出色:
我们在请求接口数据的时候,API接口定义和API接口使用总是相互影响,什么传参、回调等,耦合在一块。有时候我们会考虑一下怎么封装我们的代码让这两个东西不那么耦合,这个就是Retrofit的解耦目标,也是它的最大的特点。
Retrofit为了实现解耦,使用了特别多的设计模式,推荐以下文章:
Retrofit分析-漂亮的解耦套路
由于Retrofit是基于OkHttp,所以还需要添加OkHttp,日志拦截器,RXjava等库依赖
在build.grale添加如下依赖:
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 interface APi {
// @GET注解的作用:采用Get方法发送网络请求
// getNews(...) = 接收网络请求数据的方法
// 其中返回类型为Call,News是接收数据的类(即上面定义的News类)
// 如果想直接获得Responsebody中的内容,可以定义网络请求返回值为
Call<ResponseBody>
@Headers("apikey:81bf9da930c7f9825a3c3383f1d8d766")
@GET("word/word")
Call<News> getNews(@Query("num") String num,@Query("page")String page);
}
(1). Retrofit将Http请求抽象成Java接口,并在接口里面采用注解来配置网络请求参数。用动 态代理将该接口的注解“翻译”成一个Http请求,最后再执行 Http请求
注意: 接口中的每个方法的参数都需要使用注解标注,否则会报错
(2). APi接口中的最后一个注释,Responsebody是Retrofit网络请求回来的原始数据类,没经过Gson转换什么的,如果你不想转换,比如我就想看看接口返回的json字符串,那就像注释中说的,把Call的泛型定义为ResponseBody:Call
注释代码 | 请求格式 |
---|---|
@GET | GET请求 |
@POST | POST请求 |
@PUT | Put请求 |
@DELETE | DELETE请求 |
@HEAD | HEAD请求 |
@OPTIONS | OPTIONS请求 |
@PATCH | PATCH请求 |
@HTTP | HTTP请求可替换以上7个方法 |
说白了就是我们的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
在我们日常开发中,BaseUrl具体是啥由后端接口童鞋给出,之后接口童鞋们会出各种各种的后缀(比如上面的 “word/word”)组成各种各行的接口用来供移动端数据调用,实现各种各样的功能
用于替换以上7个请求方法或扩展其他方法,有三个属性
public interface BlogService{
/**
* method 请求方法,不区分大小写
* path 路径
* hasBody 是否有请求体
*/
@HTTP(method = "get", path = "new/{id}", hasBody = false)
Call<ResponseBody> getNew(@Path("id") int id);
}
注释代码 | 说明 |
---|---|
@Headers | 添加请求头,作用于方法(其余请求参数作用于方法参数 ) |
@Path | 替换路径 |
@Query | 替代参数值,通常是结合get请求的 |
@QueryMap | 效果等同多个@query拼接 |
@FormUrlEncoded | 用表单数据提交 |
@Field | 替换参数值,是结合post请求的 |
@url | 动态的url请求数据 |
@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的区别
举个例子:
//@Header
@GET("user")
Call<User> getUser(@Header("Authorization") String authorization)
//@Headers
@Headers("Authorization:authorization")
@GET("user")
Call<User> getUser()
以上两个方法的效果是一致的。
区别就在于使用场景和使用方式
使用场景:@Header用于添加不固定的请求头,@Headers用于添加固定的请求头
使用范围:@Header作用于方法的参数;@Headers作用于方法
@Path主要用于Get请求,用于替换Url路径中的变量字符。
public interface csdnService {
@GET("users/{user}/question")
Call<List<Repo>> getData(@Path("user") String user);}
该接口定义了一个getData方法,该方法通过GET请求去访问服务器的users/{user}/question路径,其中通过@Path注解会把路径中的{user}替换成参数user的具体值。比如:user的值如果是zhangsan,那么Url的路径就是users/zhangsan/question.
@Query主要用于Get请求数据,用于拼接在拼接在Url路径后面的查询参数,一个@Query相当于拼接一个参数,多个参数中间用,隔开。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://ms.csdn.net/")
.build();public interface csdnService { //如果没有参数 @GET("api/ask/all_questions")
Call<List<Repo>> getData();
//只有少数参数
@GET("api/ask/all_questions")
Call<List<Repo>> getData(@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请求方式,如果携带的参数不是以
?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注解,也就是说,两者可以同时使用。
主要的效果等同于多个@Query参数拼接,主要也用于Get请求网络数据。
@GET("http://ms.csdn.net/api/ask/all_questions")
Call<List<Repo>> getData(@QueryMap Map<String,String> params);
Map<String,String>params=newHashMap();
params.put("name","liming");
params.put("age",24);
params.put("sex","man");
params.put("city","Shanghai");
这样等价于请求数据接口:
http://ms.csdn.net/api/ask/all_questions?name=liming&age=24&sex=man&city=Shanghai
@Field的用法类似于@Query,就不在重复列举了,主要不同的是@Field主要用于Post请求数据。作用:发送 Post请求 时提交请求的表单字段
@FieldMap的用法类似于@QueryMap。
两者主要区别是:如果请求为post实现,那么最好传递参数时使用@Field、@FieldMap和@FormUrlEncoded配合使用。因为@Query和或QueryMap都是将参数拼接在url后面的,而@Field或@FieldMap传递的参数时放在请求体的。
我们在代码中使用是不是发现了@POST比起@GET多了一个@FromUrlEncoded的注解。
如果去掉@FromUrlEncoded在post请求中使用@Field和@FieldMap,那么程序会抛出Java.lang.IllegalArgumentException: @Field parameters can only be used with form encoding. 的错误异常。
所以如果平时公司如果是Post请求的话,千万别忘记了加这@FromUrlEncoded注解。
@FormUrlEncoded
@POST("users/user/question")
Call<TnGou> getTngouPost(@Field("page") int page);
非表单请求体;
使用 @Body 注解,指定一个对象作为 request body 。
作用:以 Post方式 传递 自定义数据类型 给服务器
注意:如果提交的是一个Map,那么作用相当于 @Field
@POST("users/new")
Call<User> createUser(@Body User user);
@Url是动态的Url请求数据的注解。需要注意的是使用@Path时,path对应的路径不能包含”/”,不然每个加到host Url后面的东西都会被省略掉。千万注意了
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://ms.csdn.net/")
.build();
public interface csdnService {
@GET
Call> getData(@Url String user);
}
参考:
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);
这一块知识点有三个:
第一部分:在创建 Retrofit 实例时通过.baseUrl()设置,就是上面的
.baseUrl("http://apis.baidu.com/txapi/")
第二部分:在网络请求接口的注解设置,就是在上面的 API 接口中用 GET 注解的字符串:
@GET("word/word")
Retrofit 的网络请求的完整 Url = 创建 Retrofit 实例时通过.baseUrl+网络请求接口的注解设置(下面称“path”):如下有四种形式,建议采用第三种方式来配置,并尽量使用同一种路径形式。
//设置数据解析器
.addConverterFactory(GsonConverterFactory.create())
数据解析器使得来自接口的json结果会自动解析成定义好了的字段和类型都相符的json对象接受类。在Retrofit 2.0中,Package 中已经没有Converter了,需要自己创建一个Converter, 否则Retrofit只能接收字符串结果,剩下的json转换的活还得你自己来干。如果你想接收json结果并自动转换成解析好的接收类,必须自己创建Converter对象,然后使用addConverterFactory把它添加进来!
Retrofit支持多种数据解析方式,在使用时注意需要在Gradle添加依赖:
数据解析器 | Grade依赖 |
---|---|
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 | 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数据解析器
接口返回
Call<News> news = mApi.getNews("1", "10");
返回的Call可以理解成源生的了,默认就这么写。但像很多很多项目都是结合着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) {
//请求失败时候的回调
}
});
//对发送请求进行封装
Call<News> news = mApi.getNews("1", "10");
//发送网络请求(同步)
Response<Reception> response = news.execute();
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) {
//请求失败时候的回调
}
});
请求城市数据的url为:http://111.111.1.11/Base/getCities?clientVersion=205002&version=1622
请求方式为get,请求参数为clientVersion与version,请求数据为城市的id与name,那么使用Retrofit完成数据请求的流程如下
public class ApiService {
public static final String RES_GET_CITIES_LIST = "Base/getCities";
public interface CityService {
@GET(RES_GET_CITIES_LIST)
Call<CityManager> getCity
(@QueryMap Map<String, String> queryMap);
}
}
retrofit在使用过程中,需要定义一个接口对象,@GET标识为get请求,@GET中所填写的value值和baseUrl组成完整的路径,baseUrl在构造retrofit对象时给出。@QueryMap 标识为接口查询的关键字,这里需要的参数有两个,所以我使用了@QueryMap,与下面这种写法是等价的:
Call<CityManager> getCity
(@Query("clientVersion") String clientVersion, @Query("version") String version);
接口中的方法必须要有返回值,这里将我们定制的javabean对象传进去即可。
public static final String BASE_URL = "http://111.111.1.11/";
Map<String, String> queryMap = new HashMap<>();
queryMap.put("clientVersion", "205002");
queryMap.put("version", "1622");
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiService.CityService cityService = retrofit.create(ApiService.CityService.class);
Call<CityManager> call = cityService.getCity(queryMap);
call.enqueue(new Callback<CityManager>()
{
@Override
public void onResponse(Call<CityManager> call, Response<CityManager> response)
{
ArrayList<String> cityNames = new ArrayList<>();
ArrayList<String> cityIds = new ArrayList<>();
for (CityManager.CityBean city : response.body().getCities()) {
cityNames.add(city.getName());
cityIds.add((city.getId()));
}
}
@Override
public void onFailure(Call<CityManager> call, Throwable t)
{
//进行异常情况处理
}
});
Retrofit的构建使用的是构造者模式,指定一个baseUrl,添加一个对象转换器,用于将服务器返回的数据转换成对应实体类对象。构造完成以后,调用create方法就可以拿到我们的接口实例。然后再调用我们之前定义好的获取城市的方法,得到一个call对象,通过call.enqueue即可完成异步的网络请求。最后在数据请求成功的时候,通过response.body()即可拿到我们定义在Call< T >中需要返回的对象,数据请求失败的时候,进行异常的处理。
POST请求与GET请求算是我们日常开发中最最常用的两种网络访问方式
public interface IServiceApi {
@POST("/claims/preclaims")
Observable<PublicResponseEntity<PreclaimsResponseEntity>> postClaimPreclaims(@Header("Authorization") String authorization, @QueryMap HashMap<String, String> deviceInfo, @Body RequestBody body);
}
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下载文件方式与其他请求几乎无异,拿我用到下载PDF的程序来举例子
public interface IServiceApi {
····
//PDF文件Retrofit下载
@Streaming
@GET
Observable<ResponseBody> retrofitDownloadFile(@Url String fileUrl);
...
}
上面的代码有几个注意的点:
①@Streaming 是注解大文件的,小文件可以忽略不加注释,但是大文件一定需要注释,不然会出现OOM。
②fileUrl就是PDF的下载地址,通过参数形式传进来
③正常来讲,API接口的返回类型是Call,即:
public interface IServiceApi {
····
//PDF文件Retrofit下载
@Streaming
@GET
Call<ResponseBody> retrofitDownloadFile(@Url String fileUrl);
...
}
但是我项目中是Retrofit结合RXJava来使用的,我把它的返回值类型定义为Observable,推荐这种写法,便利于后续的数据处理
参考:Retrofit 基本用法