既然本节是学习如何使用多线程下载,那我们先要明白什么是多线程下载,在搞明白什么是多线程下载之前,需要先知道什么是单线程下载。
上图就是说明了单线程下载的原来,因此单线程下载速度很慢,因为只有一个任务在干活。
这样的话,3个线程下载一个文件,总比1个线程一个文件的速度要快。所以多线程下载数据的速度就快。
既然知道了多线程的下载原理,那我们就分析多个线程是如何下载数据,以及如何保存数据的。
知道多线程下载的原理,以及每个线程如何存放数据后,那就开始写代码。
1: 当然先要获取该数据的大小了,这样才知道给每个线程分配多大的下载量
我在服务器上下载一个exe文件名为:wireshark.exe
先从服务器上获取该文件的大小,并计算每个线程应该下载的大小区间
public void downloade(View v)
{
Thread thread = new Thread()
{
//服务器地址
String path = "http://192.168.1.123:8080/Wireshark.exe";
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() == 200)
{
//获取数据的总大小
int length = conn.getContentLength();
//每个线程的大小
int size = length / threadCount;
for(int i = 0; i < threadCount; i++)
{
int startIndex = i * size;
int endIndex = (i + 1)*size - 1;
//最后一个线程的结束地址为文件总大小-1
if(i == threadCount - 1)
{
endIndex = length - 1;
}
System.out.println("线程" + i + "的下载区间为:" + startIndex + "---" + endIndex);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
thread.start();
}
打印结果为:
可以看到大小是正确的。总的大小为29849552大小
2: 既然已经给每个线程分好了下载区间,那我们就开始下载
在下载开始时,先要在存储设备上分配一个个下载文件一样大小的临时文件,这样可以避免下载过程中出现存储不够。
System.out.println("线程" + i + "的下载区间为:" + startIndex + "---" + endIndex);
//开启threadCount去下载数据
new downloadThread(startIndex, endIndex, i).start();
class downloadThread extends Thread{
int startIndex;//开始位置
int endIndex;//结束位置
int threadId;//线程Id
//构造方法
public downloadThread(int startIndex, int endIndex, int threadId) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
@Override
public void run() {
//这次需要请求要下载的数据了
URL url;
try {
url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setReadTimeout(5000);
conn.setReadTimeout(5000);
//设置本次HTTP请求数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,返回码为206
if(conn.getResponseCode() == 206)
{
//此时取到的流里的数据只有上面给定区间的大小
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
int total = 0;
//再次打开临时文件
File file = new File(Environment.getExternalStorageDirectory(), filename);
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//把文件的写入位置指定到startindex
raf.seek(startIndex);
while((len = is.read(b)) != -1)
{
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了" + total);
}
System.out.println("线程" + threadId + "---------------下载完毕-------------------");
raf.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
从打印中可以看到是可以下载成功的。
3: 既然下载东西,对用户来说就的知道下载的进度。我们使用进度条显示现在的进度
设置最大进度
//获取数据的总大小
int length = conn.getContentLength();
//设置进度条的最大值
pBar.setMax(length);
//每个线程的大小
int size = length / threadCount;
这是当前的进度
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了" + total);
//设置当前进度,是3个线程的总和
currProgress += len;
pBar.setProgress(currProgress);
再设置文本显示,当前比例。要使用消息来更新UI
Handler handler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
//显示下载比例,转为为long型,int的时候有时候不够大
tView.setText((long)pBar.getProgress() * 100 / pBar.getMax() + "%");
};
};
效果图:
接下来实现断点续传:
File bakFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
try {
//判断文件是否存在
if(bakFile.exists())
{
FileInputStream fis = new FileInputStream(bakFile);
BufferedReader bReader = new BufferedReader(new InputStreamReader(fis));
//从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
int lastProgress = Integer.parseInt(bReader.readLine());
startIndex += lastProgress;
//把上次下载的进度显示至进度条
currProgress += lastProgress;
pBar.setProgress(currProgress);
//发送消息,让主线程刷新文本进度
handler.sendEmptyMessage(1);
fis.close();
}
在下载时候,先需要创建配置文件,防止下载过程中某些原因导致停止下载,当后续接着下载时,还是会用上次下载的地方接着下载
while((len = is.read(b)) != -1)
{
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了" + total);
//设置当前进度,是3个线程的总和
currProgress += len;
pBar.setProgress(currProgress);
//通过发送消息,更新文本。而进度条不需要通过发消息刷新UI,因为进度条本身就是在别的任务中使用的
handler.sendEmptyMessage(1);
//将当前的下载进度保存到配置文件中
RandomAccessFile bakRaFile = new RandomAccessFile(bakFile, "rwd");
bakRaFile.write((total + "").getBytes());
bakRaFile.close();
}
可以正常的支持断点连续下载
下载的文件可以正常运行,我将下载文件转为feiq了,因为wireshark有点大
http://download.csdn.net/detail/longwang155069/8991785
源码下载地址