retrofit+rxjava已经是目前市场上最主流的网络框架,使用它进行平常的网络请求异常轻松,之前也用retrofit做过上传文件和下载文件,但发现:使用retrofit做下载默认是不支持进度回调的,但产品大大要求下载文件时显示下载进度,那就不得不深究下了。
接下来我们一起封装,使用retrofit+rxjava实现带进度下载文件。
github:jsdownload
先来看看uml图:
大家可能还不太清楚具体是怎么处理的,别急,我们一步步来:
1、添依赖是必须的啦
1
2
3
4
5
|
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、写回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* 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,计算下载百分比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
/**
* 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 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 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
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* description:
* created by jia on 2017/11/30.
* 人之所以能,是相信能
*/
public interface downloadservice {
@streaming
@get
observable<responsebody> download( @url string url);
}
|
注意:
这里@url是传入完整的的下载url;不用截取
使用@streaming注解方法
6、最后开始请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
/**
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中将输入流写入文件;
当然也需要注意下载回调的各个位置。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/jiashuai94/article/details/78775314