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中将输入流写入文件;
当然也需要注意下载回调的各个位置。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。