您的位置:首页 > 其它

多线程断点续传下载

2016-05-27 18:27 218 查看
骚年,你是否为你写的下载模块的下载速度相比UC,豌豆荚,应用宝等慢太多而烦恼。本人刚工作时写的下载模块,期间优化来优化去,网上的相关多线程断点续传帖子也看了好多,但总是赶不上UC的下载速度!!! 最近,项目闲下来了,决定再次优化下载模块。finally!耶,媲美了UC的下载速度,杠杠的!

先讲大体实现思路,再讲其中各种导致下载速度上不去的坑。

原理:下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度。

1. 请求下载链接地址,获取getContentLength,也就是文件总大小。

public boolean initDownLoadFileSize() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(downLoadUrl)
.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.connect();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
fileSize = conn.getContentLength();
return true;
}
} catch (IOException e) {
}
return false;
}


2. 根据文件总大小,决定分几个子线程块去下载。

private void convertChunks(){
int MaximumUserCHUNKS = chunks/2;
chunks = 1;

for (int f=1 ; f <=MaximumUserCHUNKS ; f++)
if (fileSize > MegaByte*f)
chunks = f*2;

data = FileDB.getInstance().getFileDownChunkLog(this.package_name);
if (data.size() == chunks) {
for (int i = 0; i < chunks; i++) {
alreadyDownSize += data.get(i + 1);
}
} else {
data.clear();
for (int i = 0; i < chunks; i++) {
data.put(i + 1, 0);// 初始化每条线程已经下载的数据长度为0
}
FileDB.getInstance().deleteFileDownChunksLog(this.package_name);
FileDB.getInstance().saveFileDownChunkLog(this.package_name, data);
}

workList = new DownloadThread[chunks];

}
这一步骤核心:使用数据库维护各线程的下载进度,从而实现断点续传的功能。下载之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库

3. 根据分好的块开启子线程开始下载。

void startDownloadChunk(){
InputStream inStream = null;
RandomAccessFile threadfile = null;
try {
HttpURLConnection http = (HttpURLConnection) new URL(downUrl)
.openConnection();
http.setConnectTimeout( 10 * 1000 );
http.setReadTimeout( 10 * 1000 );
int startPos = block * (threadId - 1) + downLength;
int endPos;
if (threadId == downloader.getThreadNum()) {
endPos = downloader.getFileSize();
} else {
endPos = block * threadId - 1;
}
http.setRequestProperty("Range", "bytes=" + startPos + "-"
+ endPos);
inStream = http.getInputStream();
byte[] buffer = new byte[1024];
int offset;
threadfile = new RandomAccessFile(this.saveFile, "rw");
threadfile.seek(startPos);
while ( (offset = inStream.read(buffer)) > 0 ) {
threadfile.write(buffer, 0, offset);
downLength += offset;
downloader.append(offset);

if (isPause) {
downloader.Pause();
return;
}
}
downloader.checkDownloadFinish(this);
} catch (Exception e) {
LogUtil.i(e.toString());
if (tryTime < MAX_RETRY_TIMES) {
tryTime++;
startDownloadChunk();
}else {
downloader.Pause();
MyDownloadManager.getInstance().downFail(downloader, "下载失败,请重试",
true);
}
} finally {
downloader.update(threadId, downLength);
try {
if (threadfile != null)
threadfile.close();
if (inStream != null)
inStream.close();
} catch (IOException e) {
}
}
}


这一块核心主要是两个

一: 设置http请求的文件长度,http.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);

二:使用RandomAccessFile来实现多线程写入同一文件。

=======================================分割线=============================================

大体实现思路就是如上文所述,是不是感觉没几步,很简单? 0.0

但其实真正做起来还有很多细节,很多坑在。接下来,作为一个老司机,就谈谈我踩过的一些坑。

1.第一个问题就是分块,到底怎么分,分几个块合适。

博主最先开始从单线程下载改成多线程下载时,分的子线程都是2个...或者3个.... 改完后对比,差别真的不大....很尴尬,现在想想,当初

太小气了。优化后的分块逻辑文章上半部分已贴,500K一个子线程,16子线程上限。

2.第二个问题是RandomAccessFile的创建。

网上帖子大多数是这么创建的:new RandomAccessFile(file, "rws");

rws模式下文件的写入是同步的,不是异步的,会大大降低多线程写入文件的速度哦。

所以,正确的姿势是这样子的: new RandomAccessFile(file, "rw");

3.第三个问题是各子线程下载进度的保存问题。

有些同学为了实现断点续传这个功能,跟博主年轻的时候一样,想当然的在写入文件时更新数据库。navie

频繁读写数据库同样也是降低下载速度的因素。

优雅的做法是在异常时才去更新保存进度。或者设置一个阀值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: