使用Retrofit+RxJava实现带进度下载文件

Posted Android机动车

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Retrofit+RxJava实现带进度下载文件相关的知识,希望对你有一定的参考价值。

Retrofit+RxJava已经是目前市场上最主流的网络框架,使用它进行平常的网络请求异常轻松,之前也用Retrofit做过上传文件和下载文件,但发现:使用Retrofit做下载默认是不支持进度回调的,但产品大大要求下载文件时显示下载进度,那就不得不深究下了。

接下来我们一起封装,使用Retrofit+RxJava实现带进度下载文件。

github:https://github.com/shuaijia/JsDownload

先来看看UML图:

大家可能还不太清楚具体是怎么处理的,别急,我们一步步来:

1、添依赖是必须的啦

compile 'io.reactivex:rxjava:1.1.0'
compile 'io.reactivex:rxandroid:1.1.0'
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'

使用时注意版本号

2、写回调

/**
* Description: 下载进度回调
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/

public interface JsDownloadListener {
   void onStartDownload();
   void onProgress(int progress);
   void onFinishDownload();
   void onFail(String errorInfo);
}

这里就不用多说了,下载的回调,就至少应该有开始下载、下载进度、下载完成、下载失败 四个回调方法。

注意下在onProgress方法中返回进度百分比,在onFail中返回失败原因。

3、重写ResponseBody,计算下载百分比

/**
* Description: 带进度 下载请求体
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/

public class JsResponseBody extends ResponseBody {
   private ResponseBody responseBody;
   private JsDownloadListener downloadListener;
   // BufferedSource 是okio库中的输入流,这里就当作inputStream来使用。
   private BufferedSource bufferedSource;
   public JsResponseBody(ResponseBody responseBody, JsDownloadListener downloadListener) {
       this.responseBody = responseBody;
       this.downloadListener = downloadListener;
   }
   @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);
               // read() returns the number of bytes read, or -1 if this source is exhausted.
               totalBytesRead += bytesRead != -1 ? bytesRead : 0;
               Log.e("download", "read: "+ (int) (totalBytesRead * 100 / responseBody.contentLength()));
               if (null != downloadListener) {
                   if (bytesRead != -1) {
                       downloadListener.onProgress((int) (totalBytesRead * 100 / responseBody.contentLength()));
                   }
               }
               return bytesRead;
           }
       };
   }
}

将网络请求的ResponseBody 和JsDownloadListener 在构造中传入。

这里的核心是source方法,返回ForwardingSource对象,其中我们重写其read方法,在read方法中计算百分比,并将其传给回调downloadListener。

4、拦截器

只封装ResponseBody 是不够的,关键我们需要拿到请求的ResponseBody ,这里我们就用到了拦截器Interceptor 。

/**
* Description: 带进度 下载  拦截器
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/

public class JsDownloadInterceptor implements Interceptor {
   private JsDownloadListener downloadListener;
   public JsDownloadInterceptor(JsDownloadListener downloadListener) {
       this.downloadListener = downloadListener;
   }
   @Override
   public Response intercept(Chain chain) throws IOException {
       Response response = chain.proceed(chain.request());
       return response.newBuilder().body(
               new JsResponseBody(response.body(), downloadListener)).build();
   }
}

通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。

在拦截方法intercept中返回我们刚刚封装的ResponseBody 。

5、网络请求service

/**
* Description:
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/

public interface DownloadService {
   @Streaming
   @GET
   Observable<ResponseBody> download(@Url String url);
}

注意:

  • 这里@Url是传入完整的的下载URL;不用截取

  • 使用@Streaming注解方法

6、最后开始请求

/**
1. Description: 下载工具类
2. Created by jia on 2017/11/30.
3. 人之所以能,是相信能
*/

public class DownloadUtils {
   private static final String TAG = "DownloadUtils";
   private static final int DEFAULT_TIMEOUT = 15;
   private Retrofit retrofit;
   private JsDownloadListener listener;
   private String baseUrl;
   private String downloadUrl;
   public DownloadUtils(String baseUrl, JsDownloadListener listener) {
       this.baseUrl = baseUrl;
       this.listener = listener;
       JsDownloadInterceptor mInterceptor = new JsDownloadInterceptor(listener);
       OkHttpClient httpClient = new OkHttpClient.Builder()
               .addInterceptor(mInterceptor)
               .retryOnConnectionFailure(true)
               .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
               .build();
       retrofit = new Retrofit.Builder()
               .baseUrl(baseUrl)
               .client(httpClient)
               .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
               .build();
   }
   /**
    * 开始下载
    *
    * @param url
    * @param filePath
    * @param subscriber
    */

   public void download(@NonNull String url, final String filePath, Subscriber subscriber) {
       listener.onStartDownload();
       // subscribeOn()改变调用它之前代码的线程
       // observeOn()改变调用它之后代码的线程
       retrofit.create(DownloadService.class)
               .download(url)
               .subscribeOn(Schedulers.io())
               .unsubscribeOn(Schedulers.io())
               .map(new Func1<ResponseBody, InputStream>() {
                   @Override
                   public InputStream call(ResponseBody responseBody) {
                       return responseBody.byteStream();
                   }
               })
               .observeOn(Schedulers.computation()) // 用于计算任务
               .doOnNext(new Action1<InputStream>() {
                   @Override
                   public void call(InputStream inputStream) {
                       writeFile(inputStream, filePath);
                   }
               })
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(subscriber);
   }
   /**
    * 将输入流写入文件
    *
    * @param inputString
    * @param filePath
    */

   private void writeFile(InputStream inputString, String filePath) {
       File file = new File(filePath);
       if (file.exists()) {
           file.delete();
       }
       FileOutputStream fos = null;
       try {
           fos = new FileOutputStream(file);
           byte[] b = new byte[1024];
           while ((inputString.read(b)) != -1) {
               fos.write(b);
           }
           inputString.close();
           fos.close();
       } catch (FileNotFoundException e) {
           listener.onFail("FileNotFoundException");
       } catch (IOException e) {
           listener.onFail("IOException");
       }
   }
}
  1. 在OkHttpClient添加我们自定义的拦截器;

  2. 注意.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 支持RxJava;

  3. 使用RxJava的map方法将responseBody转为输入流;

  4. 在doOnNext中将输入流写入文件;

当然也需要注意下载回调的各个位置。


以上是关于使用Retrofit+RxJava实现带进度下载文件的主要内容,如果未能解决你的问题,请参考以下文章

使用OkHttp实现下载的进度监听和断点续传

带你走通 OkHttp+Retrofit+Rxjava

带你走通 OkHttp+Retrofit+Rxjava

一款基于RxJava2+Retrofit2实现简单易用的网络请求框架

基于Retrofit+RxJava 封装 Leopard 网络框架

如何使用 rxjava/retrofit 正确等待/观察来自服务器的传入数据