Retrofit2文件上传

转载请注明出处:Retrofit2文件上传

前言

使用Retrofit2已经有一段时间了,在使用时一直在感叹库的易用性和灵活性,一直想深入的研究下源码和机制,但是项目催得紧,深陷泥潭无法脱身。果然在多文件上传时被卡住了。(今天犯懒,明天就遭报应)研究半天终于跑通,特此记录。

Http MultiPart消息

其实无论什么库,只要是发送Http请求,都得遵守Http协议,所以熟悉协议内容对理解库原理、调试是有很大帮助的。

Http上传协议为MultiPart。下面是通过抓包获取的一次多文件+文本的上传消息,每行前面的行数是为了标注说明方便加上的,实际请求中没有。

1  POST http://host:8080/updata.action HTTP/1.1
2  Content-Type: multipart/form-data; boundary=bec890b3-d76c-4986-803d-dc4b57ba2421
3  Content-Length: 3046505
4  Host: host:8080
5  Connection: Keep-Alive
6  Accept-Encoding: gzip
7  User-Agent: okhttp/3.2.0
8
9  --bec890b3-d76c-4986-803d-dc4b57ba2421
10 Content-Disposition: form-data; name="title"
11 Content-Type: text/plain; charset=utf-8
12 Content-Length: 15
13
14 多文件上传
15 --bec890b3-d76c-4986-803d-dc4b57ba2421
16 Content-Disposition: form-data; name="token"
17 Content-Type: text/plain; charset=utf-8
18 Content-Length: 32
19
20 登陆Token值
21 --776becce-5bd0-41d3-aa73-d3cd3ca4209d
22 Content-Disposition: form-data; name="imgUrls"; filename="0.jpg"
23 Content-Type: image/*
24 Content-Length: 168637
25
26 (文件字节,一堆乱码)@ h r   q   UY� e<�* ?  7C  Z 6�...
27 --776becce-5bd0-41d3-aa73-d3cd3ca4209d
28 Content-Disposition: form-data; name="imgUrls"; filename="1.jpg"
29 Content-Type: image/*
30 Content-Length: 164004
31
32 (文件字节,一堆乱码)@ h r   q   UY� e<�* ?  7C  Z 6�...
33 --776becce-5bd0-41d3-aa73-d3cd3ca4209d
34 Content-Disposition: form-data; name="imgUrls"; filename="2.jpg"
35 Content-Type: image/*
36 Content-Length: 167307
37
38 (文件字节,一堆乱码)@ h r   q   UY� e<�* ?  7C  Z 6�...
39 --776becce-5bd0-41d3-aa73-d3cd3ca4209d--
  • line1:请求行
  • line2-line7:消息头
  • line2:定义请求类型及分隔符
  • line9-line39:消息正文
  • line9:分隔符,用于分割正文的各条数据
  • line39:结尾分隔符
  • line10:name定义服务端获取本条数据的key
  • line17:Content-Type定义本条数据类型为文本,charset定义编码为utf-8
  • line22:name定义Key,filename定义上传的文件名
  • line23:Content-Type定义本条数据类型为图片文件

以上代码为一次多文件+文本的表单请求,Retrofit2基本将能封装的内容都封装了,我们需要做的就是通过MultiPartBody.Part或者MultiPartBody将文本及文件数据封装好并传到接口中。

Retrofit2实现上传请求

上面说到Retrofit2封装请求消息是不完全正确的,因为Retrofit2使用动态代理将具体的请求分发给具体的http client去执行,一般使用Okhttp。

定义上传接口

/**
 * 注意1:必须使用{@code @POST}注解为post请求
* 注意:使用{@code @Multipart}注解方法,必须使用{@code @Part}/
* {@code @PartMap}注解其参数
* 本接口中将文本数据和文件数据分为了两个参数,是为了方便将封装
* {@link MultipartBody.Part}的代码抽取到工具类中
* 也可以合并成一个{@code @Part}参数 * @param params 用于封装文本数据 * @param parts 用于封装文件数据 * @return BaseResp为服务器返回的基本Json数据的Model类 */ @Multipart @POST(RequestApiPath.UPLOAD_WORK) Observable requestUploadWork(@PartMap Map params, @Part List parts); /** * 注意1:必须使用{@code @POST}注解为post请求
* 注意2:使用{@code @Body}注解参数,则不能使用{@code @Multipart}注解方法了
* 直接将所有的{@link MultipartBody.Part}合并到一个{@link MultipartBody}中 */ @POST(RequestApiPath.UPLOAD_WORK) Observable requestUploadWork(@Body MultipartBody body);

MultipartBody.Part/MultipartBody的封装

/**
 * 将文件路径数组封装为{@link List}
 * @param key 对应请求正文中name的值。目前服务器给出的接口中,所有图片文件使用
* 同一个name值,实际情况中有可能需要多个 * @param filePaths 文件路径数组 * @param imageType 文件类型 */ public static List files2Parts(String key, String[] filePaths, MediaType imageType) { List parts = new ArrayList<>(filePaths.length); for (String filePath : filePaths) { File file = new File(filePath); // 根据类型及File对象创建RequestBody(okhttp的类) RequestBody requestBody = RequestBody.create(imageType, file); // 将RequestBody封装成MultipartBody.Part类型(同样是okhttp的) MultipartBody.Part part = MultipartBody.Part. createFormData(key, file.getName(), requestBody); // 添加进集合 parts.add(part); } return parts; } /** * 其实也是将File封装成RequestBody,然后再封装成Part,
* 不同的是使用MultipartBody.Builder来构建MultipartBody * @param key 同上 * @param filePaths 同上 * @param imageType 同上 */ public static MultipartBody filesToMultipartBody(String key, String[] filePaths, MediaType imageType) { MultipartBody.Builder builder = new MultipartBody.Builder(); for (String filePath : filePaths) { File file = new File(filePath); RequestBody requestBody = RequestBody.create(imageType, file); builder.addFormDataPart(key, file.getName(), requestBody); } builder.setType(MultipartBody.FORM); return builder.build(); }

文本类型的MultipartBody.Part封装

/**
 * 直接添加文本类型的Part到的MultipartBody的Part集合中
 * @param parts Part集合
 * @param key 参数名(name属性)
 * @param value 文本内容
 * @param position 插入的位置
 */
public static void addTextPart(List parts,
                              String key, String value, int position) {
    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value);
    MultipartBody.Part part = MultipartBody.Part.createFormData(key, null, requestBody);
    parts.add(position, part);
}

/**
 * 添加文本类型的Part到的MultipartBody.Builder中
 * @param builder 用于构建MultipartBody的Builder
 * @param key 参数名(name属性)
 * @param value 文本内容
 */
public static MultipartBody.Builder addTextPart(MultipartBody.Builder builder,
                                                String key, String value) {
    RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), value);
    // MultipartBody.Builder的addFormDataPart()有一个直接添加key value的重载,但坑的是这个方法
    // 不会设置编码类型,会出乱码,所以可以使用3个参数的,将中间的filename置为null就可以了
    // builder.addFormDataPart(key, value);
    // 还有一个坑就是,后台取数据的时候有可能是有顺序的,比如必须先取文本后取文件,
    // 否则就取不到(真弱啊...),所以还要注意add的顺序
    builder.addFormDataPart(key, null, requestBody);
    return builder;
}

你可能感兴趣的:(Retrofit2文件上传)