Retrofit 基本用法

公司一直在用 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);
}
 
          
  1. 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”)


image.png

建议采用第三种方式来配置,并尽量使用同一种路径形式。

②关于数据解析器(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>,强烈推荐这种写法,便利于后续的数据处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容

  • 原来公司用的是OKGO来加载网络,现在全部替换为Retrofit了,用起来挺不适应的,现在我负责的模块代码中网络数...
    SHERLOCKvv阅读 131,725评论 27 219
  • app/build.gradle添加依赖 根据json文件创建 用于接收服务器返回数据 的类 创建 用于描述网络请...
    shpunishment阅读 437评论 0 0
  • 安卓开发领域中,很多重要的问题都有很好的开源解决方案,例如Square公司提供网络请求 OkHttp , Retr...
    aaron688阅读 1,918评论 1 20
  • 红尘说:人是因为上天注定的缘分才聚集在一起。(199)今天,便是因为《梵香》的缘分相聚。 在红尘中静读《梵香》,于...
    江清秋阅读 820评论 0 4
  • 视频编码介绍 为什么进行压缩编码? 视频是由一帧帧的图像组成(见实例)比如一张Gif图片其实就可以被分解成若干张单...
    coderwhy阅读 2,322评论 3 15