Retrofit2.0 实现图文(参数+图片)上传方法总结
最近项目里用到了类似图文上传的功能,以前都是封装OkHttp的文件上传功能,这次想换个姿势,想用Retrofit2.0实现这样的功能,本来以为挺简单的,没想到进入了深坑,连续调整了好几种姿势都报了同一个错,接着网上类似的文章找了一大推,讲得都是模棱两可,或者对多参数格式不够友好,最后还是去看了相关的源码,自己把这个问题提出来解决了,在这里记录一下。
一、定义网络请求接口
publicinterfaceGoodsReturnApiService{ @Multipart @POST(Compares.GOODS_RETURN_POST)//这里是自己post文件的地址 ObservablepostGoodsReturnPostEntitys(@PartMapMap map,@PartList parts); }
上面定义了一个接口用于上传文件请求,有几个注解需要说明一下,@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"; List fileList=newArrayList<>(); fileList.add(newFile(path1)); fileList.add(newFile(path2)); List partList=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默认赋值,这里才是关键。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。