这里先说一下断点下载的实现方法,在下载的过程中如果中断,记录下已下载的大小,当再次下载时我们就从已下载的文件长度开始,从而实现断点下载。这里主要以retrofit2网络框架为例
定义下载后存储路径
//文件存储路径
final String patch = context.getExternalCacheDir() + "/downlaod/";
这里获取的是 SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据。
定义接口
@Streaming //下载大文件
@GET
Observable<ResponseBody> executeDownload(@Header("Range") String range, @Url() String url);
http协议从1.1开始支持获取文件的部分内容,这为并行下载以及断点续传提供了技术支持。它通过在Header里两个参数实现的,客户端发请求时对应的是Range,服务器端响应时对应的是Content-Range。Range作为请求头表示只请求实体的一部分,指定范围 Range: bytes=开始长度-结束长度如:0-999。
下载方法
public Subscription downFile(final long range, final String url, final Context context, final DownloadCallBack downloadCallback) {
//请求的总长度
File file = new File(patch, "tim.apk");
String totalLength = "-";
if (file.exists()) {
totalLength += file.length();
}
return RetofitHttp.getInstance().executeDownload("bytes=" + Long.toString(range) + totalLength, url)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
downloadCallback.onError(e.toString());
}
@Override
public void onNext(ResponseBody responseBody) {
RandomAccessFile randomAccessFile = null;
InputStream inputStream = null;
long total = range;
long responseLength = 0;
try {
byte[] buf = new byte[2048];
int len = 0;
responseLength = responseBody.contentLength();
inputStream = responseBody.byteStream();
File file = new File(patch, "tim.apk");
File dir = new File(patch);
if (!dir.exists()) {
dir.mkdirs();
}
randomAccessFile = new RandomAccessFile(file, "rwd");
if (range == 0) {
randomAccessFile.setLength(responseLength);
}
randomAccessFile.seek(range);
int progress = 0;
int lastProgress = 0;
while ((len = inputStream.read(buf)) != -1) {
randomAccessFile.write(buf, 0, len);
total += len;
lastProgress = progress;
progress = (int) (total * 100 / randomAccessFile.length());
if (progress > 0 && progress != lastProgress) {
downloadCallback.onProgress(progress);
}
}
downloadCallback.onCompleted();
} catch (Exception e) {
downloadCallback.onError(e.getMessage());
e.printStackTrace();
} finally {
try {
//此处是存储文件下载的长度
SharedPreferencesUtils.setParam(context, "apkLength", total);
if (randomAccessFile != null) {
randomAccessFile.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
这里我是将下载与文件处理都放置在子线程之中,避免异常
下载监听
public interface DownloadCallBack {
void onProgress(int progress);
void onCompleted();
void onError(String msg);
}
下载调用
final File file = new File(patch+"tim.apk");
long range = 0;
int progress = 0;
if (file.exists()) {
range = (long) SharedPreferencesUtils.getParam(getNonNullActivity(), "apkLength", 0L);
progress = (int) (range * 100 / file.length());
if (range == file.length()) {
mBinding.tvText.setText("100%");
return;
}
}
HttpMethods.getInStance().downFile(range, "http://sqdd.myapp.com/myapp/qqteam/tim/down/tim.apk", getNonNullActivity(), new DownloadCallBack() {
@Override
public void onProgress(int progress) {
getNonNullActivity().runOnUiThread(() -> {
});
}
@Override
public void onCompleted() {
getNonNullActivity().runOnUiThread(() -> {
});
}
@Override
public void onError(String msg) {
getNonNullActivity().runOnUiThread(() -> {
});
}
});
由于下载响应文件处理是在子线程所以监听回调如果要做UI回应的话是需要在主线程操作的。