基于Retrofit+Okio+RxBus实现文件下载(带下载进度)

来源:互联网 发布:无人机测量数据 编辑:程序博客网 时间:2024/06/05 23:51
596人阅读 评论(0)收藏举报
分类:

目录(?)[+]

一、前言

  Retrofit是一个非常优秀、非常流行的简化HTTP请求的库,有个小的不足是下载文件时,没有提供显示文件下载进度的回调,这在下载文件时无疑会影响用户体验,本文基于Retrofit+Okio+RxBus实现了带下载进度的文件下载功能。

二、效果

三、实现过程

3.1 下载文件的代码

  接下来我们从代码入手,分析如何使用及其实现原理。假如现在要下载 http://hengdawb-app.oss-cn-hangzhou.aliyuncs.com/app-debug.apk 对应的APK文件,对应代码如下:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. String baseUrl = "http://hengdawb-app.oss-cn-hangzhou.aliyuncs.com/";  
  2. String fileName = "app-debug.apk";  
  3. String fileStoreDir = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "M_DEFAULT_DIR";  
  4. String fileStoreName = fileName;  
  5. showLoadingDialog();  
  6. FileApi.getInstance(baseUrl).loadFileByName(fileName, new FileCallback(fileStoreDir, fileStoreName) {  
  7.             @Override  
  8.             public void onSuccess(File file) {  
  9.                 super.onSuccess(file);  
  10.                 hDialogBuilder.dismiss();  
  11.             }  
  12.   
  13.             @Override  
  14.             public void progress(long progress, long total) {  
  15.                 updateProgress(progress, total);  
  16.             }  
  17.   
  18.             @Override  
  19.             public void onFailure(Call<ResponseBody> call, Throwable t) {  
  20.                 hDialogBuilder.dismiss();  
  21.                 call.cancel();  
  22.             }  
  23.   
  24.         });  

  • baseUrl:是网络请求基地址,必须以 / 结束;
  • fileName:是文件名,与baseUrl组成完整的下载地址;
  • fileStoreDir:是文件下载后在本地的存储目录;
  • fileStoreName:是文件在本地的存储名称。

3.2 FileCallback

  下载文件的代码非常简单,从上面的代码中可以看到一个叫FileCallback的类,其中有三个方法需要我们自己实现或重写,分别是onSuccess(),onFailure(),progress(),progress()方法有两个参数,progress和total,分别表示文件已下载的大小和总大小,我们只需要将这两个参数不断更新到UI上即可。FileCallback的代码如下:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. package com.hengda.tailyou.retrofitfiledownload.fileload;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7.   
  8. import okhttp3.ResponseBody;  
  9. import retrofit2.Call;  
  10. import retrofit2.Callback;  
  11. import retrofit2.Response;  
  12. import rx.android.schedulers.AndroidSchedulers;  
  13. import rx.functions.Action1;  
  14. import rx.schedulers.Schedulers;  
  15. import rx.subscriptions.CompositeSubscription;  
  16.   
  17. /** 
  18.  * 描述:Retrofit 文件下载回调 
  19.  */  
  20. public abstract class FileCallback implements Callback<ResponseBody> {  
  21.   
  22.     /** 
  23.      * 订阅下载进度 
  24.      */  
  25.     private CompositeSubscription rxSubscriptions = new CompositeSubscription();  
  26.     /** 
  27.      * 目标文件存储的文件夹路径 
  28.      */  
  29.     private String destFileDir;  
  30.     /** 
  31.      * 目标文件存储的文件名 
  32.      */  
  33.     private String destFileName;  
  34.   
  35.     public FileCallback(String destFileDir, String destFileName) {  
  36.         this.destFileDir = destFileDir;  
  37.         this.destFileName = destFileName;  
  38.         subscribeLoadProgress();  
  39.     }  
  40.   
  41.     public void onSuccess(File file) {  
  42.         unsubscribe();  
  43.     }  
  44.   
  45.     public abstract void progress(long progress, long total);  
  46.   
  47.     @Override  
  48.     public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {  
  49.         try {  
  50.             saveFile(response);  
  51.         } catch (IOException e) {  
  52.             e.printStackTrace();  
  53.         }  
  54.     }  
  55.   
  56.     /** 
  57.      * 保存 
  58.      * 
  59.      * @param response 
  60.      * @return 
  61.      * @throws IOException 
  62.      */  
  63.     public File saveFile(Response<ResponseBody> response) throws IOException {  
  64.         InputStream is = null;  
  65.         byte[] buf = new byte[2048];  
  66.         int len;  
  67.         FileOutputStream fos = null;  
  68.         try {  
  69.             is = response.body().byteStream();  
  70.             File dir = new File(destFileDir);  
  71.             if (!dir.exists()) {  
  72.                 dir.mkdirs();  
  73.             }  
  74.             File file = new File(dir, destFileName);  
  75.             fos = new FileOutputStream(file);  
  76.             while ((len = is.read(buf)) != -1) {  
  77.                 fos.write(buf, 0, len);  
  78.             }  
  79.             fos.flush();  
  80.             onSuccess(file);  
  81.             return file;  
  82.         } finally {  
  83.             try {  
  84.                 if (is != null) is.close();  
  85.             } catch (IOException e) {  
  86.             }  
  87.             try {  
  88.                 if (fos != null) fos.close();  
  89.             } catch (IOException e) {  
  90.             }  
  91.         }  
  92.     }  
  93.   
  94.     /** 
  95.      * 订阅文件下载进度 
  96.      */  
  97.     private void subscribeLoadProgress() {  
  98.         rxSubscriptions.add(RxBus.getDefault()  
  99.                 .toObservable(FileLoadEvent.class)  
  100.                 .onBackpressureBuffer()  
  101.                 .subscribeOn(Schedulers.io())  
  102.                 .observeOn(AndroidSchedulers.mainThread())  
  103.                 .subscribe(new Action1<FileLoadEvent>() {  
  104.                     @Override  
  105.                     public void call(FileLoadEvent fileLoadEvent) {  
  106.                         progress(fileLoadEvent.getProgress(), fileLoadEvent.getTotal());  
  107.                     }  
  108.                 }));  
  109.     }  
  110.   
  111.     /** 
  112.      * 取消订阅,防止内存泄漏 
  113.      */  
  114.     private void unsubscribe() {  
  115.         if (!rxSubscriptions.isUnsubscribed()) {  
  116.             rxSubscriptions.unsubscribe();  
  117.         }  
  118.     }  
  119.   
  120. }  

  代码比较多,跟下载进度相关的其实非常少,可以看到咱们实现的progress()方法在是在RxBus收到FileLoadEvent后才调用的,熟悉RxBus的人肯定可以想到,下载进度的实时更新是通过以下几步实现的:

  • 1)RxBus发送FileLoadEvent对象;
  • 2)FileLoadEvent中包含当前下载进度和文件总大小;
  • 3)FileCallback订阅RxBus发送的FileLoadEvent;
  • 4)根据接收到的FileLoadEvent更新下载Dialog的UI。

3.3 何时何处发送的FileLoadEvent?

  使用Retrofit进行网络操作时,通常都与OKHttpClient结合使用;我们可以用OKHttpClient来设置连接超时,请求超时,网络拦截器等。这里我们也需要使用自己的OKHttpClient,在网络拦截器中使用我们自定义的ResponseBody。代码如下:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. private FileApi(String baseUrl) {  
  2.     retrofit = new Retrofit.Builder()  
  3.             .client(initOkHttpClient())  
  4.             .baseUrl(baseUrl)  
  5.             .build();  
  6.     fileService = retrofit.create(FileService.class);  
  7. }  

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 初始化OkHttpClient 
  3.  * 
  4.  * @return 
  5.  */  
  6. private OkHttpClient initOkHttpClient() {  
  7.     OkHttpClient.Builder builder = new OkHttpClient.Builder();  
  8.     builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);  
  9.     builder.networkInterceptors().add(new Interceptor() {  
  10.         @Override  
  11.         public Response intercept(Chain chain) throws IOException {  
  12.             Response originalResponse = chain.proceed(chain.request());  
  13.             return originalResponse  
  14.                     .newBuilder()  
  15.                     .body(new FileResponseBody(originalResponse))  
  16.                     .build();  
  17.         }  
  18.     });  
  19.     return builder.build();  
  20. }  

[java] view plain copy 在CODE上查看代码片派生到我的代码片
  1. public class FileResponseBody extends ResponseBody {  
  2.   
  3.     Response originalResponse;  
  4.   
  5.     public FileResponseBody(Response originalResponse) {  
  6.         this.originalResponse = originalResponse;  
  7.     }  
  8.   
  9.     @Override  
  10.     public MediaType contentType() {  
  11.         return originalResponse.body().contentType();  
  12.     }  
  13.   
  14.     @Override  
  15.     public long contentLength() {  
  16.         return originalResponse.body().contentLength();  
  17.     }  
  18.   
  19.     @Override  
  20.     public BufferedSource source() {  
  21.         return Okio.buffer(new ForwardingSource(originalResponse.body().source()) {  
  22.             long bytesReaded = 0;  
  23.   
  24.             @Override  
  25.             public long read(Buffer sink, long byteCount) throws IOException {  
  26.                 long bytesRead = super.read(sink, byteCount);  
  27.                 bytesReaded += bytesRead == -1 ? 0 : bytesRead;  
  28.                 RxBus.getDefault().post(new FileLoadEvent(contentLength(), bytesReaded));  
  29.                 return bytesRead;  
  30.             }  
  31.         });  
  32.     }  
  33.   
  34. }  

  FileResponseBody重写了source()方法,在Okio.buffer中处理下载进度相关的逻辑,也是在这个时候发送的FileLoadEvent的,到这里整个过程就差不多全了。

总结

  其实整个过程并不复杂,总结起来就一下几点:

  • 1)使用自定义的OKHttpClient,在自定义的FileResponseBody中发送文件下载进度;
  • 2)在FileCallback中订阅FileLoadEvent;
  • 3)根据FileLoadEvent中的参数,调用progress(),刷新下载UI。

源码已上传Github,RetrofitFileDownload

转自:http://www.tailyou.site/2016/06/22/基于Retrofit-Okio-RxBus实现文件下载(带下载进度)/#rd?sukey=3997c0719f15152082403b18fc5108edcba8ef69abbe1891a9450ae7921f97ab7f9f472fd055d7b55360001d25a6d031
0 0