Android Retrofit 实现(图文上传)文字(参数)和多张图片一起上传

使用Retrofit进行文件上传,肯定离不开Part & PartMap。

  1. public interface FileUploadService {
  2. @Multipart
  3. @POST("upload")
  4. Call<ResponseBody> upload(@Part("description") RequestBody description,
  5. @Part MultipartBody.Part file);
  6. }

上面是定义的接口方法,需要注意的是,这个方法不再有 @FormUrlEncoded 这个注解,而换成了 @Multipart,后面只需要在参数中增加 Part 就可以了。也许你会问,这里的 Part 和 Field 究竟有什么区别,其实从功能上讲,无非就是客户端向服务端发起请求携带参数的方式不同,并且前者可以携带的参数类型更加丰富,包括数据流。也正是因为这一点,我们可以通过这种方式来上传文件,下面我们就给出这个接口的使用方法:

  1. //先创建 service
  2. FileUploadService service = retrofit.create(FileUploadService.class);
  3.  
  4. //构建要上传的文件
  5. File file = new File(filename);
  6. RequestBody requestFile =
  7. RequestBody.create(MediaType.parse("application/otcet-stream"), file);
  8.  
  9. MultipartBody.Part body =
  10. MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);
  11.  
  12. String descriptionString = "This is a description";
  13. RequestBody description =
  14. RequestBody.create(
  15. MediaType.parse("multipart/form-data"), descriptionString);
  16.  
  17. Call<ResponseBody> call = service.upload(description, body);
  18. call.enqueue(new Callback<ResponseBody>() {
  19. @Override
  20. public void onResponse(Call<ResponseBody> call,
  21. Response<ResponseBody> response) {
  22. System.out.println("success");
  23. }
  24.  
  25. @Override
  26. public void onFailure(Call<ResponseBody> call, Throwable t) {
  27. t.printStackTrace();
  28. }
  29. });

在实验时,我上传了一个只包含一行文字的文件: 
Visit me: http://www.println.net

那么我们去服务端看下我们的请求是什么样的: 
HEADERS

  1. Accept-Encoding: gzip
  2. Content-Length:
  3. Content-Type: multipart/form-data; boundary=9b670d44-63dc-4a8a-833d-66e45e0156ca
  4. User-Agent: okhttp/3.2.
  5. X-Request-Id: 9d70e8cc-958b-4f42-b979-4c1fcd474352
  6. Via: 1.1 vegur
  7. Host: requestb.in
  8. Total-Route-Time:
  9. Connection: close
  10. Connect-Time:

FORM/POST PARAMETERS

  1. description: This is a description

RAW BODY

  1. 9b670d44-63dc-4a8a-833d-66e45e0156ca
  2. Content-Disposition: form-data; name=”description
  3. Content-Transfer-Encoding: binary
  4. Content-Type: multipart/form-data; charset=utf-
  5. Content-Length:
  6.  
  7. This is a description
  8. 9b670d44-63dc-4a8a-833d-66e45e0156ca
  9. Content-Disposition: form-data; name=”aFile”; filename=”uploadedfile.txt
  10. Content-Type: application/otcet-stream
  11. Content-Length:
  12.  
  13. Visit me: http://www.println.net
  14. 9b670d44-63dc-4a8a-833d-66e45e0156ca

我们看到,我们上传的文件的内容出现在请求当中了。如果需要上传多个文件,就声明多个 Part 参数,或者试试 PartMap。

使用RequestBodyConverter简化请求

上面为大家展示了如何用 Retrofit 上传文件,这个上传的过程还是有那么点儿不够简练,我们只是要提供一个文件用于上传,可我们前后构造了三个对象:

天哪,肯定是哪里出了问题。实际上,Retrofit 允许我们自己定义入参和返回的类型,不过,如果这些类型比较特别,我们还需要准备相应的 Converter,也正是因为 Converter 的存在, Retrofit 在入参和返回类型上表现得非常灵活。 
下面我们把刚才的 Service 代码稍作修改:

  1. public interface FileUploadService {
  2. @Multipart
  3. @POST("upload")
  4. Call<ResponseBody> upload(@Part("description") RequestBody description,
  5. //注意这里的参数 "aFile" 之前是在创建 MultipartBody.Part 的时候传入的
  6. @Part("aFile") File file);
  7. }

现在我们把入参类型改成了我们熟悉的 File,如果你就这么拿去发请求,服务端收到的结果会让你哭了的。。。 
RAW BODY

  1. 7d24e78e--4ed4-9db4-57d799b6efb7
  2. Content-Disposition: form-data; name=”description
  3. Content-Transfer-Encoding: binary
  4. Content-Type: multipart/form-data; charset=utf-
  5. Content-Length:
  6.  
  7. This is a description
  8. 7d24e78e--4ed4-9db4-57d799b6efb7
  9. Content-Disposition: form-data; name=”aFile
  10. Content-Transfer-Encoding: binary
  11. Content-Type: application/json; charset=UTF-
  12. Content-Length:
  13.  
  14. // 注意这里!!之前是文件的内容,现在变成了文件的路径
  15. {“path”:”samples/uploadedfile.txt”}
  16. 7d24e78e--4ed4-9db4-57d799b6efb7

文件内容成功上传了,当然其中还存在一些问题,这个目前直接使用 Retrofit 的 Converter 还做不到,原因主要在于我们没有办法通过 Converter 直接将 File 转换为 MultiPartBody.Part,如果想要做到这一点,我们可以对 Retrofit 的源码稍作修改,这个我们下面再谈。

继续简化文件上传的接口

上面我们曾试图简化文件上传接口的使用,尽管我们已经给出了相应的 File -> RequestBody 的 Converter,不过基于 Retrofit本身的限制,我们还是不能像直接构造 MultiPartBody.Part 那样来获得更多的灵活性。这时候该怎么办?当然是 Hack~~ 
首先明确我们的需求:

  • 文件的 Content-Type 需要更多的灵活性,不应该写死在 Converter 当中,可以的话,最好可以根据文件的扩展名来映射出来对应的 Content-Type, 比如 image.png -> image/png;
  • 在请求的数据中,能够正常携带 filename 这个字段。

为此,我增加了一套完整的参数解析方案:

    • 增加任意类型转换的 Converter,这一步主要是满足后续我们直接将入参类型转换为 MultiPartBody.Part 类型:
  1. public interface Converter<F, T> {
  2. ...
  3.  
  4. abstract class Factory {
  5. ...
  6. //返回一个满足条件的不限制类型的 Converter
  7. public Converter<?, ?> arbitraryConverter(Type originalType,
  8. Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){
  9. return null;
  10. }
  11. }
  12. }

需要注意的是,Retrofit 类当中也需要增加相应的方法:

  1. public <F, T> Converter<F, T> arbitraryConverter(Type orignalType,
  2. Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
  3. return nextArbitraryConverter(null, orignalType, convertedType, parameterAnnotations, methodAnnotations);
  4. }
  5.  
  6. public <F, T> Converter<F, T> nextArbitraryConverter(Converter.Factory skipPast,
  7. Type type, Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {
  8. checkNotNull(type, "type == null");
  9. checkNotNull(parameterAnnotations, "parameterAnnotations == null");
  10. checkNotNull(methodAnnotations, "methodAnnotations == null");
  11.  
  12. int start = converterFactories.indexOf(skipPast) + ;
  13. for (int i = start, count = converterFactories.size(); i < count; i++) {
  14. Converter.Factory factory = converterFactories.get(i);
  15. Converter<?, ?> converter =
  16. factory.arbitraryConverter(type, convertedType, parameterAnnotations, methodAnnotations, this);
  17. if (converter != null) {
  18. //noinspection unchecked
  19. return (Converter<F, T>) converter;
  20. }
  21. }
  22. return null;
  23. }
  • 再给出 arbitraryConverter 的具体实现:
  1. public class TypedFileMultiPartBodyConverterFactory extends Converter.Factory {
  2. @Override
  3. public Converter<TypedFile, MultipartBody.Part> arbitraryConverter(Type originalType, Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
  4. if (originalType == TypedFile.class && convertedType == MultipartBody.Part.class) {
  5. return new FileRequestBodyConverter();
  6. }
  7. return null;
  8. }
  9. }
  1. public class TypedFileMultiPartBodyConverter implements Converter<TypedFile, MultipartBody.Part> {
  2.  
  3. @Override
  4. public MultipartBody.Part convert(TypedFile typedFile) throws IOException {
  5. RequestBody requestFile =
  6. RequestBody.create(typedFile.getMediaType(), typedFile.getFile());
  7. return MultipartBody.Part.createFormData(typedFile.getName(), typedFile.getFile().getName(), requestFile);
  8. }
  9. }
  1. public class TypedFile {
  2. private MediaType mediaType;
  3. private String name;
  4. private File file;
  5.  
  6. public TypedFile(String name, String filepath){
  7. this(name, new File(filepath));
  8. }
  9.  
  10. public TypedFile(String name, File file) {
  11. this(MediaType.parse(MediaTypes.getMediaType(file)), name, file);
  12. }
  13.  
  14. public TypedFile(MediaType mediaType, String name, String filepath) {
  15. this(mediaType, name, new File(filepath));
  16. }
  17.  
  18. public TypedFile(MediaType mediaType, String name, File file) {
  19. this.mediaType = mediaType;
  20. this.name = name;
  21. this.file = file;
  22. }
  23.  
  24. public String getName() {
  25. return name;
  26. }
  27.  
  28. public MediaType getMediaType() {
  29. return mediaType;
  30. }
  31.  
  32. public File getFile() {
  33. return file;
  34. }
  35. }
  • 在声明接口时,@Part 不要传入参数,这样 Retrofit 在 ServiceMethod.Builder.parseParameterAnnotation 方法中解析 Part时,就会认为我们传入的参数为 MultiPartBody.Part 类型(实际上我们将在后面自己转换)。那么解析的时候,我们拿到前面定义好的 Converter,构造一个 ParameterHandler:
  1. ...
  2. } else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {
  3. return ParameterHandler.RawPart.INSTANCE;
  4. } else {
  5. Converter<?, ?> converter =
  6. retrofit.arbitraryConverter(type, MultipartBody.Part.class, annotations, methodAnnotations);
  7. if(converter == null) {
  8. throw parameterError(p,
  9. "@Part annotation must supply a name or use MultipartBody.Part parameter type.");
  10. }
  11. return new ParameterHandler.TypedFileHandler((Converter<TypedFile, MultipartBody.Part>) converter);
  12. }
  13. ...
  1. static final class TypedFileHandler extends ParameterHandler<TypedFile>{
  2.  
  3. private final Converter<TypedFile, MultipartBody.Part> converter;
  4.  
  5. TypedFileHandler(Converter<TypedFile, MultipartBody.Part> converter) {
  6. this.converter = converter;
  7. }
  8.  
  9. @Override
  10. void apply(RequestBuilder builder, TypedFile value) throws IOException {
  11. if(value != null){
  12. builder.addPart(converter.convert(value));
  13. }
  14. }
  15. }
  • 这时候再看我们的接口声明:
  1. public interface FileUploadService {
  2. @Multipart
  3. @POST("upload")
  4. Call<ResponseBody> upload(@Part("description") RequestBody description,
  5. @Part TypedFile typedFile);
  6. }

使用方法:

  1. etrofit retrofit = new Retrofit.Builder()
  2. .baseUrl("http://www.println.net/")
  3. .addConverterFactory(new TypedFileMultiPartBodyConverterFactory())
  4. .addConverterFactory(GsonConverterFactory.create())
  5. .build();
  6.  
  7. FileUploadService service = retrofit.create(FileUploadService.class);
  8. TypedFile typedFile = new TypedFile("aFile", filename);
  9. String descriptionString = "This is a description";
  10. RequestBody description =
  11. RequestBody.create(
  12. MediaType.parse("multipart/form-data"), descriptionString);
  13.  
  14. Call<ResponseBody> call = service.upload(description, typedFile);
  15. call.enqueue(...);

至此,我们已经通过自己的双手,让 Retrofit 点亮了自定义上传文件的技能,风骚等级更上一层楼!

摘自:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=1117

http://www.chenkaihua.com/2016/04/02/retrofit2-upload-multipart-files.html?utm_source=tuicool&utm_medium=referral

http://www.jianshu.com/users/fc232a6c7db3/latest_articles

http://blog.csdn.net/u010945031/article/details/49475897

Android Retrofit 2.0文件上传的更多相关文章

  1. Servlet3.0文件上传

    Servelt3.0文件上传作为一种便捷的文件上传方式很是值得我们去应用的 1.Servlet3.0文件上传使用步骤 浏览器端的要求 表单的提交方法必须是post 必须有一个文件上传组件 <in ...

  2. PHPcms v9.6.0 文件上传漏洞

    title: PHPcms v9.6.0 文件上传漏洞 date: 2021-4-5 tags: 渗透测试,CVE漏洞复现,文件上传 categories: 渗透测试 CVE漏洞复现 文件上传 PHP ...

  3. Android开发之httpclient文件上传实现

    文件上传可能是一个比較耗时的操作,假设为上传操作带上进度提示则能够更好的提高用户体验,最后效果例如以下图: 项目源代码:http://download.csdn.net/detail/shinay/4 ...

  4. 【代码审计】UKCMS_v1.1.0 文件上传漏洞分析

      0x00 环境准备 ukcms官网:https://www.ukcms.com/ 程序源码下载:http://down.ukcms.com/down.php?v=1.1.0 测试网站首页: 0x0 ...

  5. java servlet 3.0文件上传

    在以前,处理文件上传是一个很痛苦的事情,大都借助于开源的上传组件,诸如commons fileupload等.现在好了,很方便,便捷到比那些组件都方便至极.以前的HTML端上传表单不用改变什么,还是一 ...

  6. servlet3.0文件上传与下载

    描述:文件上传与下载是在JavaEE中常见的功能,实现文件上传与下载的方式有多种,其中文件上传的方式有: (1)commons-fileupload: (2)Servlet 3.0 实现文件上传 (3 ...

  7. NetCore3.0 文件上传与大文件上传的限制

    NetCore文件上传两种方式 NetCore官方给出的两种文件上传方式分别为“缓冲”.“流式”.我简单的说说两种的区别, 1.缓冲:通过模型绑定先把整个文件保存到内存,然后我们通过IFormFile ...

  8. Android采取async框架文件上传

    页面效果 须要的权限 <uses-permission android:name="android.permission.INTERNET"/> 网络訪问权限; 布局文 ...

  9. yii2.0 文件上传

    Yii 2.0 出来好长时间了,一直都是看下官方网站,没实践过,今天弄了下图片上传操作. 1创建一个简单的数据表 mysql> desc article; +---------+-------- ...

随机推荐

  1. Perl Connect to Database without password as sysdba

    #!/oracle/product/11g/db/perl/bin/perl use lib '/oracle/product/11g/db/perl/lib/site_perl/5.10.0'; u ...

  2. 1.1 Java程序设计平台

    Java并不只是一种语言.在此之前出现的那么多中语言也没有能够引起那么大的轰动.Java是一个完整的平台,有一个庞大的库,其中包含了很多可重用的代码和一个提供诸如安全性.跨操作系统的可移植性以及自动垃 ...

  3. Spring MVC 单元测试Demo

    @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(locations={" ...

  4. scrapy——3 crawlSpider——爱问

    scrapy——3  crawlSpider crawlSpider 爬取一般网站常用的爬虫类.其定义了一些规则(rule)来提供跟进link的方便的机制. 也许该spider并不是完全适合您的特定网 ...

  5. 【[Offer收割]编程练习赛14 D】剑刃风暴(半径为R的圆能够覆盖的平面上最多点数目模板)

    [题目链接]:http://hihocoder.com/problemset/problem/1508 [题意] [题解] 求一个半径为R的圆能够覆盖的平面上的n个点中最多的点数; O(N2log2N ...

  6. PatentTips - DMA address translation between peer-to-peer IO devices

    BACKGROUND As processing resources have increased, demands to run multiple software programs and ope ...

  7. code vs 3376 符号三角形

    3376 符号三角形  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description 如下图是由14个“+”和14个“-”组 ...

  8. [转]十五天精通WCF——第一天 三种Binding让你KO80%的业务

    转眼wcf技术已经出现很多年了,也在.net界混的风生水起,同时.net也是一个高度封装的框架,作为在wcf食物链最顶端的我们所能做的任务已经简单的不能再简单了, 再简单的话马路上的大妈也能写wcf了 ...

  9. &lt;LeetCode OJ&gt; 268. Missing Number

    268. Missing Number Total Accepted: 31740 Total Submissions: 83547 Difficulty: Medium Given an array ...

  10. LeetCode之LCP(Longest Common Prefix)问题

    这个也是简单题目.可是关键在于题意的理解. 题目原文就一句话:Write a function to find the longest common prefix string amongst an ...