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.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. RESTful API 学习【第1篇】

    一. 什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” REST从资源的角 ...

  2. Git 基础教程 之 搭建Git服务器

    截图自: 廖雪峰老师官方网站 https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/0 ...

  3. Keil MDK下如何设置非零初始化变量(复位后变量值不丢失)

    一些工控产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备.而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始 ...

  4. 【MFC Programming】 Using Dialog To Set A Correlate Menu

    This blog will show how to display a menu we designed in a dialog. 1.Insert a new dialog& a new ...

  5. 赛门铁克扩展验证EV SSL证书

      申请EV SSL证书,将接受最严格验证企业域名所有权和企业身份信息,属于最高信任级别扩展验证(EV)的 EV SSL证书,最高达256位自适应加密.Symantec不仅提供先进的SSL加密技术,同 ...

  6. How to change java version in Linux

    How to change default Java version on Linux Posted on November 1, 2015 by Dan Nanni Leave a comment ...

  7. trigger dependencies

    有时候,会想知道某个表是不是会有一些trigger去更新它. 但是一般更新语句是写在trigger 内部,所以我不确定 dba_dependencies这个视图能不能存储这种依赖关系. 做个试验: 创 ...

  8. IE訪问Oracle EBS打不开Form的问题

     IE訪问Oracle EBS打不开Form的问题 例如以下图. 最后我才知道真正的原因.原来是兼容性视图的问题. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5 ...

  9. Delphi7中单元文件内各个部分的执行顺序

    注:本文主要是讨论delphi程序启动时和退出时的执行顺序,期间有些知识来源于Delphi帮助,有些来自<Delphi7程序设计教程>(这本书只告诉我有initialization 和 f ...

  10. 复习--最小生成树&&并查集

    我个人比较喜欢Kruskal算法,所以就把这个方法写了一下,但过不了洛谷,70分. 思路是先全读入,再排序,一条一条加边.运用并查集. #include<iostream> #includ ...