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