(三)Rxjava2+Retrofit之文件上传与下载

结合 第一篇文章对Retrofit的封装,本篇文章将讲述如何实现文件上传与下载。本篇文章可分为文件上传与文件下载两部分内容。

一、使用RxJava+Retrofit上传文件

使用Retrofit上传文件到服务器可分为单文件上传与多文件上传,实现都比较简单。不妨用两个例子来分别看下单文件和多文件上传。

1 、实现单文件上传
单文件上传最常见的就是更换头像,我们就以此为例来看。
首先定义上传头像的接口方法,代码如下:

    @Multipart
    @POST("user/uploadAvatar.do")
    Observable uploadAvatar(@Part("userId") RequestBody userId,@Part MultipartBody.Part image);

注意上面上面方法加了@Multipart的注解。对于上传文件必须要加这个注解,不然会报异常!另外方法中有两个参数,即UserId和要上传的头像文件!返回值是我我们自定义的Observable(详见上一片文章),接下来在我们注册的页面调用这个方法,如下:

 File file = new File(picPath);
 //  图片参数
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part imageBody = MultipartBody.Part.createFormData("uploadFile", file.getName(), requestFile);
 //  手机号参数
RequestBody userIdBody = RequestBody.create(MediaType.parse("multipart/form-data"), phone);

IdeaApi.getApiService()
                .uploadAvatar(userIdBody,imageBody )
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver(this, true) {
                    @Override
                    public void onSuccess(UploadAvatarResponseresponse) {
                        EventBus.getDefault().post(new RegisterSuccess("register success"));
                        showToast("头像上传成功");
                        finish();
                    }
                });

显然,上面的方法有个弊端。当接口中需要的参数较少时使用上面的方法无可厚非。如果接口中需要的参数非常多,那么上面的方法使用起来就麻烦了。因此对于参数较多的单文件上传可以使将所有参数都放入一个List集合中。同样以上传头像为例。

先定义上传头像接口的方法:

    @Multipart
    @POST("user/register.do")
    Observable register(@Part List partList);

可以看到现在方法中间参数变为一个List《ltipartBody.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("userId", userId)
                .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(UploadAvatarResponse response) {
                        EventBus.getDefault().post(new RegisterSuccess("register success"));
                        showToast("注册成功,请登陆");
                        finish();
                    }

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

2.实现多文件上传。
对于多图上传其实跟单文件上传没有多大区别,只不过多了些参数而已。先看定义多文件上传接口:

	@POST("upload/uploadPic")
    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(UpLoadMultiFileResponse response) {
                        EventBus.getDefault().post(new RegisterSuccess("register success"));
                        showToast("注册成功,请登陆");
                        finish();
                    }

同样,当上传图片较多时可以采用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("picture1",requestFile1 );
	map.put("picture2",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实现文件下载才是比较重要的内容。

二、使用RxJava+Retrofit下载

使用Retrofit下载文件其实非常简单,但是对于用户来说往往都希望可以看到下载进度,然而遗憾的是Retrofit并没有为我们提供文件下载进度的接口。因此,关于下载进度就需要我们自己来通过拦截器实现。本文不讲解Retrofit下载的基本使用,而是针对Retrofit加入下载进度实现一个简单的封装。老规矩,先来看一下封装后的使用。代码如下:

DownloadUtils downloadUtils = new DownloadUtils();
//	开始下载
public void download(View view) {
        btn.setClickable(false);
        downloadUtils.download(Constants.DOWNLOAD_URL, new DownloadListener() {
            @Override
            public void onProgress(int progress) {
                LogUtils.e("--------下载进度:" + progress);
                Log.e("onProgress", "是否在主线程中运行:" + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
                progressBar.setProgress(progress);
                mTvPercent.setText(String.valueOf(progress) + "%");
            }

            @Override
            public void onSuccess(ResponseBody responseBody) {  //  运行在子线程
                saveFile(responseBody);
                Log.e("onSuccess", "是否在主线程中运行:" + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
            }

            @Override
            public void onFail(String message) {
                btn.setClickable(true);
                ToastUtils.show("文件下载失败,失败原因:" + message);
                Log.e("onFail", "是否在主线程中运行:" + String.valueOf(Looper.getMainLooper() == Looper.myLooper()));
            }

            @Override
            public void onComplete() {  //  运行在主线程中
                ToastUtils.show("文件下载成功");
                btn.setClickable(true);
            }
        });
    }
//	取消下载
public void cancelDownload(View view) {
        if (downloadUtils != null) {
            downloadUtils.cancelDownload();
        }
    }

上面代码中使用DownloadUtils类来实现下载文件与取消下载。并且在下载时回调出了下载进度。那么关于以上代码是如何实现的呢?接下来看详细解析。
**1.获取下载进度。**新建一个ProgressHelper类,在该类中为Retrofit添加拦截器,核心代码如下:

public static OkHttpClient.Builder addProgress(OkHttpClient.Builder builder){

        if (builder == null){
            builder = new OkHttpClient.Builder();
        }

        final ProgressListener progressListener = new ProgressListener() {
            //该方法在子线程中运行
            @Override
            public void onProgress(long progress, long total, boolean done) {
                Log.d("progress:",String.format("%d%% done\n",(100 * progress) / total));
                if (mProgressHandler == null){
                    return;
                }

                progressBean.setBytesRead(progress);
                progressBean.setContentLength(total);
                progressBean.setDone(done);
                mProgressHandler.sendMessage(progressBean);

            }
        };

        //添加拦截器,自定义ResponseBody,添加下载进度
        builder.networkInterceptors().add(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                okhttp3.Response originalResponse = chain.proceed(chain.request());
                return originalResponse.newBuilder().body(
                        new ProgressResponseBody(originalResponse.body(), progressListener))
                        .build();

            }
        });

        return builder;
    }

上述代码中在添加拦截器时通过自定义的ResponseBody将下载的进度信息回调了出来,并通过Handler不断的将下载进度发送出去。ProgressResponseBody中代码如下:

public class ProgressResponseBody extends ResponseBody {
    private final ResponseBody responseBody;
    private final ProgressListener progressListener;
    private BufferedSource bufferedSource;

    public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                progressListener.onProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                return bytesRead;
            }
        };
    }
}

2.为Retrofit添加进度监听。 上面我们已经实现了下载进度的获取,那么接下来就需要为Retrofit设置下载进度监听了。来看DownloadUtils中的代码:

 public void download(@NonNull String url, DownloadListener downloadListener) {
        mDownloadListener = downloadListener;
        getApiService().download(url)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .doOnNext(getConsumer())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(getObserver());
    }

    private CommonService getApiService() {
        OkHttpClient.Builder httpClientBuilder = RetrofitService.getOkHttpClientBuilder();
        ProgressHelper.addProgress(httpClientBuilder);
        CommonService commonService = RetrofitService.getRetrofitBuilder(Constants.API_SERVER_URL)
                .client(httpClientBuilder.build())
                .build()
                .create(CommonService.class);
        ProgressHelper.setProgressHandler(new DownloadProgressHandler() {
            @Override
            protected void onProgress(long bytesRead, long contentLength, boolean done) {
                mDownloadListener.onProgress((int) ((100 * bytesRead) / contentLength));
            }
        });
        return commonService;
    }

上面代码中将httpClientBuilder添加到ProgressHelper中,然后调用ProgressHelper中的setProgressHandler方法回调除了下载进度信息。此时通过DownloadListener类再次将下载信息回调了出来。同时DownloadListener中还有onSuccess、onFail、onComplete方法,分别在以下地方调用:

private Observer getObserver() {
        return new Observer() {

            @Override
            public void onSubscribe(Disposable d) {
                mDisposables.add(d);
            }

            @Override
            public void onNext(ResponseBody responseBody) {
                mDownloadListener.onComplete();
            }

            @Override
            public void onError(Throwable e) {
                mDownloadListener.onFail(e.getMessage());
            }

            @Override
            public void onComplete() {
                mDownloadListener.onComplete();
            }
        };
    }

3.取消下载。取消下载其实就是通过CompositeDisposable中的clear方法终止了RxJava的生命周期。

至此关于Retrofit的上传与下载就分析完了,文末可以参看源码。

(一)Rxjava2+Retrofit完美封装
(二)Rxjava2+Retrofit实现Token自动刷新
(三)Rxjava2+Retrofit实现文件上传与下载

源码传送门

好库推荐

给大家推荐一下BannerViewPager。这是一个基于ViewPager实现的具有强大功能的无限轮播库。通过BannerViewPager可以实现腾讯视频、QQ音乐、酷狗音乐、支付宝、天猫、淘宝、优酷视频、喜马拉雅、网易云音乐、哔哩哔哩等APP的Banner样式以及指示器样式。

欢迎大家到github关注BannerViewPager!

你可能感兴趣的:(Android封装篇)