前言:最近有个断点下载的需求,捣鼓了下,然后分享下
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装(一)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件下载(二)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件上传(三)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之常见问题(四)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之断点下载(五)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之数据预处理(六)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之多Url(七)
- 【Android架构】基于MVP模式的Retrofit2+RXjava封装之Token的刷新(八)
关于文件下载,在第2篇中已经详细说过,这里就不在详细说了,先对之前的下载做个封装
首先是ApiServer
@Streaming
@GET
/**
* 大文件官方建议用 @Streaming 来进行注解,不然会出现IO异常,小文件可以忽略不注入
*/
Observable downloadFile(@Url String fileUrl);
定义下载回调
public interface DownFileCallback {
void onSuccess(String path);
void onFail(String msg);
void onProgress(long totalSize, long downSize);
}
DownLoadManager
public class DownLoadManager {
private static DownLoadManager loadManager;
private HashMap hashMap;
private OkHttpClient client;
private Retrofit retrofit;
private ApiServer apiServer;
private DownFileCallback fileCallback;
public DownLoadManager() {
hashMap = new HashMap<>();
client = new OkHttpClient.Builder()
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
return response.newBuilder().body(new ProgressResponseBody(response.body(),
new ProgressResponseBody.ProgressListener() {
@Override
public void onProgress(long totalSize, long downSize) {
if (fileCallback != null) {
fileCallback.onProgress(totalSize, downSize);
}
}
})).build();
}
}).build();
retrofit = new Retrofit.Builder().client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("https://wawa-api.vchangyi.com/").build();
apiServer = retrofit.create(ApiServer.class);
}
public static DownLoadManager getInstance() {
synchronized (Object.class) {
if (loadManager == null) {
loadManager = new DownLoadManager();
}
}
return loadManager;
}
/**
* 下载单个文件
*
* @param url
* @param fileCallback
*/
public void downFile(final String url, final DownFileCallback fileCallback) {
//如果正在下载,则暂停
if (isDownLoad(url)) {
pause(url);
return;
}
this.fileCallback = fileCallback;
//存储的文件路径
final String path = getTemporaryName(url);
FileObserver observer = apiServer.downloadFile(url).map(new Function() {
@Override
public String apply(ResponseBody body) throws Exception {
File file = FileUtil.saveFile(path, body);
return file.getPath();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new FileObserver() {
@Override
public void onSuccess(String path) {
fileCallback.onSuccess(path);
hashMap.remove(url);
}
@Override
public void onError(String msg) {
fileCallback.onFail(msg);
hashMap.remove(url);
}
});
//保存
hashMap.put(url, observer);
}
/**
* 暂停/取消任务
*
* @param url 完整url
*/
public void pause(String url) {
if (hashMap.containsKey(url)) {
FileObserver observer = hashMap.get(url);
if (observer != null) {
observer.dispose();
hashMap.remove(url);
}
}
}
/**
* 获取临时文件名
*
* @param url
* @return
*/
public static String getTemporaryName(String url) {
String type = "";
if (url.contains(".")) {
type = url.substring(url.lastIndexOf("."));
}
String dirName = Environment.getExternalStorageDirectory() + "/mvp/";
File f = new File(dirName);
//不存在创建
if (!f.exists()) {
f.mkdirs();
}
return dirName + System.currentTimeMillis() + type;
}
/**
* 是否在下载
*
* @param url
* @return
*/
public boolean isDownLoad(String url) {
return hashMap.containsKey(url);
}
public abstract class FileObserver extends DisposableObserver {
@Override
public void onNext(T t) {
onSuccess(t);
}
@Override
public void onError(Throwable e) {
onError(e.getMessage());
}
@Override
public void onComplete() {
}
public abstract void onSuccess(T o);
public abstract void onError(String msg);
}
}
断点下载的话,有2点要注意
- 1.要添加
RANGE
请求头
if (downSize != 0 && totalSize != 0) {
request = request.newBuilder()
.addHeader("RANGE", "bytes=" + downSize + "-" + totalSize).build();
}
downSize
和totalSize
,前者是下载的开始长度,后者是整个文件的长度,这些都需要我们暂停时,自己做保存。
- 2.写文件问题,断点下载时,就不能从头开始写入文件,需要从上次结束的地方开始,这里就用到了
RandomAccessFile
/**
* @param filePath
* @param start 起始位置
* @param body
*/
public static File saveFile(String filePath, long start, ResponseBody body) {
InputStream inputStream = null;
RandomAccessFile raf = null;
File file = null;
try {
file = new File(filePath);
raf = new RandomAccessFile(filePath, "rw");
inputStream = body.byteStream();
byte[] fileReader = new byte[4096];
//移动到该位置
raf.seek(start);
while (true) {
int read = inputStream.read(fileReader);
if (read == -1) {
break;
}
raf.write(fileReader, 0, read);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return file;
}
建个实体类,来保存文件相关属性,get和set就不贴了
public class DownModel {
private String url;
private String path;
private String title;
private String cover;
private long totalSize;
private long currentTotalSize;
private long downSize;
private boolean isExists;
private boolean isFinish;
private boolean isPause;
}
/**
* 下载单个文件
*
* @param downModel
* @param
*/
public void downFile(final DownModel downModel, final DownFileCallback fileCallback) {
if (downModel == null) {
return;
}
//如果正在下载,则暂停
final String url = downModel.getUrl();
if (isDownLoad(url)) {
pause(url);
Log.e("cheng", "pause url=" + url);
return;
}
//当前链接
currentUrl = url;
//是否是断点下载
if (downModel.getDownSize() != 0 && downModel.getTotalSize() != 0) {
totalSize = downModel.getTotalSize();
downSize = downModel.getDownSize();
currentPath = downModel.getPath();
} else {
totalSize = 0;
downSize = 0;
currentPath = getTemporaryName(url);
downModel.setPath(currentPath);
}
this.fileCallback = fileCallback;
Log.e("cheng", "currentUrl=" + currentUrl);
Log.e("cheng", "downSize=" + downSize + ",totalSize=" + totalSize + ",currentPath=" + currentPath);
FileObserver observer = apiServer.downloadFile(url).map(new Function() {
@Override
public String apply(ResponseBody body) throws Exception {
if (downModel.getDownSize() != 0 && downModel.getTotalSize() != 0) {
return FileUtil.saveFile(currentPath, downModel.getDownSize(), body).getPath();
}
File file = FileUtil.saveFile(currentPath, body);
return file.getPath();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new FileObserver() {
@Override
public void onSuccess(String path) {
downModel.setFinish(true);
downModel.setPath(path);
downModel.setExists(true);
if (fileCallback != null) {
fileCallback.onSuccess(path);
}
hashMap.remove(url);
currentUrl = null;
}
@Override
public void onError(String msg) {
if (fileCallback != null) {
fileCallback.onFail(msg);
}
hashMap.remove(url);
currentUrl = null;
}
});
//保存
hashMap.put(url, observer);
}
需要注意的是,如果文件总大小为50M
,已下载的大小为10M
,再次下载时onProgress
返回的totalSize
是文件总长度 减去 已下载大小 10M
, 即40M
,downSize
为本次下载的已下载量
private void down2() {
String url = "http://download.sdk.mob.com/apkbus.apk";
if (downModel == null) {
downModel = new DownModel();
downModel.setUrl(url);
}
DownLoadManager.getInstance().downFile(downModel, new DownFileCallback() {
@Override
public void onSuccess(String path) {
showtoast("下载成功,path=" + path);
}
@Override
public void onFail(String msg) {
}
@Override
public void onProgress(long totalSize, long downSize) {
Log.e("cheng", "totalSize =" + totalSize + ",downSize=" + downSize);
if (downModel.getTotalSize() == 0) {
downModel.setTotalSize(totalSize);
}
downModel.setCurrentTotalSize(totalSize);
downModel.setDownSize(downSize + downModel.getTotalSize() - downModel.getCurrentTotalSize());
runOnUiThread(new Runnable() {
@Override
public void run() {
int progress = (int) (downModel.getDownSize() * 100 / downModel.getTotalSize());
tvDown.setText(progress + "%");
sbDown.setProgress(progress);
}
});
}
});
}
最后,献上源码 Github