Retrofit2.0 实现图文(参数+图片)上传方法总结
最近项目里用到了类似图文上传的功能,以前都是封装OkHttp的文件上传功能,这次想换个姿势,想用Retrofit2.0实现这样的功能,本来以为挺简单的,没想到进入了深坑,连续调整了好几种姿势都报了同一个错,接着网上类似的文章找了一大推,讲得都是模棱两可,或者对多参数格式不够友好,最后还是去看了相关的源码,自己把这个问题提出来解决了,在这里记录一下。
一、定义网络请求接口
publicinterfaceGoodsReturnApiService{
@Multipart
@POST(Compares.GOODS_RETURN_POST)//这里是自己post文件的地址
ObservablepostGoodsReturnPostEntitys(@PartMapMapmap,@PartListparts);
}
上面定义了一个接口用于上传文件请求,有几个注解需要说明一下,@Multipart这是Retrofit专门用于文件上传的注解,需要配合@POST一起使用。
方法postGoodsReturnPostEntitys(@PartMapMap
在类型Map
第二个参数使用注解@Part用于文件上传,多文件上传使用集合类型List
这里着重说明一下,postGoodsReturnPostEntitys(@PartMapMap
二、初始化Retrofit
publicclassHttpRequestClient{
publicstaticfinalStringTAG="HttpRequestClientTAG";
privatestaticRetrofitretrofit;
privatestaticOkHttpClientgetOkHttpClient(){
//日志显示级别
HttpLoggingInterceptor.Levellevel=HttpLoggingInterceptor.Level.BODY;
//新建log拦截器
HttpLoggingInterceptorloggingInterceptor=newHttpLoggingInterceptor(newHttpLoggingInterceptor.Logger(){
@Override
publicvoidlog(Stringmessage){
Log.d(TAG,message);
}
});
loggingInterceptor.setLevel(level);
//定制OkHttp
OkHttpClient.BuilderhttpClientBuilder=newOkHttpClient
.Builder();
//OkHttp进行添加拦截器loggingInterceptor
httpClientBuilder.addInterceptor(loggingInterceptor);
returnhttpClientBuilder.build();
}
publicstaticRetrofitgetRetrofitHttpClient(){
if(null==retrofit){
synchronized(HttpRequestClient.class){
if(null==retrofit){
retrofit=newRetrofit.Builder()
.client(getOkHttpClient())
.baseUrl(Compares.URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
}
}
returnretrofit;
}
}
为了演示,Retrofit封装比较简陋,为的是查看网络拦截,就不详细说了。
三、发起文件上传请求
privatevoidpostGoodsPicToServer(){
Mapparams=newHashMap<>();
//以下参数是伪代码,参数需要换成自己服务器支持的
params.put("type",convertToRequestBody("type"));
params.put("title",convertToRequestBody("title"));
params.put("info",convertToRequestBody("info");
params.put("count",convertToRequestBody("count"));
//为了构建数据,同样是伪代码
Stringpath1=Environment.getExternalStorageDirectory()+File.separator+"test1.jpg";
Stringpath2=Environment.getExternalStorageDirectory()+File.separator+"test1.jpg";
ListfileList=newArrayList<>();
fileList.add(newFile(path1));
fileList.add(newFile(path2));
ListpartList=filesToMultipartBodyParts(fileList);
HttpRequestClient.getRetrofitHttpClient().create(GoodsReturnApiService.class)
.postGoodsReturnPostEntitys(params,partList)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(newObserver(){
@Override
publicvoidonSubscribe(@NonNullDisposabled){
}
@Override
publicvoidonNext(@NonNullGoodsReturnPostEntitygoodsReturnPostEntity){
}
@Override
publicvoidonError(@NonNullThrowablee){
}
@Override
publicvoidonComplete(){
}
});
}
上面的params和fileList都是构造的伪代码,需要根据自己项目的业务需求改变。
下面是上传文件成功第一个关键,对参数请求头(姑且叫这个名字,对应Retrofit上传文件时参数那部分请求头,下文件(图片)请求头同理,对应文件那部分请求头)的content-type赋值,使用convertToRequestBody()方法。
privateRequestBodyconvertToRequestBody(Stringparam){
RequestBodyrequestBody=RequestBody.create(MediaType.parse("text/plain"),param);
returnrequestBody;
}
因为GsonConverterFactory.create()转换器的缘故,会将参数请求头的content-type值默认赋值application/json,如果没有进行这步转换操作,就可以在OKHttp3的日志拦截器中查看到这样的赋值,这样导致服务器不能正确识别参数,导致上传失败,所以这里需要对参数请求头的content-type设置一个正确的值:text/plain。
下面是上传文件成功第二个关键的地方,将文件(图片)请求头的content-type使用方法filesToMultipartBodyParts()对其赋值"image/png",并返回MultipartBody.Part集合。
privateListfilesToMultipartBodyParts(List files){ List parts=newArrayList<>(files.size()); for(Filefile:files){ RequestBodyrequestBody=RequestBody.create(MediaType.parse("image/png"),file); MultipartBody.Partpart=MultipartBody.Part.createFormData("multipartFiles",file.getName(),requestBody); parts.add(part); } returnparts; }
说到底,还是对参数请求头和文件(图片)请求头的content-type属性赋值处理,不要让Retrofit默认赋值,这里才是关键。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。