Android Retrofit 2.0文件上传
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>() {
- @Override
- public void onResponse(Call<ResponseBody> call,
- Response<ResponseBody> response) {
- System.out.println("success");
- }
- @Override
- public void onFailure(Call<ResponseBody> call, Throwable t) {
- t.printStackTrace();
- }
- });
在实验时,我上传了一个只包含一行文字的文件:
Visit me: http://www.println.net
那么我们去服务端看下我们的请求是什么样的:
HEADERS
- Accept-Encoding: gzip
- Content-Length:
- Content-Type: multipart/form-data; boundary=9b670d44-63dc-4a8a-833d-66e45e0156ca
- User-Agent: okhttp/3.2.
- X-Request-Id: 9d70e8cc-958b-4f42-b979-4c1fcd474352
- Via: 1.1 vegur
- Host: requestb.in
- Total-Route-Time:
- Connection: close
- Connect-Time:
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-
- Content-Length:
- This is a description
- –9b670d44-63dc-4a8a-833d-66e45e0156ca
- Content-Disposition: form-data; name=”aFile”; filename=”uploadedfile.txt”
- Content-Type: application/otcet-stream
- Content-Length:
- Visit 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--4ed4-9db4-57d799b6efb7
- Content-Disposition: form-data; name=”description”
- Content-Transfer-Encoding: binary
- Content-Type: multipart/form-data; charset=utf-
- Content-Length:
- This is a description
- –7d24e78e--4ed4-9db4-57d799b6efb7
- Content-Disposition: form-data; name=”aFile”
- Content-Transfer-Encoding: binary
- Content-Type: application/json; charset=UTF-
- Content-Length:
- // 注意这里!!之前是文件的内容,现在变成了文件的路径
- {“path”:”samples/uploadedfile.txt”}
- –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 类型:
- public interface Converter<F, T> {
- ...
- abstract class Factory {
- ...
- //返回一个满足条件的不限制类型的 Converter
- public 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) + ;
- 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 unchecked
- return (Converter<F, T>) converter;
- }
- }
- return null;
- }
- 再给出 arbitraryConverter 的具体实现:
- public class TypedFileMultiPartBodyConverterFactory extends Converter.Factory {
- @Override
- public 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> {
- @Override
- public 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;
- }
- @Override
- void 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.jianshu.com/users/fc232a6c7db3/latest_articles
http://blog.csdn.net/u010945031/article/details/49475897
Android Retrofit 2.0文件上传的更多相关文章
- Servlet3.0文件上传
Servelt3.0文件上传作为一种便捷的文件上传方式很是值得我们去应用的 1.Servlet3.0文件上传使用步骤 浏览器端的要求 表单的提交方法必须是post 必须有一个文件上传组件 <in ...
- PHPcms v9.6.0 文件上传漏洞
title: PHPcms v9.6.0 文件上传漏洞 date: 2021-4-5 tags: 渗透测试,CVE漏洞复现,文件上传 categories: 渗透测试 CVE漏洞复现 文件上传 PHP ...
- Android开发之httpclient文件上传实现
文件上传可能是一个比較耗时的操作,假设为上传操作带上进度提示则能够更好的提高用户体验,最后效果例如以下图: 项目源代码:http://download.csdn.net/detail/shinay/4 ...
- 【代码审计】UKCMS_v1.1.0 文件上传漏洞分析
0x00 环境准备 ukcms官网:https://www.ukcms.com/ 程序源码下载:http://down.ukcms.com/down.php?v=1.1.0 测试网站首页: 0x0 ...
- java servlet 3.0文件上传
在以前,处理文件上传是一个很痛苦的事情,大都借助于开源的上传组件,诸如commons fileupload等.现在好了,很方便,便捷到比那些组件都方便至极.以前的HTML端上传表单不用改变什么,还是一 ...
- servlet3.0文件上传与下载
描述:文件上传与下载是在JavaEE中常见的功能,实现文件上传与下载的方式有多种,其中文件上传的方式有: (1)commons-fileupload: (2)Servlet 3.0 实现文件上传 (3 ...
- NetCore3.0 文件上传与大文件上传的限制
NetCore文件上传两种方式 NetCore官方给出的两种文件上传方式分别为“缓冲”.“流式”.我简单的说说两种的区别, 1.缓冲:通过模型绑定先把整个文件保存到内存,然后我们通过IFormFile ...
- Android采取async框架文件上传
页面效果 须要的权限 <uses-permission android:name="android.permission.INTERNET"/> 网络訪问权限; 布局文 ...
- yii2.0 文件上传
Yii 2.0 出来好长时间了,一直都是看下官方网站,没实践过,今天弄了下图片上传操作. 1创建一个简单的数据表 mysql> desc article; +---------+-------- ...
随机推荐
- 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 ...
- 1.1 Java程序设计平台
Java并不只是一种语言.在此之前出现的那么多中语言也没有能够引起那么大的轰动.Java是一个完整的平台,有一个庞大的库,其中包含了很多可重用的代码和一个提供诸如安全性.跨操作系统的可移植性以及自动垃 ...
- Spring MVC 单元测试Demo
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(locations={" ...
- scrapy——3 crawlSpider——爱问
scrapy——3 crawlSpider crawlSpider 爬取一般网站常用的爬虫类.其定义了一些规则(rule)来提供跟进link的方便的机制. 也许该spider并不是完全适合您的特定网 ...
- 【[Offer收割]编程练习赛14 D】剑刃风暴(半径为R的圆能够覆盖的平面上最多点数目模板)
[题目链接]:http://hihocoder.com/problemset/problem/1508 [题意] [题解] 求一个半径为R的圆能够覆盖的平面上的n个点中最多的点数; O(N2log2N ...
- PatentTips - DMA address translation between peer-to-peer IO devices
BACKGROUND As processing resources have increased, demands to run multiple software programs and ope ...
- code vs 3376 符号三角形
3376 符号三角形 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 如下图是由14个“+”和14个“-”组 ...
- [转]十五天精通WCF——第一天 三种Binding让你KO80%的业务
转眼wcf技术已经出现很多年了,也在.net界混的风生水起,同时.net也是一个高度封装的框架,作为在wcf食物链最顶端的我们所能做的任务已经简单的不能再简单了, 再简单的话马路上的大妈也能写wcf了 ...
- <LeetCode OJ> 268. Missing Number
268. Missing Number Total Accepted: 31740 Total Submissions: 83547 Difficulty: Medium Given an array ...
- LeetCode之LCP(Longest Common Prefix)问题
这个也是简单题目.可是关键在于题意的理解. 题目原文就一句话:Write a function to find the longest common prefix string amongst an ...