摘要:本文实现断点下载文件,支持多调线程,最大数量10条。(下载测试用例为APK文件,保证了文件的正确性。)
github已经托管项目,可以下载demo查看,同事提供在线引用,使用方式参考、:
https://github.com/yuanhy/Appframework
下面是一些主要的代码摘要:
/**
* 断点下载的文件
*
* @param context 上下文
* @param multithreadnubs 线程数量 (最大10条)
* @param url 网络url
* @param fileDownPath 文件的父目录
* @param progressListener
*/
public static void fileDownMultithreadBreakpoint();一行代码就可以达到你要的要求。不用再去管里面的业务。如果你想了解更多,就继续往下。
AndroidOkHttp3 androidOkHttp3 = AndroidOkHttp3.getInstance(context);
androidOkHttp3.initFileClient();
1 //获取文件的长度
androidOkHttp3.getContentLength(url, new YCallBack() {
@Override
public void onError(Object o) {
progressListener.onError("");
}
@Override
public void onOk(Object o) {
HashMap<String, Long> hashMap = new HashMap<>();
long contentlength = (long) o;
long length = (contentlength / multithreadnubs);
if (contentlength > 0) {//2获取到正确的文件长度
for (int i = 0; i < multithreadnubs; i++) {//线程分割
long start = length * i;
long end = length * i + length - 1;
if (i == multithreadnubs - 1) {//3最后一条线程要特别处理
start = length * i;
end = contentlength - 1;
}
String dataKey = fileName + start;//4多点下载保存本节点的key,用于实现 界面的进度刷新功能
long them = SharedPreferencesUtil.getSharedPreferencesUtil(context).getLong(dataKey);
hashMap.put(dataKey, them);
AppFramentUtil.logCatUtil.i("总长度:" + contentlength +
" 线程 :" + i + "起始位置:" + start + " 结束位置:" + end);
//5下载文件的线程
FileDownThred fileDownThred = new FileDownThred(context, start, end, url, fileDownPath,
fileName, i, new ProgressListener() {
@Override
public void onProgress(long fileSizeBytes, long remainingBytes, String dataKey, String thredId, boolean done) {
long downFileSize = remainingBytes;
hashMap.put(dataKey, downFileSize);
long lenth = 0;
for (long value : hashMap.values()) {
lenth = lenth + value;
}
int progress = (int) (lenth * 100 / contentlength);
AppFramentUtil.logCatUtil.i("线程数量:" + multithreadnubs +
" 当前线程:" + thredId +
" 需要下载:" + fileSizeBytes + " 已下载:" + remainingBytes +
" 总大小:" + contentlength + " 已下载总:" + lenth + " 总进度:" + progress);
progressListener.onProgress(progress, contentlength);
if (lenth == contentlength) {
progressListener.onProgress(progress, contentlength, true);
}
}
@Override
public void onProgress(Object o, Object o2) {
}
});
AppFramentUtil.tasks.postRunnable(fileDownThred);
}
} else {
progressListener.onError("");
}
}
});
下面在介绍下最底层的 写入文件的一些业务逻辑。
/**
* 断点多线程下载文件
*
* @param url
* @param downFilePath
* @param fileName 文件名字带后缀
* @param progressListener
*/
@Override
public void downFileMultithreadBreakpoint(final long startIndex, final long contentLength,
final String url, final String downFilePath, final String fileName,
final ProgressListener progressListener) {
Request request = new Request.Builder()
.addHeader("RANGE", "bytes=" + startIndex + "-" + contentLength)
.url(url).build();
String threadId = String.valueOf(Thread.currentThread().getId());
mFileClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败监听回调
progressListener.onError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String dataKey = fileName + startIndex;//多点下载保存本节点的key
long downSizi = 0;//已经下载的长度
boolean isOk = false;
InputStream inputStream = null;
byte[] buf = new byte[2048];
int len = 0;
RandomAccessFile randomAccessFile = null;
// 储存下载文件的目录
File dir = new File(downFilePath);
if (!dir.exists()) {
dir.mkdirs();
}
inputStream = response.body().byteStream();
long total = response.body().contentLength();
if (total <= 0) {
progressListener.onError("");
return;
}
File file = new File(dir, fileName);
if (!file.exists()) {
file.createNewFile();
}
randomAccessFile = new RandomAccessFile(file, "rw");
long seekStart = startIndex;
/**
* 本地文件已下载的大小
*/
long them = SharedPreferencesUtil.getSharedPreferencesUtil(mContext).getLong(dataKey);
AppFramentUtil.logCatUtil.e(TAG, "datakey:" + dataKey + " downSiz:" + them);
if (them == contentLength - startIndex) {
progressListener.onProgress(total, them, dataKey, threadId, isOk);
return;
} else {
seekStart = seekStart + them;
}
try {
randomAccessFile.seek(seekStart);//指定文件写入的位置 。例如上次下载了100个,这次送101开始
long sum = them;//将本地文件已经下载数量复制给临时变量吗,提供下载进度使用
inputStream.skip(them);//指定读取流读取的位置,例如上次数据保存了100,这次从101位置开始读取
while ((len = inputStream.read(buf)) != -1) {
randomAccessFile.write(buf, 0, len);
sum += len;
downSizi = sum;
progressListener.onProgress(total, sum, isOk);
progressListener.onProgress(total, sum, dataKey, threadId, isOk);
}
isOk = true;
progressListener.onProgress(total, sum, dataKey, threadId, isOk);
progressListener.onProgress(total, sum, isOk);
AppFramentUtil.logCatUtil.i(TAG, file.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
SharedPreferencesUtil.getSharedPreferencesUtil(mContext).putLong(dataKey, downSizi);
AppFramentUtil.logCatUtil.e(TAG, "datakey:" + dataKey + " downSiz:" + downSizi + "/n/r" + e.getStackTrace().toString());
} finally {
try {
if (inputStream != null)
inputStream.close();
} catch (IOException e) {
}
SharedPreferencesUtil.getSharedPreferencesUtil(mContext).putLong(dataKey, downSizi);
try {
if (randomAccessFile != null)
randomAccessFile.close();
} catch (IOException e) {
}
if (isOk) {
// 下载完成
progressListener.onOk(file.getAbsolutePath());
} else {
progressListener.onError("");
}
}
}
});
}