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

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

一、使用RxJava+Retrofit上传文件

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

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

    @Multipart
    @POST("user/uploadAvatar.do")
    Observable<UploadAvatarResponse> 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<UploadAvatarResponse>(this, true) {
                    @Override
                    public void onSuccess(UploadAvatarResponseresponse) {
                        EventBus.getDefault().post(new RegisterSuccess("register success"));
                        showToast("头像上传成功");
                        finish();
                    }
                });

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

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

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

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

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

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

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

二、使用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<ResponseBody> getObserver() {
        return new Observer<ResponseBody>() {

            @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

使用RxJava2 + Retrofit2 + OKHttp进行POST请求,可以按照以下步骤进行: 1. 添加依赖 在项目的build.gradle文件中添加以下依赖: ``` dependencies { implementation 'io.reactivex.rxjava2:rxjava:2.2.19' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:okhttp:4.9.1' } ``` 2. 创建Service接口 创建一个接口,用于定义POST请求的方法。例如: ``` public interface ApiService { @POST("login") Observable<LoginResponse> login(@Body LoginRequest request); } ``` 3. 创建Retrofit对象 在Application类或其他初始化类中,创建Retrofit对象: ``` public class MyApp extends Application { private static ApiService apiService; @Override public void onCreate() { super.onCreate(); // 创建OkHttpClient对象 OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); // 创建Retrofit对象 Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://example.com/api/") .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); apiService = retrofit.create(ApiService.class); } public static ApiService getApiService() { return apiService; } } ``` 4. 发起POST请求 在需要发起POST请求的地方,可以使用以下代码: ``` LoginRequest request = new LoginRequest(); request.setUsername("admin"); request.setPassword("123456"); MyApp.getApiService().login(request) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<LoginResponse>() { @Override public void onSubscribe(Disposable d) { } @Override public void onNext(LoginResponse response) { // 处理响应数据 } @Override public void onError(Throwable e) { // 处理异常 } @Override public void onComplete() { } }); ``` 上述代码中,我们首先创建了一个LoginRequest对象,用于存储要发送的数据。然后调用MyApp.getApiService().login(request)方法,发起POST请求。在这里,我们使用了RxJava2的Observable对象,将请求结果封装为一个可观察对象。使用subscribeOn(Schedulers.io())指定在IO线程中进行网络请求,使用observeOn(AndroidSchedulers.mainThread())指定在主线程中处理响应。最后通过subscribe方法订阅请求结果,处理响应数据或异常。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值