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

来源:互联网 发布:windows下wep破解 编辑:程序博客网 时间:2024/05/21 05:39

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];            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");        }    }}
  1. 在构造中将下载地址和最后回调传入,当然,也可以将保存地址传入;
  2. 在OkHttpClient添加我们自定义的拦截器;
  3. 注意.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 支持RxJava;
  4. 使用RxJava的map方法将responseBody转为输入流;
  5. 在doOnNext中将输入流写入文件;

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

获取更多精彩内容,请关注微信公众号——安卓 干货营!
这里写图片描述

阅读全文
0 0