HTTP多线程断点下载

原理:获取目标文件的大小,在本地创建一个相同大小的文件,并计算每个线程需要下载的起始位置及大小,然后分配至每个线程独立下载,全部下载完毕则自动合并.

实现步骤

1. 查看并计算目标文件的大小

1
2
3
4
5
6
7
8
URL url = new URL(mPath);
HttpURLConnection connection = (HttpURLConnection)
url.openConnection();
connection.setRequestMethod("GET");
int code = connection.getResponseCode();
if (code == 200) {
int length = connection.getContentLength();
System.out.println("文件总长度为:" + length);

2. 设置目标文件在本地的映射

1
2
3
4
RandomAccessFile raf = new RandomAccessFile(
Environment.getExternalStorageDirectory().
getAbsolutePath()+"/"+getDownlaodName(mPath), "rw");
raf.setLength(length);

3. 开启子线程并分配下载任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int blokeSize = length / mTotalCount;
System.out.println("每一块大小为:" + blokeSize);
runningThreadCount = mTotalCount;
for (int threadid = 0; threadid < mTotalCount; threadid++) {
int startPosition = threadid * blokeSize;
int endPosition = (threadid + 1) * blokeSize - 1;
//最后一个线程应该下载至末尾
if (threadid == mTotalCount - 1) {
endPosition = length - 1;
}
System.out.println("线程编号:" + threadid + ""
+ ",下载范围:" + startPosition + "~~" + endPosition);
//开启下载子线程
new DownloadThread(threadid, startPosition,
endPosition).start();

4. 子线程中的具体逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public void run() {
System.out.println("线程"+threadid+"开始运行了");
try{
//读存有历史下载进度的文件,判断是否已经下载过
File finfo=new File(Environment
.getExternalStorageDirectory()
.getAbsolutePath() +"/"+mTotalCount
+ getDownlaodName(mPath)+threadid+".txt");
if (finfo.exists()&&finfo.length()>0) {
//获取文件输入流
FileInputStream fis=new FileInputStream(finfo);
//读取缓冲
BufferedReader br=new BufferedReader(
new InputStreamReader(fis));
//得到文件内容
String lasrposition=br.readLine();
//转化为int值
int intlastposition=Integer.parseInt(lasrposition);
lastLoadSize=intlastposition-startPosition;
startPosition=intlastposition;
fis.close();
}
URL url=new URL(mPath);
HttpURLConnection conn=(HttpURLConnection)
url.openConnection();
conn.setRequestMethod("GET");
System.out.println("线程实际下载:"+threadid+",范围:"+startPosition
+"~~"+endPosition);
//设置http请求头部参数
conn.setRequestProperty("Range", "bytes="
+startPosition+"-"+endPosition);
int code=conn.getResponseCode();
//206代表请求部分数据成功
if (code==206) {
//拿到链接返回的输入流
InputStream is=conn.getInputStream();
//指向要写的文件(abc.exe)
RandomAccessFile raf=new RandomAccessFile(
Environment.getExternalStorageDirectory().getAbsolutePath()
+"/"+getDownlaodName(mPath), "rw");
//指定文件开始写的位置
raf.seek(startPosition);
//下载缓冲区,越大下载越快,对硬盘损耗越小,但是越容易丢失数据
byte[] buffer=new byte[1024*1024];
int len=-1;
int total=0;//当前线程本次下载数据总量
while ((len=is.read(buffer))!=-1) {
raf.write(buffer,0,len);
total+=len;
//将每次更新的数据同步到底层硬盘
RandomAccessFile inforaf=new RandomAccessFile(
Environment.getExternalStorageDirectory().getAbsolutePath()
+"/"+mTotalCount +getDownlaodName(mPath)+threadid+".txt","rwd");
//保存当前线程下载到什么位置
inforaf.write(String.valueOf(startPosition+total).getBytes());
inforaf.close();
}
is.close();
raf.close();
System.out.println("线程"+threadid+"下载完毕");

}
}catch(Exception e){
e.printStackTrace();
}finally{
//同步代码块,保证同时间仅有一个线程执行此区块代码
synchronized (MainActivity.class) {
runningThreadCount--;
if (runningThreadCount<=0) {
System.out.println("多线程下载完毕");
for (int i = 0; i <mTotalCount ; i++) {
File f=new File(Environment.getExternalStorageDirectory()
.getAbsolutePath()
+"/"+mTotalCount
+getDownlaodName(mPath)
+i+".txt");
System.out.println(f.delete());
endTime=System.currentTimeMillis();
}
System.out.println("结束时间:"+endTime);
System.out.println("总共花费:"
+(endTime-startTime)/1000+"秒");
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(),
"总共花费:"+(endTime-startTime)/1000+"秒",
Toast.LENGTH_SHORT).show();
}
});
}
}
}
}

备注:如果不使用断点下载,只需要将判断和存储历史下载信息的逻辑删除即可。