(三)最流行的网络请求框架Rxjava2+Retrofit之文件上传

来源:互联网 发布:淘宝带图评价福利 编辑:程序博客网 时间:2024/04/30 14:42

* 本文同步发表在简书,转载请注明出处。

结合 第一篇文章对Retrofit的封装,本篇文章将讲述如何实现文件上传服务器。要讲述的内容主要有一下几点:

  • 使用Retrofit上传文件时遇到的坑
  • 实现单文件上传
  • 实现多文件上传

一、使用Retrofit上传文件时遇到的坑。项目中注册接口中有上传头像的功能,本以为上传头像是一个很简单的事情,可万万没想到使用Retrofit上传头像时却遇到了一个大坑。填这个坑花费了足足两天的时间。先来看下上传文件时遇到的异常信息:

java.lang.IllegalArgumentException: Invalid % sequence at 432: --8c46470b-77e7-4dd8-b4e5-55f6968db182Content-Disposition: form-data; name="phone"Content-Length: 111551526--8c46470b-77e7-4dd8-b4e5-55f6968db182Content-Disposition: form-data; name="password"Content-Length: 6123123--8c46470b-77e7-4dd8-b4e5-55f6968db182Content-Disposition: form-data; name="uploadFile"; filename="test.txt"Content-Type: multipart/form-dataContent-Length: 27Hello 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<BasicResponse<RegisterBean>> 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<BasicResponse<RegisterBean>>(this, true) {                    @Override                    public void onSuccess(BasicResponse<RegisterBean> response) {                        EventBus.getDefault().post(new RegisterSuccess("register success"));                        showToast("注册成功,请登陆");                        finish();                    }                });

显然,上面的方法有个弊端。当我们接口中需要的参数较少时使用上面的方法无可厚非。但假如说我们接口中需要的参数非常多,那么上面的方法使用起来就麻烦了。因此接下来我们可以看下第二种方法如何使用。

2.单文件上传方法(2),同样以注册接口为例,先定义注册接口的方法:

    @Multipart    @POST("user/register.do")    Observable<BasicResponse<RegisterBean>> register(@Part List<MultipartBody.Part> 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<MultipartBody.Part> parts = builder.build().parts();        IdeaApi.getApiService()                .register(parts)                .subscribeOn(Schedulers.io())                .observeOn(AndroidSchedulers.mainThread())                .subscribe(new DefaultObserver<BasicResponse<RegisterBean>>(this, true) {                    @Override                    public void onSuccess(BasicResponse<RegisterBean> response) {                        EventBus.getDefault().post(new RegisterSuccess("register success"));                        showToast("注册成功,请登陆");                        finish();                    }

这样是不是比第一种方法清爽了很多呢!

三、 实现多文件上传。
对于多图上传其实跟单文件上传没有多大区别,只不过多了些参数而已。跟但图片类似同样可以有两种方法。
1.多文件传方法(1):
首先定义多文件上传接口:

    @POST("upload/")    Observable<BasicResponse> 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<BasicResponse<RegisterBean>>(this, true) {                    @Override                    public void onSuccess(BasicResponse<RegisterBean> response) {                        EventBus.getDefault().post(new RegisterSuccess("register success"));                        showToast("注册成功,请登陆");                        finish();                    }

或者使用第二种方法实现多文件上传,如下:
1.多文件上传方法(2),采用map集合来存放多个图片RequestBody参数。
首先定义多文件上传接口

  @POST()  Observable<BasicResponse> uploadFiles(        @Part("filename") String description,        @PartMap() Map<String, RequestBody> 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<String,RequestBody> 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<BasicResponse<RegisterBean>>(this, true) {                    @Override                    public void onSuccess(BasicResponse<RegisterBean> response) {                        EventBus.getDefault().post(new RegisterSuccess("register success"));                        showToast("注册成功,请登陆");                        finish();                    }

至此关于Retrofit上传文件已经基本完成。但由于多文件上传并没有实际验证,因此如果存在问题请留言告知。

阅读全文
0 0
原创粉丝点击