一、多线程下载的原理图解
二、javaee代码实现
public class MultiDownload {
static int ThreadCount = 3;
static int finishedThread = 0;
// 确定下载地址
static String path = "http://192.168.33.28:8080/GeePlayer_2.3.28.2726_Setup_baidu.exe";
public static void main(String[] args) {
// 发送get请求,请求这个地址的资源
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if (conn.getResponseCode() == 200) {
// 拿到所请求资源文件的长度
int length = conn.getContentLength();
File file = new File("QQPlayer.exe");
// 生成临时文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
// 设置临时文件的大小
raf.setLength(length);
raf.close();
// 计算出每个线程应该下载多少字节
int size = length / ThreadCount;
for (int i = 0; i < ThreadCount; i++) {
// 计算线程下载的开始位置和结束位置
int startIndex = i * size;
int endIndex = (i + 1) * size - 1;
// 如果是最后一个线程,那么结束位置写死
if (i == ThreadCount - 1) {
endIndex = length - 1;
}
new DownLoadThread(startIndex, endIndex, i).start();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这里我们创建了一个MultiDownload,通过HttpURLConnection框架请求资源。获取到文件的长度之后,我们首先生成了一个临时文件,用来确保用户的内存是可以放下我们请求的资源的。
然后计算出每个子线程下载的开始位置和结束位置。
class DownloadThread extends Thread{
int threadId;
int start;
int end;
public DownloadThread(int threadId, int start, int end) {
super();
this.threadId = threadId;
this.start = start;
this.end = end;
}
@Override
public void run() {
try{
System.out.println("线程"+threadId+"的下载区间是:"+start+"------"+end);
URL url = new URL(MultiDownload.path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setRequestProperty("Range", "bytes= "+start+"-"+end);//这一句话十分重要,只有设置了该属性才能下载指定范围的文件
if(conn.getResponseCode()==
206){ //这里是因为请求的是文件的一部分,所以返回码是206
InputStream inputStream = conn.getInputStream();
File file = new File("GeePlayer_2.3.28.2726_Setup_baidu.exe");
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.seek(start); //将文件指针定位到要写的位置
byte[] b = new byte[1024];
int len;
while((len=inputStream.read(b))!=-1){
raf.write(b,0,len);
}
raf.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
这里我们定义了一个外部类,用来下载。同样的首先获取一个HttpURLConnection对象,然后设置一些基本的属性后我们又给它设置了一个非常重要的属性setRequestProperty(),传入两个参数,
表示下载的字节范围。然后当返回码为206的时候我们将文件写到工程的根目录下。这样我们的多线程下载就在Javaee上实现了。
但是这个时候我们的程序功能还是太简陋了,现在市面上的下载软件都是支持断点续传的,接下来,我们也给我们的程序加上断点续传的功能。
@Override
public void run() {
// 再次发送http请求,下载原文件
try {
File progressFile = new File(threadId + ".txt");
// 判断进度临时文件是否存在
if (progressFile.exists()) {
FileInputStream fis = new FileInputStream(progressFile);
BufferedReader br = new BufferedReader(new InputStreamReader(
fis));
// 从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
startIndex += Integer.parseInt(br.readLine());
fis.close();
}
System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---"
+ endIndex);
HttpURLConnection conn;
URL url = new URL(MultiDownload.path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
// 设置本次http请求所请求的数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
+ endIndex);
// 请求部分数据,相应码是206
if (conn.getResponseCode() == 206) {
// 流里此时只有1/3原文件的数据
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
int total = 0;
// 拿到临时文件的输出流
File file = new File("GeePlayer_2.3.28.2726_Setup_baidu.exe");
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);
// 生成一个专门用来记录下载进度的临时文件
RandomAccessFile progressRaf = new RandomAccessFile(
progressFile, "rwd");
// 每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
progressRaf.write((total + "").getBytes());
progressRaf.close();
}
System.out.println("线程" + threadId
+ "下载完毕");
raf.close();
MultiDownload.finishedThread++;
synchronized (MultiDownload.path) {
if (MultiDownload.finishedThread == MultiDownload.ThreadCount) {
for (int i = 0; i < MultiDownload.ThreadCount; i++) {
File f = new File(i + ".txt");
f.delete();
}
MultiDownload.finishedThread = 0;
}
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
这里我们自定义了一个total变量,用来存储每个子线程当前已经下载了多少字节,然后通过RandomAccessFile写入到本地临时文件,文件命名就是threadId.txt
然后我们又定义了一个静态常量finishedThread=0,当我们每个子线程执行完毕的时候就+1,当finishedThread=总共的子线程数目时,就代表所有的子线程
下载完毕,我们就可以将所有记录子线程下载字节个数的文件给删除掉了。然后将finishedThread重新置为0。 最后在线程开始的时候判断一下,如果有进度
临时文件存在,那么我们就读出上一次下载的总进度,然后与原本的开始位置相加,得出新的开始位置,这就实现了断点续传的功能。
三、将代码移植到安卓中
界面就是当我们点击按钮的时候,我们开始下载我们存在本地服务器上的GeePlayer_2.3.28.2726_Setup_baidu.exe到SD卡
所以我们首先要配置两个权限 1.联网权限 2.写SD卡权限
然后在Button的点击事件中,我们创建一个新的子线程用来请求网络,还有其他的一些细小的修改,在此就不再赘述。