当前位置: 首页 > 工具软件 > Retrofit > 使用案例 >

Android进阶之路 - Retrofit注解全析

韦望
2023-12-01

我还记得2017年的时候就已经有同事慢慢开始使用Retrofit网络框架了,在2018年的时候也是同事封装好我直接使用的,索性2020年的时候自己写新项目,借着这个机会巩固封装了一波,只是时隔几个月才来记录,起初久久无法落笔,因为想写的有点多,最终决定分为几个部分进行记录 ~

兄弟篇

嗯哼,你为什么使用Retrofit啊?

嗯…嗯… 因为Retrofit是基于okHttp进行的二次封装,那么就首先就具备了okHttp的优势,即 

okHttp优点(远不止以下几点)

  • 内置连接池,减少请求延迟,支持连接复用;
  • 支持gzip压缩响应体,减少传输的数据量;
  • 通过缓存响应避免了重复的请求;
  • 支持http2、SPDY, 对一台机器的所有请求共享同一个socket;

okHttp缺点(远不止以下几点)

  • 消息传递需要频繁切换子主线程

At the same time,Retrofit支持了RxJava、RxAndroid的响应式编程,可快速动态切换子主线程,从而实现同步、异步的请求,这样在弥补了okHttp的不足之余,还二次进行了升级;

Last,Retrofit是一款注解型的网络框架,使用简单,扩展性强、耦合性低,安全性高、同时支持动态解析Model,可配置不同HTTP client来实现网络请求,如okhttp、httpclient等;

前情提要

基础了解

Retrofit的注解分类,我个人主要将其分为四种类型,即请求类、标记类、参数类

类型注解作用范围
请求类@GET
@POST
@PUT
@DELETE
@PATH
@HEAD
@OPTIONS
@HTTP
标记类@FormUrlEncodedPost请求
@MultipartPost请求、图片、文件上传
@StreamingGet请求、 图片、文件下载
参数类@Path通用
@Header通用
@Headers通用
@Url通用
@QueryGet请求
@QueryMapGet请求
@BodyPost请求
@FieldPost请求
@FieldMapPost请求
@Part图片、文件上传
@PartMap图片、文件上传
  • 模拟地址

日常开发中我们常会将网络请求的rul抽取在一个类中,BaseUrl 一般为服务器地址,其余url一般为对应接口的请求地址 ~

 //服务器地址,每个人都不同
 BaseUrl="http://test.retrofit.cn/"
 //接口地址
 ProductUrl="getProduct"
  • 返回类型

在网络请求中我们需要根据接口的返回体创建对应的model用于json接收和解析,所以T泛指当前接口返回的对应实体~

常规的话我们会和后台约定一个外层的基础model,然后内置不同的model,这点可查看我后续的文章 ~

示例:Get无参请求,这里的T指的更像是ProductBean这样的model

 @GET("getProduct")
 Call<T>getProductList();

HTTP协议补充

这里并非要开始讲Http协议、TLS、SSL、四层结构、七层结构、安全证书等等(哈哈....)

主要说一下在Http请求中,常见请求方式的使用场景(虽然下面也有写,但是先简单说一下)

  • Put 请求:常用于新增、更新数据的场景
  • Get请求:常用于获取数据的场景
  • Post请求:常用于安全获取数据的场景
  • Delete请求:常用于删除数据的场景

请求方式,如Put、Get、Post 等请求

请求方式:@PUT、@GET、@POST、@DELETE、@PATCH、@OPTIONS、@HEAD、@HTTP

常见的请求方式应该就是Get、Post请求了,但是Retrofit还支持了其他的多种请求方式用于适应更多场景 ~

HTTP版本请求方式
1.0GET
POST
HEAD
1.1PUT
DELETE
OPTIONS
TRACE
CONNECT

使用对应请求方式时,需要将对应注解声明在方法上方,目前主要涉及8种请求方式,除Get、Post之外的请求方式具体如下 ~

  • @PUT :提交资源或者更新资源(将资源提交到服务器,如果请求的url地址已经在服务器上存在对应的资源了,则put请求提交的实体则会对其进行修改)

  • @PATCH:PATCH请求是对PUT请求的补充,用于局部更新资源

  • @DELETE:DELETE方法请求源服务器删除Request-URI标识的资源(如果响应包括描述状态的实体,则成功响应应为200(OK),如果操作尚未执行,则应为202(已接受);如果操作已颁布但响应不包括,则应为204(无内容)一个实体)

  • @HEAD:HEAD方法与GET相同,只是服务器不能在响应中返回消息体

响应HEAD请求的HTTP头中包含的元信息应该与响应GET请求时发送的信息相同;该方法可用于获得关于请求所暗示的实体的元信息,而无需转移实体主体本身。此方法通常用于测试超文本链接的有效性,可访问性和最近的修改

对HEAD请求的响应是可缓存的,因为响应中包含的信息可用于从该资源更新先前缓存的实体;如果新字段值指示缓存的实体与当前实体不同(如Content-Length,Content-MD5,ETag或Last-Modified中的更改所示),则缓存必须将缓存条目视为陈旧

使用场景

  1. 检查资源的有效性
  2. 检查超链接的有效性
  3. 检查网页是否被串改
  4. 用于自动搜索机器人获取网页的标志信息,获取rss种子信息,或者传递安全认证信息等
  • @OPTIONS:主要用途有俩种
  1. 获取服务器支持的HTTP请求方法,是黑客喜欢使用的方法。
  2. 用来检查服务器的性能,如:AJAX进行跨域请求时的预检,需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全
  • @HTTP

该注解其实是通用注解,可动态配置其余7种请求方式,相比其他注解请求专一性弱,扩展性高,所以在开发中很少使用~

Http参数

  • method 请求方式
  • path 请求地址(除域名外的地址)
  • hasBody(默认为false,通常不写)

以Get、Post为例

  • GET
 @HTTP(method = "GET", path = "getProduct", hasBody = false)
 Call<T> getProductList();
  • POST (PS:1.加入FormUrlEncoded注解 2.hasBody = true)
 @FormUrlEncoded
 @HTTP(method = "POST", path = "getProduct", hasBody = true)
 Call<T> getProductList(@FieldMap Map<String, String> params);

常用注解

@Url

接口地址,出现在声明接口的场景中,主要有俩种设置方式

  • 入门写法:方法参数内通过@Url进行添加
 @GET
 Call<T> getProductList(@Url String url);
  • 常规写法:请求方式后进行添加
 @GET("getProduct")
 Call<T>getProductList();

@Path

地址参数,出现在需要动态补全请求地址的场景中

  • 单参

示例1

 @GET("getProduct/{userId}")
 Call<T> getProductList(@Path("userId")String userId);

示例 2

 @GET("getProduct/{userId}/date")
 Call<T> getProductList(@Path("userId")String userId);
  • 多参
 @GET("getProduct/{userId}/{time}")
 Call<T> getProductList(@Path("userId") String userId, @Path("time")String time);

@Header 、@Headers

在开发实战中,每逢和后台沟通接口时总会有请求头浮现,所有该注解还是蛮常用的 ~

在开发使用中,一般关于请求头的设置方式有三种

here:具有相同名称的请求头不会相互覆盖,而是会照样添加到请求头中

设置方式

  • @Heade 单请求头(动态)
    @FormUrlEncoded
	@POST("getProduct")
    Call<T> getProductList(@Header("token") int token);
  • @Headers多请求头(静态)
 @Headers({
    "header1:headerValue1",
    "header2:headerValue2",
 })
 @POST("getProduct")
 Call<T> getProductList();

@GET(@Query、@QueryMap)

最常见的请求方式之一,相比post优势在于明文请求,测试相对简单,缺点就是安全性低一些 ~

主要使用注解: @GET、@Query、@QueryMap

  • 无参请求 (实际路径:http://test.retrofit.cn/getProduct)
 @GET("getProduct")
 Call<T> getProductList();
  • 单参请求(实际路径:http://test.retrofit.cn/getProduct?userId={userId参数})
 @GET("getProduct")
 Call<T> getProductList(@Query("userId") String userId);
  • 多参请求(实际路径:http://test.retrofit.cn/getProduct?userId={userId参数}&time={time参数})

此方式其实还是由单参组合而成,请继续往下看

 @GET("getProduct")
 Call<T> getProductList(@Query("userId") String userId, @Query("time")String time);

一般多参Get请求,我们使用@QueryMap注解,传入对应类型的map,具体参数继续往下看

 @GET("getProduct")
 Call<T> getProductList(@QueryMap Map<String, String> map);

map传入方式,内部包含了后台要求的字段信息

 HashMap<String, Object> map = new HashMap<>();
 map.put(userId,"传入userId");
 map.put(time,"传入time");

@POST(@FormUrlEncoded、@Field、@FieldMap)

相比Get请求而言,Post请求主要在于提升了数据的安全性,在实现方面除注解名不同外,实现方法相似;但要注意@FormUrlEncoded注解 ~

主要使用注解: @POST、@FormUrlEncoded、@Field、@FieldMap

ps:关于使用@FormUrlEncoded注解时要注意,其使用场景一般都建立在Post请求需要传入参数的场景下,如无需传入参数则不需要调用@FormUrlEncoded注解!

  • 使用@FormUrlEncoded注解,表示请求正文将使用表单网址编码
  • 自带”application / x-www-form-urlencoded” MIME类型,字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码
  • 无参请求
 @POST("getProduct")
 Call<T> getProductList();
  • 单参请求
 @FormUrlEncoded
 @POST("getProduct")
 Call<T> getProductList(@Field("userId") String userId);
  • 多参请求(实际路径:http://test.retrofit.cn/getProduct?userId={userId参数}&time={time参数})

此方式其实还是由单参组合而成,请继续往下看

 @FormUrlEncoded
 @POST("getProduct")
 Call<T> getProductList(@Field("userId") String userId, @Field("time")String time);

一般多参Post请求,我们使用@QueryMap注解,传入对应类型的map,具体参数继续往下看

 @FormUrlEncoded
 @POST("getProduct")
 Call<T> getProductList(@FieldMap Map<String, String> map);

map传入方式,内部包含了后台要求的字段信息

 HashMap<String, Object> map = new HashMap<>();
 map.put(userId,"传入userId");
 map.put(time,"传入time");

@Body

该注解常用于Post请求,一般建立在传参较多且较乱的复杂场景下,关于它的使用大家需要注意几点

  • 需创建传参对应的实体包装类,如下方的TBean(关于实体类最好是public权限,同时声明有参无参、get/set方法)
  • @Body标签不能和@FormUrlEncoded或@Multipart标签同时使用(会报错)
 //接口
 @POST("getProduct")
 Call<ResponseBody> getProductList(@Body TBean bean);

//实体类
public class TBean {
    private String userId;
    private String time;

    public TBean(String userId, String time) {
        this.userId = userId;
        this.time = time;
    }

    public TBean() {
        super();
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }
}

@DELETE

这个请求方式比较个性一点,首先这种请求方式并不常用,同时删除方式有点区别(这里是在我2022年的时候补全的,用的是协程做的请求,大家看自己需要关注的就行)

  • path传参
  @DELETE("member/dynamic/{dynamicNo}")
  suspend fun delMemberDynamic(@Path("dynamicNo") dynamicNo: String): TNResponse<Boolean>
  • body传参注意:使用 @HTTP注解的原始方法进行实现
  @HTTP(method = "DELETE", path = "member", hasBody = true)
  suspend fun delMember(@Body edit: UpdatePhone): TNResponse<Boolean>

@Part、@PartMap(@Multipart)

@Part和@PartMap注解使用场景相对比较有针对性,一般都是用于图片上传、文件上传的场景 !

主要使用注解: @Multipart、@Part、@PartMap

上传方式

 //单张上传
 @Multipart
 @POST("upload")
 Call<T> uploadFile(@Part() RequestBody file);

 //图文上传
 @Multipart
 @POST("upload")
 Call<T> upload(@Part("description") RequestBody description, @Part MultipartBody.Part file);

 //多张上传
 @Multipart
 @POST("upload")
 Call<T> uploadFiles(@PartMap Map<String, RequestBody> param);

辅助工具

因传入参数类型基本为RequestBody类型,故可使用下方俩个方法快速转换

 public static RequestBody toRequestBodyOfText (String value) {
        RequestBody body = RequestBody.create(MediaType.parse("text/plain"), value);
        return body ;
 }

 public static RequestBody toRequestBodyOfImage(File pFile){
        RequestBody fileBody = RequestBody.create(MediaType.parse("image/*"), pFile);
        return fileBody;
 }

调用场景 - 图文上传

 //需要上传的文件
 File file = new File(path);
 //不论是文本或图片文件均可通过辅助工具修改其MediaType
 RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
 //使用MultipartBody.Part时需要和服务端约定好Key,这里的part name是我们自定的image
 MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestBody);
 // 添加上传文件描述
 String descriptionString = "文件描述";
 RequestBody description = RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);

扩展

@Part扩展

  • 如果类型是okhttp3.MultipartBody.Part,内容将被直接使用;
    省略part中的名称,即 @Part MultipartBody.Part part
  • 如果类型是RequestBody,那么该值将直接与其内容类型一起使用;
    在注释中提供part名称(例如,@Part(“foo”)RequestBody foo)
  • 其他对象类型将通过使用转换器转换为适当的格式;
    在注释中提供part名称(例如,@Part(“foo”)Image photo)

@PartMap扩展

  • 如果类型是RequestBody,那么该值将直接与其内容类型一起使用。
  • 其他对象类型将通过使用转换器转换为适当的格式。

@Streaming

Retrofit中下载的主要注解方式,一般常见于图片下载,具体使用如下 ~

 @GET
 @Streaming
 Call<ResponseBody> downloadImage(@Url String url);

参考Blog

 类似资料: