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