原文出处:http://www.chenkaihua.com/2016/04/02/retrofit2-upload-multipart-files.html

Retrofit2是目前很流行的android网络框架,运用注解和动态代理,极大的简化了网络请求的繁琐步骤,非常适合处理restfull网络请求。在项目中,经常需要上传文件到服务器,有时候是需要上传多个文件。网上文章基本都是单文件上传教程,这篇文章主要讲retrofit的多文件上传实现。

个人觉得有必要深入理解http协议,这样无论使用哪个网络框架,碰到类似这样上传的问题,一眼就能知道问题出在哪里。因此就有必要了解http协议的上传机制。

一、了解 multipart/form-data

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

post格式如下:

--${bound}
Content-Disposition: form-data; name="Filename"HTTP.pdf
--${bound}
Content-Disposition: form-data; name="file000"; filename="HTTP协议详解.pdf"
Content-Type: application/octet-stream%PDF-1.5
file content
%%EOF--${bound}
Content-Disposition: form-data; name="Upload"Submit Query
--${bound}--

${bound}是Content-Type里boundary的值

二、Retrofit2 对multipart/form-data的封装

Retrofit其实是个网络代理框架,负责封装请求,然后把请求分发给http协议具体实现者-httpclient。retrofit默认的httpclient是okhttp。

既然Retrofit不实现http,为啥还用它呢。因为他方便!!
Retrofit会根据注解封装网络请求,待httpclient请求完成后,把原始response内容通过转化器(converter)转化成我们需要的对象(object)。

具体怎么使用 retrofit2,请参考: GitHub和Retrofit官网

那么Retrofit和okhttp怎么封装这些multipart/form-data上传数据呢

在retrofit中:

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

在okhttp3中:

  • okhttp3.MultipartBody: multipart/form-data的抽象封装,继承okhttp3.RequestBody
  • okhttp3.MultipartBody.Part: multipart/form-data里的一项数据。

三、Service接口定义

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

{
data: "上传了3个文件",
msg: "访问成功",
code: 200
}

因此需要对返回数据封装成一个对象,考虑到复用性,封装成泛型最佳:

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

接着定义一个上传的网络请求Service:

public interface FileuploadService {/*** 通过 List<MultipartBody.Part> 传入多个part实现多文件上传* @param parts 每个part代表一个* @return 状态信息*/@Multipart@POST("users/image")Call<BaseResponse<String>> uploadFilesWithParts(@Part() List<MultipartBody.Part> parts);/*** 通过 MultipartBody和@body作为参数来上传* @param multipartBody MultipartBody包含多个Part* @return 状态信息*/@POST("users/image")Call<BaseResponse<String>> uploadFileWithRequestBody(@Body MultipartBody multipartBody);
}

由上可知,有两种方式实现上传

  • 使用@Multipart注解方法,并用@Part注解方法参数,类型是List< okhttp3.MultipartBody.Part>
  • 不使用@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。

四、上传多个文件

写好service接口后,来看看怎么构造MultipartBody
可以写一个方法,用于把File对象转化成MultipartBody:

public static MultipartBody filesToMultipartBody(List<File> files) {MultipartBody.Builder builder = new MultipartBody.Builder();for (File file : files) {// TODO: 16-4-2  这里为了简单起见,没有判断file的类型 RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);builder.addFormDataPart("file", file.getName(), requestBody);}builder.setType(MultipartBody.FORM);MultipartBody multipartBody = builder.build();return multipartBody;}

或者把File转化成MultipartBody.Part:

  public static List<MultipartBody.Part> filesToMultipartBodyParts(List<File> files) {List<MultipartBody.Part> parts = new ArrayList<>(files.size());for (File file : files) {// TODO: 16-4-2  这里为了简单起见,没有判断file的类型RequestBody requestBody = RequestBody.create(MediaType.parse("image/png"), file);MultipartBody.Part part = MultipartBody.Part.createFormData("file", file.getName(), requestBody);parts.add(part);}return parts;}

最后就剩下调用了

为了复用,因此把构建Retrofit简单封装成一个builder类:

public class RetrofitBuilder {private static Retrofit retrofit;public synchronized static Retrofit buildRetrofit() {if (retrofit == null) {HttpLoggingInterceptor logging = new HttpLoggingInterceptor();Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create(gson);logging.setLevel(HttpLoggingInterceptor.Level.BODY);OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logging).retryOnConnectionFailure(true).build();retrofit = new Retrofit.Builder().client(client).baseUrl(Config.NetURL.BASE_URL).addConverterFactory(gsonConverterFactory).addCallAdapterFactory(RxJavaCallAdapterFactory.create()).build();}return retrofit;}
}

接着可以在activity里调用FileUploadService的接口了:

  private void uploadFile() {MultipartBody body = MultipartBuilder.filesToMultipartBody(mFileList);RetrofitBuilder.buildRetrofit().create(FileUploadService.class).uploadFileWithRequestBody(body).enqueue(new Callback<BaseResponse<String>>() {@Overridepublic void onResponse(Call<BaseResponse<String>> call, Response<BaseResponse<String>> response) {if (response.isSuccessful()) {BaseResponse<String> body = response.body();Toast.makeText(LoginActivity.this, "上传成功:"+response.body().getMsg(), Toast.LENGTH_SHORT).show();} else {Log.d(TAG,"上传失败");Toast.makeText(RegisterActivity.this, "上传失败", Toast.LENGTH_SHORT).show();}}@Overridepublic void onFailure(Call<BaseResponse<String>> call, Throwable t) {Toast.makeText(RegisterActivity.this, "网络连接失败", Toast.LENGTH_SHORT).show();}});}

参考资料:http://my.oschina.net/cnlw/blog/168466

Retrofit2 multpart多文件上传详解相关推荐

  1. Multipart/form-data POST文件上传详解

    Multipart/form-data POST文件上传详解 理论 简单的HTTP POST 大家通过HTTP向服务器发送POST请求提交数据,都是通过form表单提交的,代码如下: <form ...

  2. php文件上传详解,PHP文件上传实例详解!!!

    这篇文章主要介绍了PHP文件上传实例代码,需要的朋友可以参考下 首先来看下上传部分的表单代码: 文件:  这里有几个要注意的地方,首先看这句 ,这里我们采用POST方法,个别浏览器还支持PUT方法,当 ...

  3. Struts2之struts2文件上传详解

    一.学习案例:通过在uploadfile.jsp页面填写完表单,提交后跳转到success.jsp页面,然后验证upload包下上传文件是否成功. 二.案例分析:struts2文件上传并不是表面上看的 ...

  4. JAVA文件上传详解(附源码)

    文章目录 JAVA文件上传详解(附源码) 1.准备工作 2.使用类介绍 FileItem类 ServletFileUpload类 3.代码编写 JAVA文件上传详解(附源码) 在web应用中,文件上传 ...

  5. JavaScript 文件上传详解

    本文为 Qunar 技术沙龙投稿,版权归原作者所有,未经允许,请勿转载. 原文地址:http://mp.weixin.qq.com/s/KWFyJa06CNXrU8zhSzzQFQ 作者:梁志,201 ...

  6. python爬虫:Multipart/form-data POST文件上传详解

    简单的HTTP POST 大家通过HTTP向服务器发送POST请求提交数据,都是通过form表单提交的,代码如下: <form method="post"action=&qu ...

  7. elment-ui文件上传详解

    文件上传总有各种问题,算是给自己一个总结吧 HTML <el-form-item prop="" class="form-item"><tem ...

  8. channelsftp的put_JSch - Java实现的SFTP(文件上传详解篇)

    public void put(String src, String dst) 将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同. 采用 ...

  9. java web文件上传详解_java web图片上传和文件上传实例详解

    java web图片上传和文件上传 图片上传和文件上传本质上是一样的,图片本身也是文件.文件上传就是将图片上传到服务器,方式虽然有很多,但底层的实现都是文件的读写操作. 注意事项 1.form表单一定 ...

最新文章

  1. 用 Qt 给嵌入式Linux加个桌面
  2. 斯坦福马超:随机梯度下降,也爱躺「平」……why?
  3. 电机贴上锡纸到底有没有作用?
  4. PS 图像尺寸|点阵格式图像|矢量格式图像|图像格式的选择
  5. java应用之solr入门篇
  6. 【CyberSecurityLearning 52】Web架构安全分析(web工作机制、HTTP协议)
  7. phpcms避免字段值重复
  8. 我不愿意直接表达情绪:答上海《新闻晨报》记者顾筝小姐问
  9. 算法设计与分析——递归与分治策略——棋盘覆盖
  10. php 存储html 内容,HTML 本地存储
  11. 中查出所有姓张的学生为啥查不出来_只有笔试成绩没有面试成绩是什么原因 教师资格面试成绩怎么查...
  12. 40个视差滚动效果网站的新技术应用
  13. PeopleSoft Rich Text Boxes上定制Tool Bars
  14. 计算机五大类型,操作系统目前有五大类型
  15. 建模实训报告总结_建模实习工作总结
  16. 7-6 愿天下有情人都是失散多年的兄妹 (25分)
  17. 华为担纲建设基础软硬件国家新一代AI开放创新平台
  18. vim跳转到函数定义处
  19. Android存储访问框架的使用
  20. 分享几个快速加微信粉丝的方法

热门文章

  1. office365加速解决方案
  2. CentOS系统设置自动登录
  3. CS231n 学习笔记(2)——神经网络 part2 :Softmax classifier
  4. java读取Oracle中大字段数据(CLOB)的方法
  5. Vue 使用 prerender-spa-plugin 添加loading
  6. 计蒜客NOIP模拟赛(2) D2T2紫色百合
  7. v8-su-root
  8. hdu1848(sg函数打表)
  9. [文件系统]文件系统学习笔记(十)---杂项
  10. WordPress SEO ☞ WordPress网站终极优化指南