Retrofit解析2之使用简介

整体Retrofit内容如下:

前面介绍完RESTful之后,我们先来初步认识下Retrofit的使用"姿势"。本文的主要内容如下:

  • 1、Retrofit是什么?
  • 2、Retrofit的配置
  • 3、Retrofit的那些注解
  • 4、为什么要用Retrofit?
  • 5、Form表单提交与multipart/form-data
  • 6、Retrofit2 对multipart/form-data的支持
  • 7、总结

一、什么是Retrofit

Retrofit的官网是这样说的:

A type-safe HTTP client for Android and Java

我简单翻译一下就是:

一个类型安全的、Android或者Java的客户端

通过使用注解去描述一个HTTP请求,并且支持Multipart请求和文件上传。

我理解的Retrfit: 一个可以简化我们网络操作的工作的第三方库。当然我们自己也可以实现,但是自己去实现带来的是比较高的时间成本和检验成本。同样,Retrofit是Square公司开源的一个高质量高效率的HTTP库,它将我们自己开发的底层的代码和细节都封装了起来,有了Retrofit之后我们对于一些请求我们就只需要一行代码或者一个注解。所有的网络通信,其核心任务就只有一个就是:Client端与Server端进行数据和交互操作,所有Retrofit就将底层代码都封装起来,只是暴露除了我们业务中的数据模型和操作方法。下面让我们了解下Retrofit的配置

retrofit.png

二、Retrofit的配置

1、首选在你的项目build.gradle里面添加如下配置
compile 'com.squareup.retrofit2:retrofit:2.2.0'

如果你序列化 采用GSON,同时回调采用RxJava 处理,还应该添加如下内容

 compile 'com.squareup.retrofit2:converter-gson:2.1.0'
 compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

当然也别忘记添加网络权限

<uses-permission android:name="android.permission.INTERNET" />
2、定义返回值实体类

这个类煲剧哦返回数据的所有属性

public class GitHubRepo {  
    private int id;
    private String name;

    public GitHubRepo() {
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
3、定义网络接口

写一个interface名字是GitHubService

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}
4、创建Retrofit对象
 retrofit = new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .client(client)//这个client是OkHttpClient,以后和Okhttp的基本用法和流程分析中细说
      //          .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(new Gson()))
                .build();
5、创建Service对象
GitHubService service=retrofit.create(GitHubService.class);
6、发起请求
Call<List<Repo>> repos = service.listRepos("octocat");

三、注解详解

Retrofit 2.3 包含很多的注解,包括如下内容:

  • 1、方法注解:@GET @POST、@PUT、@DELETE、@PATCH、@OPTIONS、@HTTP
  • 2、标记注解:@FormUrlEncoded、@Multipart、@Streaming
  • 3、参数注解:@Query 、@QueryMap、@Body、@Field、@FieldMap、@Part、@PartMap
  • 4、其它注解:@Path、@Header、@Headers、@Url

(一)、方法注解:

1、@GET:用于发送一个get请求

@GET 注解一般必须添加相对路径或者绝对路径或者全路径,如果不想用在@GET 注解后添加请求路径,则可以在方法的第一个参数中用@Url 添加请求路径。

2、@POST:用于发送一个POST请求

@POST 注解一般必须添加相对路径或绝对路径或者全路径,如果不想在@POST 后添加请求路径,则可以在方法的第一个参数用@Url 注解添加请求路径。

3、@PUT:用于发送一个PUT请求

@PUT 注解一般必须添加相对路径或者绝对路径或者全路径,如果不想在PUT注解后添加请求路径,则可以在方法的第一个参数用@Url 注解添加请求路径。

4、@DELETE:用于发送一个DELETE请求

@DELETE 注解 一般必须添加相对路径或者绝对路径或者全路径,如果不想在DELETE注解后添加请求路径,则可以在方法的第一个参数中用@Url 注解添加请求路径。

5、@PATCH:用于发送一个PATCH请求

@PATCH 注解 一般必须添加相对路径或绝对路径或者全路径,如果不想在PATCH注解后添加请求路径,则可以在方法的第一恶参数用@Url 注解添加请求路径

6、@OPTIONS:用于发送一个OPTIONS请求

@OPTIONS 注解一般必须添加相对路径或绝对路径或者全路径,如果不想在OPTIONS注解后添加请求路径,则可以在方法的第一个参数用@Url 注解添加请求路径。

7、@HTTP:作用于方法,用于发送一个自定义的HTTP

如下所示:

//自定义HTTP请求的标准样式
interface Service {
     @HTTP(method = "CUSTOM", path = "custom/endpoint/")
     Call<ResponseBody> customEndpoint();
   }
//发送一个DELETE请求
interface Service {
     @HTTP(method = "DELETE", path = "remove/", hasBody = true)
     Call<ResponseBody> deleteObject(@Body RequestBody object);
   }

(二)、标记注解:

1、@FormUrlEncoded:用于修饰Fiedl注解 和FileldMap注解

使用该注解,表示请求正文将使用表单网址编码。字段应该声明为参数,并用@Field 注解和 @FieldMap 注解,使用@FormUrlEncoded 注解的请求将具有"application/x-www-form-urlencoded" MIME类型。字段名称和值将先进行UTF-8进行编码,再根据RFC-3986进行URI编码。

2、@Multipart:作用于方法

使用该注解,表示请求体是多部分的,每个部分作为一个参数,且用Part注解声明。

3、@Streaming:作用于方法

未使用@Straming 注解,默认会把数据全部载入内存,之后通过流获取数据也是读取内存中数据,所以返回数据较大时,需要使用该注解。 处理返回Response的方法的响应体,用于下载大文件

@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);

提醒:如果是下载大文件必须加上@Streaming 否则会报OOM

(三)、参数注解

1、@Query:作用于方法参数,用于添加查询参数,即请求参数

参数值通过String.valueOf()转换为String 并进行URL编码,使用该注解定义的参数,参数值可以为空,为空时,忽略该值,当传入一个List或array时,为每个非空item拼接请求键值对,所有的键是统一的,如:name=张三&name=李四&name=王五。

@GET("/list")
Call<ResponseBody> list(@Query("page") int page);
@GET("/list")
Call<ResponseBody> list(@Query("category") String category);
//传入一个数组
@GET("/list")
Call<ResponseBody> list(@Query("category") String... categories);
//不进行URL编码
@GET("/search")
Call<ResponseBody> list(@Query(value="foo", encoded=true) String foo);
2、@QueryMap:作用于方法的参数

以map的形式添加查询参数,即请求参数,参数的键和值都通过String.valueOf()转换为String格式。默认map的值进行URL编码,map中的每一项发键和值都不能为空,否则跑出IllegalArgumentException异常。 示例如下:

//使用默认URL编码
@GET("/search")
Call<ResponseBody> list(@QueryMap Map<String, String> filters);
//不使用默认URL编码
@GET("/search")
Call<ResponseBody> list(@QueryMap(encoded=true) Map<String, String> filters);
3、@Body:作用于方法参数

使用@Body 注解定义的参数不能为null 当你发送一个post或put请求,但是又不想作为请求参数或表单的方式发送请求时,使用该注解定义的参数可以直接传入一个实体类,retrofit会通过convert把该实体序列化并将序列化的结果直接作为请求体发送出去。 示例如下: 先来看下实体类

   class Repo {
    final String owner;
    final String name;
    Repo(String owner, String name) {
      this.owner = owner;
      this.name = name;
    }
  }

再来看下接口类

  interface Service {
    @POST("/")
    Call<ResponseBody> sendNormal(@Body Repo repo);
4、@Field:作用于方法的参数

用String.valueOf()把参数值转换为String,然后践行URL编码,当参数值为null是=时,会自动忽略,如果传入的是一个List或者array,则为每一个非空的item拼接一个键值对,每一个键值对中的键是相同的,值就是非空的item的值。如:name=张三&name=李四&name=王五,如果itme的值有空格,在拼接的时候会自动忽略,例如某个item的值为:张 三,则拼接后为name=张三。 示例如下:

//普通参数
@FormUrlEncoded
@POST("/")
Call<ResponseBody> example(@Field("name") String name,@Field("occupation") String occupation);

//固定或可变数组
@FormUrlEncoded
@POST("/list")
Call<ResponseBody> example(@Field("name") String... names);
5、@FieldMap:作用于方法的参数

map中的每一项的键和值都不能为空,否则抛出IllegalArgumentException异常。 如下:

@FormUrlEncoded
@POST("/things")
Call<ResponseBody> things(@FieldMap Map<String, String> fields);
6、@Part:作用于方法的参数,用于定义Multipart请求的每和part

使用该注解定义的参数,参数值可以为空,为空时,则忽略。使用该注解定义的参数类型有如下3中方式可选:

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

示例如下:

@Multipart
@POST("/")
Call<ResponseBody> example(
       @Part("description") String description,
       @Part(value = "image", encoding = "8-bit") RequestBody image);
7、@PartMap:作用于方法的参数

以map的方式定义Multipart请求的每个part map中每一项的键和值都不能为空,否则抛出IllegalArgumentException异常。 使用@PartMap 注解定义的参数类型有一下两种:

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

(四)其他注解:

1、@Path:用于方法的参数

在URL路径中替换指定参数值。使用String.valueOf()和URL编码将值转换为字符串。 使用@Path 注解 定义的参数的值不能为空,参数值默认使用URL编码。

2、@Header:作用于方法的参数,用于添加请求头

使用 @Header 注解 定义的请求头可以为空,当为空时,会自动忽略,当传入一个List或者array时,为拼接每个非空的item的值到请求头中。 具有相同名称的请求头不会相互覆盖,而是照样添加到请求头中 代码如下:

@GET("/")
Call<ResponseBody> foo(@Header("Accept-Language") String lang);
3、@Headers:作用于方法,用于添加一个或多个请求头中

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

//添加一个请求头
 @Headers("Cache-Control: max-age=640000")
 @GET("/")

//添加多个请求头
@Headers({ "X-Foo: Bar", "X-Ping: Pong"})
@GET("/")
4、@Url: 作用于方法参数

用于添加请求的接口地址: 代码如下:

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

(四)注意事项:

  • 1、Map用来组合复杂的参数,并且对于FieldMap,HeaderMap,PartMap,QueryMap这四种作用方法的注解,其参数类型必须为Map实例,且key的类型必须为String类型,否则抛出异常。
  • 2、Query、QueryMap与Field、FieldMap功能一样,生成的数据形式一样;Query、QueryMap的数据体现在Url上;Field、FieldMap的数据是请求体
  • 3、{占位符}和PATH尽量只用在URL的path部分,url的参数使用Query、QueryMap代替,保证接口的简洁
  • 4、Query、Field、Part支持数据和实现了iterable接口的类型,如List、Set等,方便向后台传递数组,代码如下:
  • 5、以上部分注解真正的实现在ParameterHandler类中,每个注解的真正实现都是ParameterHandler类中的一个final类型的内部类,每个内部类都对各个注解的使用要求做了限制,比如参数是否可空、键和值是否可空等。
  • 6、@FormUrlEncoded 注解和@Multipart 注解不能同时使用,否则会抛出methodError(“Only one encoding annotation is allowed.”),可在ServiceMethod类中parseMethodAnnotation()方法中找到不能同时使用的具体原因。
  • 7、@Path 与@Url 注解不能同时使用,否则会抛出parameterError(p, "@Path parameters may not be used with @Url."),可在ServcieMethod类中parseParameterAnnotation()方法中找到不能同时使用的具体代码。其实原因也是很好理解:Path注解用于替换url中的参数,这就要求在使用path注解时,必须已经存在请求路径。不然没法替换路径中指定的参数。而@Url 注解是在参数中指定了请求路径的,这时候情定请求路径已经晚,path注解找不到请求路径,更别提更换请求路径了中的参数了。
  • 8 使用@Body 注解的参数不能使用form 或multi-part编码,即如果为方法使用了FormUrlEncoded或Multipart注解,则方法的参数中不能使用@Body 注解,否则会抛出异常parameterError(p, “@Body parameters cannot be used with form or multi-part encoding.”)

四、为什么要用Retrofit

1、咱们一起来看下Retrofit的优点:

  • 请求的方法参数注解可以定制
  • 支持同步、异步和RxJava
  • 超级解耦(我最爱)
  • 可以配置不同的反序列化工具来解析数据,如json、xml等

2、为什么使用Retrofit?

在处理HTTP请求的时候,因为不同场景或者边界情况等比较难处理。你需要考虑网络状态,需要在请求失败后重试,需要处理HTTPS等问题,二这些事情让你很苦恼,而Retrofit可以将你从这些头疼的事情中解放出来。 当然你也可以选择android-async-http和Volley,但为什么选择Retrofit?首先效率高,其次Retrofit强大且配置灵活,第三和OkHttp无缝衔接,第四Jack Wharton主导的(你懂的)。 在Retrofit2之前,OkHttp是一个可选的客户端。二Retrofit2中,Retrofit与OkHttp强耦合,使得更好地利用OkHttp,包括使用OkHttp解决一些棘手的问题。

五、Form表单提交与multipart/form-data

由于后面涉及到Form表单提交数据的格式,为了方便部分人更好的理解,我先在这里讲解下。

(一)、Form表单

1、form表单常用属性

  • action:url 地址,服务器接收表单数据的地址
  • method:提交服务器的http方法,一般为post和get
  • name:最好好吃name属性的唯一性
  • enctype: 表单数据提交时使用的编码类型,默认使用"pplication/x-www-form-urlencoded",如果是使用POST请求,则请求头中的content-type指定值就是该值。如果表单中有上传文件,编码类型需要使用"multipart/form-data",类型,才能完成传递文件数据。
2、浏览器提交表单时,会执行如下步骤

  • 1、识别出表单中表单元素的有效项,作为提交项
  • 2、构建一个表单数据集
  • 3、根据form表单中的enctype属性的值作为content-type对数据进行编码
  • 4、根据form表单中的action属性和method属性向指定的地址发送数据
3、提交方式

  • 1、get:表单数据会被encodeURIComponent后以参数的形式:name1=value1&name2=value2 附带在url?后面,再发送给服务器,并在url中显示出来。
  • 2、post:content-type 默认"application/x-www-form-urlencoded"对表单数据进行编码,数据以键值对在http请求体重发送给服务器;如果enctype 属性为"multipart/form-data",则以消息的形式发送给服务器。
4、POST请求

HTTP/1.1 协议规定的HTTP请求方法有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 这几种。其中POST一般用于向服务器提交数据。

大家知道,HTTP协议是以ASCII 码传输,建立在TCP/IP协议之上的应用层规范。规范把HTTP请求分为3大块:状态行、请求头、消息体。类似于如下:

<method> <request-URL> <version>
<headers>
<entity-body>

协议规定POST提交的数据必须放在消息主题(entity-body)中,但协议并没有规定数据必须使用什么编码方式。实际上,开发者可以自己决定消息体的格式,只要后面发送的HTTP请求满足上面的格式就可以了。

但是,数据发送出去后,还要服务器解析成功才有意义。一般服务器都内置了自动解析常见数据格式的功能。服务端通常是根据请求头(headers)中的Content-Type字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。所以说到POST提交数据方法,包含了Content-Type和消息主题编码方式两部分。

5、enctype 指定的 content-type

  • application/x-www-form-urlencoded
  • application/json
  • text/xml
  • multipart/form-data

下面我们就详细的介绍下它们。

(一)、application/x-www-form-urlencoded

这应该是最常见的POST提交数据的方式了。浏览器的原生<form>表单,如果不设置enctype属性,那么最终会以application/x-www-form-urlencoded方法提交数据。请求类似于如下内容(省略了部分无关的内容):

POST http://www.hao123.com/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

  • 1、Content-Type 被指定为 application/x-www-form-urlencoded。
  • 2、提交的数据按照key-value的格式,也就是key1=value1,key2=value2这种方式进行编码,key和val都进行URL转码。大部分服务器都对这种方式支持。

(二)、application/json

application/json 这个Content-Type作为响应头大家肯定不陌生。事实上现在已经基本都是都是这种方式了,来通知服务器消息体是序列化后的JSON字符串。由于JSON规范的流行,除了低版本的IE之外的现在主流浏览器都原生支持JSON。当然服务器也有处理JSON的函数。

JSON格式支持比键值对更复杂的结构化数据,这样点也很有用,在需要提交数据层次非常深的数据时,用JSON序列化之后提交,非常方便。

例如:

POST http://www.hao123.com/ HTTP/1.1 
Content-Type: application/json;charset=utf-8
{"title":"test","sub":[1,2,3]}

这种方案,可以很方便的提交复杂的结构化的数据,特别适合RESTful的接口。而且各大抓包工具如chrome自带的开发者工具,Firebug、Fidder,都会以树形结构展示JSON数据,非常友好。

(三)、text/xml

它是一种使用HTTP作为传输协议,XML作为编码方式的远程调用规范。典型的XML-RPC是这样的:

POST http://www.example.com HTTP/1.1 
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
    <methodName>examples.getStateName</methodName>
    <params>
        <param>
            <value><i4>41</i4></value>
        </param>
    </params>
</methodCall>

XML-RPC 协议很简单、功能够用,各种语言的实现都有。它的使用也很广泛,但是我还是比较倾向于JSON,因为相比于JSON,XML太过于臃肿。

(四)、multipart/form-data

在最初的http协议中,没有定义上传文件的Method, 为了实现这个功能,http协议组改造了post请求,添加一种post规范,设定这种规范的Content-Type为multipart/form-data;boundary=${bound},其中${bound}是定义分割符,用于分割各项内容(文件,key-value对),不然服务器无法正确识别各项内容。post body里需要用到,尽量保证随机唯一。

这又是一个常见的POST数据提交的方式。我们使用表单上传文件时,必须让form表单enctype等于multipart/form-data。 例如: 假设 form 如下:

<form action="/upload" enctype="multipart/form-data" method="post">
    Username: <input type="text" name="username">
    Password: <input type="password" name="password">
    File: <input type="file" name="file">
    <input type="submit">
</form>

header

Content-Type: multipart/form-data; boundary={boundary}\r\n

body

普通 input 数据

--{boundary}\r\n
Content-Disposition: form-data; name="username"\r\n
\r\n
Tom\r\n

文件上传 input 数据

--{boundary}\r\n
Content-Disposition: form-data; name="file"; filename="myfile.txt"\r\n
Content-Type: text/plain\r\n
Content-Transfer-Encoding: binary\r\n
\r\n
hello word\r\n

结束标志

--{boundary}--\r\n

数据示例

POST /upload HTTP/1.1
Host: 172.16.100.128:5000
Content-Length: 394 
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLumpDpF3AwbRwRBn
Referer: http://172.16.100.128:5000/

------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="username"

Tom
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="password"

passwd
------WebKitFormBoundaryUNZIuug9PIVmZWuw
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: text/plain

hello world
------WebKitFormBoundaryUNZIuug9PIVmZWuw--

这个例子稍微复杂点。首先生成了一个boundary用于分割不同的字段,为了避免与正文内容重复,boundary很长很复杂。然后Content-Type里指明了数据以multipart/form-data来编码,本次请求的boundary是什么内容。消息主体里按照字段个数又分为多个结构类型的部分,每个部分都以---boundary开始,紧接着是内容描述信息,然后是回车,然后是字段的具体内容(文本和二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以----boundary----标志结束。

(五)、MIME类型

大家已经知道了目前市场上主流上四个方式

  • application/x-www-form-urlencoded
  • application/json
  • text/xml
  • multipart/form-data

其实还有一种类型是text/plain,text/plain是纯文本传输的意思,在发邮件的时候要设置这种编码类型,否则会出现接受时编码混乱的问题。网络上经常拿text/plain和text/html做比较,其实这两个很好区分,前者用来传输纯文本文件,后者则是传递html代码的编码类型,在发送头文件时才用得上。

上面提到的MIME,它的英文全称是"Multipurpose Internet Mail Extensions"多功能Internet邮件扩充服务,它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但是后来也应用到浏览器。服务器会将它们发送的多媒体数据的类型告诉浏览器,而通知手段就是说明该多媒体的MIME类型,从而让浏览器知道接受到的信息哪些是MP3,哪些是Shockwave文件等等。服务器将MIME标识符放入传送的数据中来告诉浏览器使用哪个插件读取相关文件。

每个MIME类型由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类。 常见的MIME类型

  • 超文本标记语言文本 .html,.html text/html
  • 普通文本 .txt text/plain
  • RTF文本 .rtf application/rtf
  • GIF图形 .gif image/gif
  • JPEG图形 .jpeg,.jpg image/jpeg
  • 声音文件 .au audio/basic
  • MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
  • RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
  • MPEG文件 .mpg,.mpeg video/mpeg
  • AVI文件 .avi video/x-msvideo
  • GZIP文件 .gz application/x-gzip
  • TAR文件 .tar application/x-tar

Internet中有一个专门组织IANA来确定标准的MIME类型,但是Internet发展的太快,很多应用程序等不及IANA来确认他们使用的MIME类型为标准类型。因此它们使用在类别中以x-开头的方法标示这个类别还没有成为标准,例如:x-gzip,x-tar等。事实上这些类型运用的很广泛,已经成为了实际标准。只要客户端和服务器共同承认合格MIME类型,即使它不是标准的类型也没有关系,客户程序就能根据MIME类型,采用具体的处理手段来处理数据。而服务器和客户端,缺省都设置了标准和常见的MIME类型,只有对不常见的MIME类型,才需要同时设置服务器和客户端,以进行识别。

六、Retrofit2 对multipart/form-data的支持

我们知道Retrofit其实是一个网络代理框架,负责封装请求,然后把请求分发给http协议,具体实现者是okhttpclient。

(一)、如何使用Retrofit和okHttp封装multipart/form-data

1、在retrofit2中:

  • 使用** @retrofit2.http.Multipart : 标记一个请求是multipart/form-data类型,需要和 @retrofit2.http.POST **一同使用,并且方法参数必须是 ** @retrofit2.http.Part **注解。
  • ** @retrofit2.http.Part **: 代表Multipart里的一项数据,即用${bound}分隔的内容块。
2、在okhttp3中:

  • okhttp3.MultipartBody :multipart/form-data 的抽象封装,继承okhttp3.RequestBody
  • okhttp3.MultipartBody.Part:multipart/form-data里的一项数据。
3、Service接口定义

假设服务器上传接口返回数据类型为application/json,字段如下

{
data: {},
msg: "上传成功",
code: 200
}

因此需要对返回数据封装成一个对象:

public class NetResponse<T> {
    public int code;
    public String msg;
    public T data;
}

访问接口的定义

public interface uploadFileService {
    /**
     * 通过 List<MultipartBody.Part> 传入多个part实现多文件上传
     * @param parts 每个part代表一个
     * @return 状态信息
     */
    @Multipart
    @POST("users/image")
    Call<NetResponse<Object>> uploadFilesWithParts(@Part() List<MultipartBody.Part> parts);


    /**
     * 通过 MultipartBody和@body作为参数来上传
     * @param multipartBody MultipartBody包含多个Part
     * @return 状态信息
     */
    @POST("users/image")
    Call<NetResponse<Object>> uploadFileWithRequestBody(@Body MultipartBody multipartBody);
}

所以,我们知道,有两种方式可以实现上传

  • 1、使用@Multipart注解方法,并用@Part注解方法参数,类型是List<okhttp3.MultipartBody.Part>,或者
  • 2、不使用@Multipart注解方法,直接使用@Body注解方法参数,类型是okhttp3.MultipartBody

可以看到,无论方法参数类型是MultipartBody.Part还是MultipartBody,这些类都不是Retrofit的类,而是okhttp实现上传的源生类。

为什么可以这样写:

Retrofit会判断@Body的参数类型,如果参数类型是okhttp3.RequestBody,则Retrofit不做包装处理,直接丢给okhttp3处理。而MultipartBody是继承 RequestBody,因此Retrofit不会自动包装这个对象。同理,Retrofit会判断@Part的参数类型,如果参数为okhttp3.MultipartBody.Part,则Retrofit会把RequestBody封装成MultipartBody,再把Part添加到MultipartBody。

七、总结

Retrofit将REST API抽象成Java接口,使用注解来描述每一个API地址和请求,支持URL参数替换(包括查询参数和路径参数),以及表单编码和多部分请求功能。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

【答疑解惑】如何避免程序崩溃之一

避免程序崩溃,有很多方法,分别针对不同的崩溃原因,我今天想谈谈一种程序员经常碰到的、不管是初学者甚至编程老手都经常犯的错误,就是程序运行时栈的崩溃。 这种错误相...

3658
来自专栏蓝天

从程序员角度看ELF

原文:http://xcd.blog.techweb.com.cn/archives/222.html

1044
来自专栏JackieZheng

Spring实战——缓存

缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis…… 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓...

21010
来自专栏CSDN技术头条

一图读懂JVM架构解析

本文阐述了JVM的构成和组件,配图清晰易懂,是学习Java开发者的入门必读文章。 每个Java开发人员都知道字节码经由JRE(Java运行时环境)执行。但他们或...

2258
来自专栏JackieZheng

Spring实战——缓存

缓存 提到缓存,你能想到什么?一级缓存,二级缓存,web缓存,redis…… 你所能想到的各种包罗万象存在的打着缓存旗号存在的各种技术或者实现,无非都是宣扬缓...

22210
来自专栏好好学java的技术栈

各大公司Java后端开发面试题总结

Java虚拟机规范中将Java运行时数据分为六种。 1.程序计数器:是一个数据结构,用于保存当前正常执行的程序的内存地址。Java虚拟机的多线程就是通过线程轮流...

6155
来自专栏jeremy的技术点滴

java中用好cache

2867
来自专栏Android相关

Android---SharedPreferences解析

SharedPreferences真正实现的类是:SharedPreferencesImpl

1983
来自专栏linux驱动个人学习

Linux进程启动过程分析do_execve(可执行程序的加载和运行)---Linux进程的管理与调度(十一)

我们前面提到了, fork, vfork等复制出来的进程是父进程的一个副本, 那么如何我们想加载新的程序, 可以通过execve来加载和启动新的程序。

3903
来自专栏java 成神之路

深入理解 Synchronized

2839

扫码关注云+社区

领取腾讯云代金券