前言:最近有个断点下载的需求,捣鼓了下,而后分享下java
关于文件下载,在第2篇中已经详细说过,这里就不在详细说了,先对以前的下载作个封装 首先是ApiServer
git
@Streaming
@GET
/** * 大文件官方建议用 @Streaming 来进行注解,否则会出现IO异常,小文件能够忽略不注入 */
Observable<ResponseBody> downloadFile(@Url String fileUrl);
复制代码
定义下载回调github
public interface DownFileCallback {
void onSuccess(String path);
void onFail(String msg);
void onProgress(long totalSize, long downSize);
}
复制代码
DownLoadManager
api
public class DownLoadManager {
private static DownLoadManager loadManager;
private HashMap<String, FileObserver> 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<ResponseBody, String>() {
@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<String>() {
@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<T> extends DisposableObserver<T> {
@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点要注意架构
RANGE
请求头if (downSize != 0 && totalSize != 0) {
request = request.newBuilder()
.addHeader("RANGE", "bytes=" + downSize + "-" + totalSize).build();
}
复制代码
downSize
和totalSize
,前者是下载的开始长度,后者是整个文件的长度,这些都须要咱们暂停时,本身作保存。app
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就不贴了dom
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<ResponseBody, String>() {
@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<String>() {
@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
为本次下载的已下载量ide
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);
}
});
}
});
}
复制代码
最后,献上源码 Githubpost