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

 更新时间:2018年05月10日 08:34:50   作者:jiashuai94  
这篇文章主要为大家详细介绍了Retrofit+RxJava实现带进度下载文件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

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

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

github: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];

 int len;
 while ((len = inputString.read(b)) != -1) {
 fos.write(b,0,len);
 }
 inputString.close();
 fos.close();

 } catch (FileNotFoundException e) {
 listener.onFail("FileNotFoundException");
 } catch (IOException e) {
 listener.onFail("IOException");
 }

 }
}
  • 在构造中将下载地址和最后回调传入,当然,也可以将保存地址传入;
  • 在OkHttpClient添加我们自定义的拦截器;
  • 注意.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 支持RxJava;
  • 使用RxJava的map方法将responseBody转为输入流;
  • 在doOnNext中将输入流写入文件;

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java微信公众平台开发(7) 公众平台测试帐号的申请

    Java微信公众平台开发(7) 公众平台测试帐号的申请

    这篇文章主要为大家详细介绍了Java微信公众平台开发第七步,微信公众平台测试帐号的申请,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Maven Repository 使用方法

    Maven Repository 使用方法

    对于Java开发者来说,Maven Repository是个必须掌握的网站,它可以让开发者更加方便地管理和维护 Java 项目的依赖项,同时简化了项目开发的过程,这篇文章主要介绍了Maven Repository 使用方法,需要的朋友可以参考下
    2024-02-02
  • SpringBoot 对象存储 MinIO的详细过程

    SpringBoot 对象存储 MinIO的详细过程

    MinIO 是一个基于 Go 实现的高性能、兼容 S3 协议的对象存储,它适合存储海量的非结构化的数据,这篇文章主要介绍了SpringBoot 对象存储 MinIO,需要的朋友可以参考下
    2023-07-07
  • kafka 启动报错 missingTopicsFatal is true的解决

    kafka 启动报错 missingTopicsFatal is true的解决

    这篇文章主要介绍了kafka 启动报错 missingTopicsFatal is true的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 解决idea 通过build project 手动触发热部署失败的问题

    解决idea 通过build project 手动触发热部署失败的问题

    在debug运行项目的过程中,并且保证(不添加方法,不修改方法名)一定的规则的情况下,可以通过build project 来手动热部署项目,本文给大家介绍解决idea 通过build project 手动触发热部署失败的问题,感兴趣的朋友一起看看吧
    2023-12-12
  • Springboot MultipartFile文件上传与下载的实现示例

    Springboot MultipartFile文件上传与下载的实现示例

    在Spring Boot项目中,可以使用MultipartFile类来处理文件上传和下载操作,本文就详细介绍了如何使用,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • Spring通过c3p0配置bean连接数据库

    Spring通过c3p0配置bean连接数据库

    这篇文章主要为大家详细介绍了Spring通过c3p0配置bean连接数据库,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • 如何使用cmd命令行窗口运行java文件

    如何使用cmd命令行窗口运行java文件

    多年以来一直使用的是IDE来写java项目,导致很多的最基础的东西都渐渐模糊了,最近遇到一个问题就是如果命令行来运行一个java项目,这里总结下,这篇文章主要给大家介绍了关于如何使用cmd命令行窗口运行java文件的相关资料,需要的朋友可以参考下
    2023-10-10
  • Java中Arrays.sort()方法的比较器详解

    Java中Arrays.sort()方法的比较器详解

    这篇文章主要介绍了Java中Arrays.sort()方法的比较器详解,Arrays.sort(Object[] a)此方法看似没有要求我们实现比较器,对于基本数据类型,String类型确实如此,因为这些类型中已经自带了compareTo()方法,需要的朋友可以参考下
    2023-12-12
  • 怎样给Kafka新增分区

    怎样给Kafka新增分区

    这篇文章主要介绍了怎样给Kafka新增分区问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12

最新评论