转自:https://blog.csdn.net/qq_20521573/article/details/78356747
一、使用Retrofit上传文件时遇到的坑。项目中注册接口中有上传头像的功能,本以为上传头像是一个很简单的事情,可万万没想到使用Retrofit上传头像时却遇到了一个大坑。填这个坑花费了足足两天的时间。先来看下上传文件时遇到的异常信息:
java.lang.IllegalArgumentException: Invalid % sequence at 432: --8c46470b-77e7-4dd8-
b4e5-55f6968db182
Content-Disposition: form-data; name="phone"
Content-Length: 11
1551526
--8c46470b-77e7-4dd8-b4e5-55f6968db182
Content-Disposition: form-data; name="password"
Content-Length: 6
123123
--8c46470b-77e7-4dd8-b4e5-55f6968db182
Content-Disposition: form-data; name="uploadFile"; filename="test.txt"
Content-Type: multipart/form-data
Content-Length: 27
Hello World %
--8c46470b-77e7-4dd8-b4e5-55f6968db182--
分析上面的日志可以知道,错误的原因是因为文件中存在非法的参数“%”。什么情况啊,难道上传文件还不能包含“%”了这不科学啊。况且试验了图片上传每张图片流中都包含%。也就是所有图片上传均失败。于是自己又写了一个test.txt
的文件放到了手机里边测试,当test.txt文件中写入Hello World时候测试上传没有
异常,但当我在Hello World末尾加了%后就出现了java.lang.IllegalArgumentException: Invalid % sequence at 432
这个异常!刚开始的时候以为是自己上传文件部分代码写的有问题。于是参考网上的代码试了各种上传方法均无济于事。接着开始百度看网上是否有类似问题,但是几乎搜遍了整个百度也没有找到问题的解决方案。后来转战谷歌,强大的谷歌上也没有找到解决办法!无奈之下只好自己调了,于是开始逐一排查自己封装的代码是否有问题。经过不懈的努力终于找到了问题所在。请看下面代码
private IdeaApi() {
// 日志拦截器
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor((message) -> {
try {
String text = URLDecoder.decode(message, "utf-8");
LogUtils.e("OKHttp-----", text);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
LogUtils.e("OKHttp-----", message);
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb
LogUtils.e(UserInfoTools.getUserId(Utils.getContext()));
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(IdeaApiService.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.connectTimeout(IdeaApiService.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.addNetworkInterceptor(new HttpCacheInterceptor())
.cache(cache)
.addInterceptor((chain) -> { // 统一配置配置请求头
Request request = chain.request().newBuilder()
.addHeader("user_id", UserInfoTools.getUserId(Utils.getContext()))
.addHeader("psw_just_test", "4567")
.build();
return chain.proceed(request);
})
.addInterceptor(loggingInterceptor)
.build();
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(IdeaApiService.API_SERVER_URL)
.build();
service = retrofit.create(IdeaApiService.class);
}
这段代码是对Retrofit进行封装并添加了拦截器,当我把 .addInterceptor(loggingInterceptor)这一行注释掉以后,奇迹出现了。点击注册后竟然神奇般的注册成功了!(发现了问题所在,兴奋至极啊,真的差一点就放弃了!)注意这行代码,是为Retrofit添加了日志拦截器!继续跟进代码看这个拦截器的内容,经过调试发现竟然是因为HttpLoggingInterceptor 中setLevel
的一行代码导致的,即loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
这行代码。于是试了试把BOEY改成了HEADERS后再试测发现这个异常就没有再出现了!但是改过之后日志拦截器会有些问题,就是只能拦截请求头的日志,请求体中的日志无法拦截,其实已经失去了拦截日志的意义了!但是这个问题终归解决了。只需要改一个单词或者去掉日志拦截器!修改后是这样的:loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
二 、实现单文件上传的两种方法
解决了上面的异常后心里一下再踏实了好多!于是接下来看看如何使用Retrofit来上传文件把!假如说我们注册时接口有三个参数:手机号、密码、头像。
1.单文件上传方法(1)
首先定义注册接口方法:
@Multipart
@POST("user/register.do")
Observable> register(@Part("phone") RequestBody phone,@Part("password") RequestBody password,@Part MultipartBody.Part image);
注意上面上面方法加了@Multipart的注解。对于上传文件必须要加这个注解,不然会报异常!另外方法中有三个参数,即我们注册时所需要的参数!返回值是我我们自定义的Observable(详见上一片文章),接下来在我们注册的页面调用这个方法,如下:
File file = new File(picPath);
// 图片参数
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("uploadFile", file.getName(), requestFile);
// 手机号参数
RequestBody phoneBody = RequestBody.create(MediaType.parse("multipart/form-data"), phone);
// 密码参数
RequestBody pswBody = RequestBody.create(MediaType.parse("multipart/form-data"), password);
IdeaApi.getApiService()
.register(phoneBody,pswBody,body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver>(this, true) {
@Override
public void onSuccess(BasicResponse response) {
EventBus.getDefault().post(new RegisterSuccess("register success"));
showToast("注册成功,请登陆");
finish();
}
});
显然,上面的方法有个弊端。当我们接口中需要的参数较少时使用上面的方法无可厚非。但假如说我们接口中需要的参数非常多,那么上面的方法使用起来就麻烦了。因此接下来我们可以看下第二种方法如何使用。
2.单文件上传方法(2),同样以注册接口为例,先定义注册接口的方法:
@Multipart
@POST("user/register.do")
Observable> register(@Part List partList);
可以发现方法中的参数变成了List《MultipartBody.Part》的集合。这样所有的参数我们只需要放到这个集合里边即可!是不是方便了很多?所以推荐使用这种写法!接下来看注册页面如何调用这个方法:
File file = new File(picPath);
RequestBody imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("phone", phone)
.addFormDataPart("password", password)
.addFormDataPart("uploadFile", file.getName(), imageBody);
List parts = builder.build().parts();
IdeaApi.getApiService()
.register(parts)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver>(this, true) {
@Override
public void onSuccess(BasicResponse response) {
EventBus.getDefault().post(new RegisterSuccess("register success"));
showToast("注册成功,请登陆");
finish();
}
这样是不是比第一种方法清爽了很多呢!
三、 实现多文件上传。
对于多图上传其实跟单文件上传没有多大区别,只不过多了些参数而已。跟但图片类似同样可以有两种方法。
1.多文件传方法(1):
首先定义多文件上传接口:
@POST("upload/")
Observable uploadFiles(@Part("filename") String description,
@Part("pic\"; filename=\"image1.png") RequestBody imgs1,
@Part("pic\"; filename=\"image2.png") RequestBody imgs2,);
调用接口上传图片:
File file = new File(picPath);
RequestBody requestFile1 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part
.createFormData("uploadFile", file.getName(), requestFile);
RequestBody requestFile2 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part
.createFormData("uploadFile", file.getName(), requestFile);
IdeaApi.getApiService()
.uploadFiles("pictures",requestFile1,requestFile2 )
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver>(this, true) {
@Override
public void onSuccess(BasicResponse response) {
EventBus.getDefault().post(new RegisterSuccess("register success"));
showToast("注册成功,请登陆");
finish();
}
或者使用第二种方法实现多文件上传,如下:
1.多文件上传方法(2),采用map集合来存放多个图片RequestBody参数。
首先定义多文件上传接口
@POST()
Observable uploadFiles(
@Part("filename") String description,
@PartMap() Map maps);
然后调用接口实现多文件上传
File file = new File(picPath);
RequestBody requestFile1 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part
.createFormData("uploadFile", file.getName(), requestFile);
RequestBody requestFile2 = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part
.createFormData("uploadFile", file.getName(), requestFile);
Map map=new HashMap<>();
map.put("文件1",requestFile1 );
map.put("文件2",requestFile2 );
IdeaApi.getApiService()
.uploadFiles("pictures",map)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DefaultObserver>(this, true) {
@Override
public void onSuccess(BasicResponse response) {
EventBus.getDefault().post(new RegisterSuccess("register success"));
showToast("注册成功,请登陆");
finish();
}
至此关于Retrofit上传文件已经基本完成。但由于多文件上传并没有实际验证,因此如果存在问题请留言告知。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_20521573/article/details/78356747