前言
本文主要讲解RxJava2.0+Retrofit2.0实现下载文件并带进度效果,如果按照传统方法是很容易实现的。但是,发现网上搜索的例子都是通过OkHttpClient的拦截器去拦截Response来实现进度显示(侵入性有点强),个人发现bug不少,问题都是在UI更新方面出了问题,只要记住UI刷新在主线程更新都容易解决,下面介绍两种非修改拦截器实现文件下载的方法:
效果图
图中下载速度较快,是基于在公司专线环境测试
依赖添加
/*Retrofit是一款类型安全的网络框架,基于HTTP协议,服务于Android和java语言,集成了okhttp依赖*/
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
/*RxAndroid一款Android客户端组件间异步通信的框架,1和2差别很大*/
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.8'
具体实现
/**
* 下载文件法1(使用Handler更新UI)
*
* @param observable 下载被观察者
* @param destDir 下载目录
* @param fileName 文件名
* @param progressHandler 进度handler
*/
public static void downloadFile(Observable observable, final String destDir, final String fileName, final DownloadProgressHandler progressHandler) {
final DownloadInfo downloadInfo = new DownloadInfo();
observable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(ResponseBody responseBody) {
InputStream inputStream = null;
long total = 0;
long responseLength;
FileOutputStream fos = null;
try {
byte[] buf = new byte[2048];
int len;
responseLength = responseBody.contentLength();
inputStream = responseBody.byteStream();
final File file = new File(destDir, fileName);
downloadInfo.setFile(file);
downloadInfo.setFileSize(responseLength);
File dir = new File(destDir);
if (!dir.exists()) {
dir.mkdirs();
}
fos = new FileOutputStream(file);
int progress = 0;
int lastProgress;
long startTime = System.currentTimeMillis(); // 开始下载时获取开始时间
while ((len = inputStream.read(buf)) != -1) {
fos.write(buf, 0, len);
total += len;
lastProgress = progress;
progress = (int) (total * 100 / responseLength);
long curTime = System.currentTimeMillis();
long usedTime = (curTime - startTime) / 1000;
if (usedTime == 0) {
usedTime = 1;
}
long speed = (total / usedTime); // 平均每秒下载速度
// 如果进度与之前进度相等,则不更新,如果更新太频繁,则会造成界面卡顿
if (progress > 0 && progress != lastProgress) {
downloadInfo.setSpeed(speed);
downloadInfo.setProgress(progress);
downloadInfo.setCurrentSize(total);
progressHandler.sendMessage(DownloadProgressHandler.DOWNLOAD_PROGRESS, downloadInfo);
}
}
fos.flush();
downloadInfo.setFile(file);
progressHandler.sendMessage(DownloadProgressHandler.DOWNLOAD_SUCCESS, downloadInfo);
} catch (final Exception e) {
downloadInfo.setErrorMsg(e);
progressHandler.sendMessage(DownloadProgressHandler.DOWNLOAD_FAIL, downloadInfo);
} finally {
try {
if (fos != null) {
fos.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onError(Throwable e) {//new Consumer
downloadInfo.setErrorMsg(e);
progressHandler.sendMessage(DownloadProgressHandler.DOWNLOAD_FAIL, downloadInfo);
}
@Override
public void onComplete() {// new Action()
}
});
}
方法2:使用RxJava发射器更新下载进度
/**
* 下载文件法2(使用RXJava更新UI)
*
* @param observable
* @param destDir
* @param fileName
* @param progressHandler
*/
public static void downloadFile2(Observable observable, final String destDir, final String fileName, final DownloadProgressHandler progressHandler) {
final DownloadInfo downloadInfo = new DownloadInfo();
observable
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.flatMap(new Function>() {
@Override
public ObservableSource apply(final ResponseBody responseBody) throws Exception {
return Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
InputStream inputStream = null;
long total = 0;
long responseLength = 0;
FileOutputStream fos = null;
try {
byte[] buf = new byte[2048];
int len = 0;
responseLength = responseBody.contentLength();
inputStream = responseBody.byteStream();
final File file = new File(destDir, fileName);
downloadInfo.setFile(file);
downloadInfo.setFileSize(responseLength);
File dir = new File(destDir);
if (!dir.exists()) {
dir.mkdirs();
}
fos = new FileOutputStream(file);
int progress = 0;
int lastProgress = 0;
long startTime = System.currentTimeMillis(); // 开始下载时获取开始时间
while ((len = inputStream.read(buf)) != -1) {
fos.write(buf, 0, len);
total += len;
lastProgress = progress;
progress = (int) (total * 100 / responseLength);
long curTime = System.currentTimeMillis();
long usedTime = (curTime - startTime) / 1000;
if (usedTime == 0) {
usedTime = 1;
}
long speed = (total / usedTime); // 平均每秒下载速度
// 如果进度与之前进度相等,则不更新,如果更新太频繁,则会造成界面卡顿
if (progress > 0 && progress != lastProgress) {
downloadInfo.setSpeed(speed);
downloadInfo.setProgress(progress);
downloadInfo.setCurrentSize(total);
emitter.onNext(downloadInfo);
}
}
fos.flush();
downloadInfo.setFile(file);
emitter.onComplete();
} catch (Exception e) {
downloadInfo.setErrorMsg(e);
emitter.onError(e);
} finally {
try {
if (fos != null) {
fos.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
addDisposable(d);
}
@Override
public void onNext(DownloadInfo downloadInfo) {
progressHandler.onProgress(downloadInfo.getProgress(), downloadInfo.getFileSize(), downloadInfo.getSpeed());
}
@Override
public void onError(Throwable e) {
progressHandler.onError(e);
}
@Override
public void onComplete() {
LogUtils.i("下载完成");
progressHandler.onCompleted(downloadInfo.getFile());
}
});
}
api服务接口类
DownloadApi.java
public interface DownloadApi {
/**
* 下载Apk1文件
*
*/
@Streaming
@GET("imtt.dd.qq.com/16891/C527A902F14C1FFD8AA9C13872D5F92F.apk?mkey=5c41136cb711c35d&f=0c2f&fsname=com.tencent.moyu_1.4.0_1.apk&csr=1bbd&cip=183.17.229.168&proto=https")
Observable downloadApkFile1();
/**
* 下载Apk2文件
*
*/
@Streaming
@GET("https://cc849cacb0e96648f8dd4bb35ff8365b.dd.cdntips.com/imtt.dd.qq.com/16891/5BB89032B0755F5922C80DA8C2CAF735.apk?mkey=5c415b9fb711c35d&f=07b4&fsname=com.tencent.mobileqq_7.9.7_994.apk&csr=1bbd&cip=183.17.229.168&proto=https")
Observable downloadApkFile2();
/**
* 下载Apk3文件
*
*/
@Streaming
@GET("https://cc849cacb0e96648f8dd4bb35ff8365b.dd.cdntips.com/imtt.dd.qq.com/16891/BEC5EEF53983300D9F0AB46166EC9EA7.apk?mkey=5c41a20bda11e60f&f=184b&fsname=com.tencent.pao_1.0.61.0_161.apk&csr=1bbd&cip=218.17.192.250&proto=https")
Observable downloadApkFile3();
}
下载进度Handler类
DownloadProgressHandler.java
/**
* 下载进度Handler
*
* @author Kelly
* @version 1.0.0
* @filename DownloadProgressHandler.java
* @time 2018/7/25 15:25
* @copyright(C) 2018 song
*/
public abstract class DownloadProgressHandler implements DownloadCallBack {
public static final int DOWNLOAD_SUCCESS = 0;
public static final int DOWNLOAD_PROGRESS = 1;
public static final int DOWNLOAD_FAIL = 2;
protected ResponseHandler mHandler = new ResponseHandler(this, Looper.getMainLooper());
/**
* 发送消息,更新进度
*
* @param what
* @param downloadInfo
*/
public void sendMessage(int what, DownloadInfo downloadInfo) {
mHandler.obtainMessage(what, downloadInfo).sendToTarget();
}
/**
* 处理消息
* @param message
*/
protected void handleMessage(Message message) {
DownloadInfo progressBean = (DownloadInfo) message.obj;
switch (message.what) {
case DOWNLOAD_SUCCESS://下载成功
onCompleted(progressBean.getFile());
removeMessage();
break;
case DOWNLOAD_PROGRESS://下载中
onProgress(progressBean.getProgress(), progressBean.getFileSize(),progressBean.getSpeed());
break;
case DOWNLOAD_FAIL://下载失败
onError(progressBean.getErrorMsg());
break;
default:
removeMessage();
break;
}
}
private void removeMessage() {
if (mHandler != null){
mHandler.removeCallbacksAndMessages(null);
}
}
protected static class ResponseHandler extends Handler {
private DownloadProgressHandler mProgressHandler;
public ResponseHandler(DownloadProgressHandler mProgressHandler, Looper looper) {
super(looper);
this.mProgressHandler = mProgressHandler;
}
@Override
public void handleMessage(Message msg) {
mProgressHandler.handleMessage(msg);
}
}
}
DownloadCallBack.java
/**
* 下载回调
*/
public interface DownloadCallBack {
/**
* 进度,运行在主线程
*
* @param progress 下载进度
* @param total 总大小
* @param speed 下载速率
*/
void onProgress(int progress, long total,long speed);
/**
* 运行在主线程
*
* @param file
*/
void onCompleted(File file);
/**
* 运行在主线程
*
* @param e
*/
void onError(Throwable e);
}
文件下载信息类
DownloadInfo.java
/**
* 下载文件信息
*
* @author Kelly
* @version 1.0.0
* @filename DownloadInfo.java
* @time 2018/7/25 14:27
* @copyright(C) 2018 song
*/
public class DownloadInfo {
private File file;
private String fileName;
private long fileSize;//单位 byte
private long currentSize;//当前下载大小
private int progress;//当前下载进度
private long speed;//下载速率
private Throwable errorMsg;//下载异常信息
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public long getCurrentSize() {
return currentSize;
}
public void setCurrentSize(long currentSize) {
this.currentSize = currentSize;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
this.progress = progress;
}
public long getSpeed() {
return speed;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public Throwable getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(Throwable errorMsg) {
this.errorMsg = errorMsg;
}
}
调用方法
DownloadApi apiService = RetrofitHelper.getInstance().getApiService(DownloadApi.class);
FileDownloader.downloadFile(apiService.downloadApkFile1(), DOWNLOAD_APK_PATH, "test.apk", new DownloadProgressHandler() {
@Override
public void onProgress(int progress, long total, long speed) {
LogUtils.i("progress:" + progress + ",speed:" + speed);
mProgress.setText(progress + "%");
mFileSize.setText(FileUtils.formatFileSize(total));
mRate.setText(FileUtils.formatFileSize(speed)+"/s");
}
@Override
public void onCompleted(File file) {
LogUtils.i("下载apk文件成功");
FileDownloader.clear();
}
@Override
public void onError(Throwable e) {
LogUtils.e("下载apk文件异常", e);
FileDownloader.clear();
}
});
下载地址
https://download.csdn.net/download/u011082160/11261106