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参数替换(包括查询参数和路径参数),以及表单编码和多部分请求功能。

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

推荐阅读更多精彩内容