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

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

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

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

//先创建 service
FileUploadService service = retrofit.create(FileUploadService.class);//构建要上传的文件
File file = new File(filename);
RequestBody requestFile =RequestBody.create(MediaType.parse("application/otcet-stream"), file);MultipartBody.Part body =MultipartBody.Part.createFormData("aFile", file.getName(), requestFile);String descriptionString = "This is a description";
RequestBody description =RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);Call<ResponseBody> call = service.upload(description, body);
call.enqueue(new Callback<ResponseBody>() {@Overridepublic void onResponse(Call<ResponseBody> call,Response<ResponseBody> response) {System.out.println("success");}@Overridepublic void onFailure(Call<ResponseBody> call, Throwable t) {t.printStackTrace();}
});

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

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

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

FORM/POST PARAMETERS

description: This is a description

RAW BODY

–9b670d44-63dc-4a8a-833d-66e45e0156ca
Content-Disposition: form-data; name=”description”
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 21This is a description
–9b670d44-63dc-4a8a-833d-66e45e0156ca
Content-Disposition: form-data; name=”aFile”; filename=”uploadedfile.txt”
Content-Type: application/otcet-stream
Content-Length: 32Visit me: http://www.println.net
–9b670d44-63dc-4a8a-833d-66e45e0156ca–

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

使用RequestBodyConverter简化请求

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

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

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

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

–7d24e78e-4354-4ed4-9db4-57d799b6efb7
Content-Disposition: form-data; name=”description”
Content-Transfer-Encoding: binary
Content-Type: multipart/form-data; charset=utf-8
Content-Length: 21This is a description
–7d24e78e-4354-4ed4-9db4-57d799b6efb7
Content-Disposition: form-data; name=”aFile”
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 35// 注意这里!!之前是文件的内容,现在变成了文件的路径
{“path”:”samples/uploadedfile.txt”}
–7d24e78e-4354-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 类型:
public interface Converter<F, T> {...abstract class Factory {...//返回一个满足条件的不限制类型的 Converterpublic Converter<?, ?> arbitraryConverter(Type originalType,Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){return null;}}
}

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

public <F, T> Converter<F, T> arbitraryConverter(Type orignalType,Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {return nextArbitraryConverter(null, orignalType, convertedType, parameterAnnotations, methodAnnotations);
}public <F, T> Converter<F, T> nextArbitraryConverter(Converter.Factory skipPast,Type type, Type convertedType,  Annotation[] parameterAnnotations, Annotation[] methodAnnotations) {checkNotNull(type, "type == null");checkNotNull(parameterAnnotations, "parameterAnnotations == null");checkNotNull(methodAnnotations, "methodAnnotations == null");int start = converterFactories.indexOf(skipPast) + 1;for (int i = start, count = converterFactories.size(); i < count; i++) {Converter.Factory factory = converterFactories.get(i);Converter<?, ?> converter =factory.arbitraryConverter(type, convertedType, parameterAnnotations, methodAnnotations, this);if (converter != null) {//noinspection uncheckedreturn (Converter<F, T>) converter;}}return null;
}

  • 再给出 arbitraryConverter 的具体实现:
public class TypedFileMultiPartBodyConverterFactory extends Converter.Factory {@Overridepublic Converter<TypedFile, MultipartBody.Part> arbitraryConverter(Type originalType, Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {if (originalType == TypedFile.class && convertedType == MultipartBody.Part.class) {return new FileRequestBodyConverter();}return null;}
}

public class TypedFileMultiPartBodyConverter implements Converter<TypedFile, MultipartBody.Part> {@Overridepublic MultipartBody.Part convert(TypedFile typedFile) throws IOException {RequestBody requestFile =RequestBody.create(typedFile.getMediaType(), typedFile.getFile());return MultipartBody.Part.createFormData(typedFile.getName(), typedFile.getFile().getName(), requestFile);}
}

public class TypedFile {private MediaType mediaType;private String name;private File file;public TypedFile(String name, String filepath){this(name, new File(filepath));}public TypedFile(String name, File file) {this(MediaType.parse(MediaTypes.getMediaType(file)), name, file);}public TypedFile(MediaType mediaType, String name, String filepath) {this(mediaType, name, new File(filepath));}public TypedFile(MediaType mediaType, String name, File file) {this.mediaType = mediaType;this.name = name;this.file = file;}public String getName() {return name;}public MediaType getMediaType() {return mediaType;}public File getFile() {return file;}
}

  • 在声明接口时,@Part 不要传入参数,这样 Retrofit 在 ServiceMethod.Builder.parseParameterAnnotation 方法中解析 Part时,就会认为我们传入的参数为 MultiPartBody.Part 类型(实际上我们将在后面自己转换)。那么解析的时候,我们拿到前面定义好的 Converter,构造一个 ParameterHandler:
...
} else if (MultipartBody.Part.class.isAssignableFrom(rawParameterType)) {return ParameterHandler.RawPart.INSTANCE;
} else {Converter<?, ?> converter =retrofit.arbitraryConverter(type, MultipartBody.Part.class, annotations, methodAnnotations);if(converter == null) {throw parameterError(p,"@Part annotation must supply a name or use MultipartBody.Part parameter type.");}return new ParameterHandler.TypedFileHandler((Converter<TypedFile, MultipartBody.Part>) converter);
}
...

static final class TypedFileHandler extends ParameterHandler<TypedFile>{private final Converter<TypedFile, MultipartBody.Part> converter;TypedFileHandler(Converter<TypedFile, MultipartBody.Part> converter) {this.converter = converter;}@Overridevoid apply(RequestBuilder builder, TypedFile value) throws IOException {if(value != null){builder.addPart(converter.convert(value));}}
}

  • 这时候再看我们的接口声明:
public interface FileUploadService {@Multipart@POST("upload")Call<ResponseBody> upload(@Part("description") RequestBody description,@Part TypedFile typedFile);
}

使用方法:

etrofit retrofit = new Retrofit.Builder().baseUrl("http://www.println.net/").addConverterFactory(new TypedFileMultiPartBodyConverterFactory()).addConverterFactory(GsonConverterFactory.create()).build();FileUploadService service = retrofit.create(FileUploadService.class);
TypedFile typedFile = new TypedFile("aFile", filename);
String descriptionString = "This is a description";
RequestBody description =RequestBody.create(MediaType.parse("multipart/form-data"), descriptionString);Call<ResponseBody> call = service.upload(description, typedFile);
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

转载于:https://www.cnblogs.com/zhujiabin/p/7601658.html

Android Retrofit 2.0文件上传相关推荐

  1. android webview 多文件上传,Android网页WebView图片文件上传的问题

    Android网页WebView图片文件上传的问题 发布时间:2020-07-13 22:48:15 来源:51CTO 阅读:5922 作者:拾荒者老大 在安卓下,webview上传图片点击是没用的, ...

  2. extjs2.0 文件上传_extjs数据存储与传输

    本章内容 qExt.data简介 qExt.data.Connection qExt.data.Record qExt.data.Store q常用proxy q常用reader q高级store q ...

  3. ASP.NET2.0文件上传以及图片处理总结篇 [转]

    1.最简单的单文件上传(没花头) 2.多文件上传 3.客户端检查上传文件类型(以上传图片为例) 4.服务器端检查上传文件类型(以上传图片为例) 5.服务器端检查上传文件类型(可以检测真正文件名) 6. ...

  4. netcore 图片 文件大小_NetCore 3.0文件上传和大文件上传的限制详解

    NetCore文件上传两种方式 NetCore官方给出的两种文件上传方式分别为"缓冲"."流式".我简单的说说两种的区别, 1.缓冲:通过模型绑定先把整个文件保 ...

  5. android小记之FTP文件上传

    android客户端实现FTP文件(包括图片)上传应该没什么难度.写下来就了为了记录一下,望能帮到新手. 需要用到 commons-net-3.0.1.jar,后面附上jar包. 直接上代码: /** ...

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

    背景 在有心课堂<自己动手写HTTP框架>课程中有下列课程: 自拍要发朋友圈如何实现 http://stay4it.com/course/4/learn#lesson/208 通过自己写的 ...

  7. Android局域网实现FTP文件上传下载客户端与服务端

    文章目录 前言 一.FTP是什么? 二.使用步骤 1 服务端 1.1 服务端的代码实现 2 客户端 2.1 客户端的代码实现 附件 前言 最近在公司的项目中,使用到了 局域网通信,不同的设备直接传递消 ...

  8. vueCli3.0文件上传后,后端返回流,前端转换成pdf预览的3种方式

    第一种是用: <iframe> 嵌套显示 先上效果图: 注:这种还带样式的是我后面修改源码的 js添加的 :   customToolbar.js //创建一个新样式 let sheet ...

  9. laravel oauth2.0 文件上传报错

    报错信息:   "message": "Invalid stream or file provided for UploadedFile",     " ...

最新文章

  1. 第一个python程序:定义,列表,元组,集合,求并集交集,键和值,运算符,缩进
  2. 常见的shell实例
  3. Eclipse 的一些调试技巧
  4. 量子力学 一 基础6 厄尔米特算符的相容性
  5. android弹出选择usb,android 解决:默认情况下用于该USB设备 自动获取USB操作权限...
  6. 腾讯产品课观后感之定位2018-08-22
  7. php当前完整url的写法
  8. 可定制的PHP缩略图生成程式(需要GD库支持)
  9. Java编程基础08——面向对象_构造方法静态static
  10. 自学python-python自学难吗
  11. boost asio异步通信
  12. 在MacOS系统中如何自定义屏幕保护程序?
  13. Windows安装office出现1046错误
  14. 部署 3ds MAX 2022 插件开发环境(3ds MAX plug-in development)
  15. 二叉树的前中后序遍历
  16. UVa OJ 10361 自动作诗机
  17. Android有效解决加载大图片内存溢出问题及优化虚拟机内存
  18. java muti实现图片上传_MutiFileUpload.java 多文件上传
  19. 微信小程序使用swiper制作轮播图留白的解决方法
  20. Flask学习笔记(一)

热门文章

  1. 微软推出免费虚拟太空望远镜软件WWT
  2. 【重识 HTML + CSS】背景相关知识点
  3. 【MyBatis笔记】0-MyBatis简介
  4. Kali渗透测试工具库(二)beef--Web浏览器攻击框架
  5. 读书笔记_打开量化投资的黑箱04
  6. map函数的用法python,详解Python map函数及Python map()函数的用法
  7. OkHttp RouteSelector代理解析
  8. php替换局部大小写字母,php替换字符串中的一些字符(区分大小写)的函数str_replace()...
  9. 如何用Linux搭建家庭云服务,使用ownCloud在Linux安装你的个人云服务 私有云的搭建...
  10. 购买域名以及申请证书