Retrofit+RxJava實(shí)現(xiàn)帶進(jìn)度下載文件
Retrofit+RxJava已經(jīng)是目前市場(chǎng)上最主流的網(wǎng)絡(luò)框架,使用它進(jìn)行平常的網(wǎng)絡(luò)請(qǐng)求異常輕松,之前也用Retrofit做過(guò)上傳文件和下載文件,但發(fā)現(xiàn):使用Retrofit做下載默認(rèn)是不支持進(jìn)度回調(diào)的,但產(chǎn)品大大要求下載文件時(shí)顯示下載進(jìn)度,那就不得不深究下了。
接下來(lái)我們一起封裝,使用Retrofit+RxJava實(shí)現(xiàn)帶進(jìn)度下載文件。
github:JsDownload
先來(lái)看看UML圖:
大家可能還不太清楚具體是怎么處理的,別急,我們一步步來(lái):
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'
使用時(shí)注意版本號(hào)
2、寫回調(diào)
/**
* Description: 下載進(jìn)度回調(diào)
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/
public interface JsDownloadListener {
void onStartDownload();
void onProgress(int progress);
void onFinishDownload();
void onFail(String errorInfo);
}
這里就不用多說(shuō)了,下載的回調(diào),就至少應(yīng)該有開(kāi)始下載、下載進(jìn)度、下載完成、下載失敗 四個(gè)回調(diào)方法。
注意下在onProgress方法中返回進(jìn)度百分比,在onFail中返回失敗原因。
3、重寫ResponseBody,計(jì)算下載百分比
/**
* Description: 帶進(jìn)度 下載請(qǐng)求體
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/
public class JsResponseBody extends ResponseBody {
private ResponseBody responseBody;
private JsDownloadListener downloadListener;
// BufferedSource 是okio庫(kù)中的輸入流,這里就當(dāng)作inputStream來(lái)使用。
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;
}
};
}
}
將網(wǎng)絡(luò)請(qǐng)求的ResponseBody 和JsDownloadListener 在構(gòu)造中傳入。
這里的核心是source方法,返回ForwardingSource對(duì)象,其中我們重寫其read方法,在read方法中計(jì)算百分比,并將其傳給回調(diào)downloadListener。
4、攔截器
只封裝ResponseBody 是不夠的,關(guān)鍵我們需要拿到請(qǐng)求的ResponseBody ,這里我們就用到了攔截器Interceptor 。
/**
* Description: 帶進(jìn)度 下載 攔截器
* 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();
}
}
通常情況下攔截器用來(lái)添加,移除或者轉(zhuǎn)換請(qǐng)求或者回應(yīng)的頭部信息。
在攔截方法intercept中返回我們剛剛封裝的ResponseBody 。
5、網(wǎng)絡(luò)請(qǐng)求service
/**
* Description:
* Created by jia on 2017/11/30.
* 人之所以能,是相信能
*/
public interface DownloadService {
@Streaming
@GET
Observable<ResponseBody> download(@Url String url);
}
注意:
這里@Url是傳入完整的的下載URL;不用截取
使用@Streaming注解方法
6、最后開(kāi)始請(qǐng)求
/**
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();
}
/**
* 開(kāi)始下載
*
* @param url
* @param filePath
* @param subscriber
*/
public void download(@NonNull String url, final String filePath, Subscriber subscriber) {
listener.onStartDownload();
// subscribeOn()改變調(diào)用它之前代碼的線程
// observeOn()改變調(diào)用它之后代碼的線程
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()) // 用于計(jì)算任務(wù)
.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");
}
}
}
- 在構(gòu)造中將下載地址和最后回調(diào)傳入,當(dāng)然,也可以將保存地址傳入;
- 在OkHttpClient添加我們自定義的攔截器;
- 注意.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 支持RxJava;
- 使用RxJava的map方法將responseBody轉(zhuǎn)為輸入流;
- 在doOnNext中將輸入流寫入文件;
當(dāng)然也需要注意下載回調(diào)的各個(gè)位置。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)戰(zhàn)之醫(yī)院管理系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java實(shí)現(xiàn)醫(yī)院管理系統(tǒng),文中用到的技術(shù)有:SpringBoot、Layui、Freemaker等,感興趣的同學(xué)可以了解一下2022-04-04
解析Jmeter脫離Jenkins后Ant集成郵件通知問(wèn)題
今天來(lái)講下本地的ant構(gòu)建并發(fā)送郵件。配置下來(lái)挺順利也挺簡(jiǎn)單的,對(duì)Jmeter脫離Jenkins后Ant集成郵件通知問(wèn)題感興趣的朋友跟隨小編一起看看吧2021-12-12
Java編程生產(chǎn)者消費(fèi)者實(shí)現(xiàn)的四種方法
Java生產(chǎn)者和消費(fèi)者問(wèn)題是線程安全模型中的經(jīng)典問(wèn)題:生產(chǎn)者和消費(fèi)者在同一個(gè)時(shí)間段共用同一個(gè)存儲(chǔ)空間,生產(chǎn)者向存儲(chǔ)空間中添加產(chǎn)品呢,消費(fèi)者取走產(chǎn)品,當(dāng)存儲(chǔ)空間為空時(shí),消費(fèi)者阻塞,當(dāng)存儲(chǔ)空間滿時(shí),生產(chǎn)者阻塞2021-10-10
關(guān)于Java中靜態(tài)代碼塊的執(zhí)行淺析
這篇文章主要給大家介紹了關(guān)于Java中靜態(tài)代碼塊執(zhí)行的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
springboot2.0.0配置多數(shù)據(jù)源出現(xiàn)jdbcUrl is required with driverClassN
這篇文章主要介紹了springboot2.0.0配置多數(shù)據(jù)源出現(xiàn)jdbcUrl is required with driverClassName的錯(cuò)誤,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
使用java代碼獲取新浪微博應(yīng)用的access token代碼實(shí)例
這篇文章主要介紹了使用java代碼獲取新浪微博應(yīng)用的access token實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
Spring Boot利用@Async如何實(shí)現(xiàn)異步調(diào)用:自定義線程池
這篇文章主要給大家介紹了關(guān)于Spring Boot利用@Async如何實(shí)現(xiàn)異步調(diào)用:自定義線程池的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2018-05-05

