PC版与Android手机版带断点续传的多线程下载
一、多线程下载
多线程下载就是抢占服务器资源
原理:服务器CPU分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源。
1、设置开启线程数,发送http请求到下载地址,获取下载文件的总长度
然后创建一个长度一致的临时文件,避免下载到一半存储空间不够了,并计算每个线程下载多少数据
2、计算每个线程下载数据的开始和结束位置
再次发送请求,用Range头请求开始位置和结束位置的数据
3、将下载到的数据,存放至临时文件中
4、带断点续传的多线程下载
定义一个int变量,记录每条线程下载的数据总长度,然后加上该线程的下载开始位置,得到的结果就是下次下载时,该线程的开始位置,把得到的结果存入缓存文件,当文件下载完成,删除临时进度文件。
publicclassMultiDownload{
staticintThreadCount=;
staticintfinishedThread=;
//确定下载地址
staticStringfilename="EditPlus.exe";
staticStringpath="http://...:/"+filename;
publicstaticvoidmain(String[]args){
//、发送get请求,去获得下载文件的长度
try{
URLurl=newURL(path);
HttpURLConnectionconn=(HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout();
conn.setReadTimeout();
if(conn.getResponseCode()==){
//如果请求成功,拿到所请求资源文件的长度
intlength=conn.getContentLength();
//、生成一个与原文件同样的大小的临时文件,以免下载一半存储空间不够了
Filefile=newFile(filename);//演示,所以将保存的文件目录放在工程的同目录
//使用RandomAccessFile生成临时文件,可以用指针定位文件的任意位置,
//而且能够实时写到硬件底层设备,略过缓存,这对下载文件是突然断电等意外是有好处的
RandomAccessFileraf=newRandomAccessFile(file,"rwd");//rwd,实时写到底层设备
//设置临时文件的大小
raf.setLength(length);
raf.close();
//、计算出每个线程应该下载多少个字节
intsize=length/ThreadCount;//如果有余数,负责最后一部分的线程负责下砸
//开启多线程
for(intthreadId=;threadId<ThreadCount;threadId++){
//计算每个线程下载的开始位置和结束位置
intstartIndex=threadId*size;//开始=线程id*size
intendIndex=(threadId+)*size-;//结束=(线程id+)*size-
//如果是最后一个线程,那么结束位置写死为文件结束位置
if(threadId==ThreadCount-){
endIndex=length-;
}
//System.out.println("线程"+threadId+"的下载区间是:"+startIndex+"----"+endIndex);
newDownloadThread(startIndex,endIndex,threadId).start();
}
}
}catch(MalformedURLExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
classDownloadThreadextendsThread{
privateintstartIndex;
privateintendIndex;
privateintthreadId;
publicDownloadThread(intstartIndex,intendIndex,intthreadId){
super();
this.startIndex=startIndex;
this.endIndex=endIndex;
this.threadId=threadId;
}
publicvoidrun(){
//每个线程再次发送http请求,下载自己对应的那部分数据
try{
FileprogressFile=newFile(threadId+".txt");
//判断进度文件是否存在,如果存在,则接着断点继续下载,如果不存在,则从头下载
if(progressFile.exists()){
FileInputStreamfis=newFileInputStream(progressFile);
BufferedReaderbr=newBufferedReader(newInputStreamReader(fis));
//从进度文件中度取出上一次下载的总进度,然后与原本的开始进度相加,得到新的开始进度
startIndex+=Integer.parseInt(br.readLine());
fis.close();
}
System.out.println("线程"+threadId+"的下载区间是:"+startIndex+"----"+endIndex);
//、每个线程发送http请求自己的数据
URLurl=newURL(MultiDownload.path);
HttpURLConnectionconn=(HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout();
conn.setReadTimeout();
//设置本次http请求所请求的数据的区间
conn.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);
//请求部分数据,响应码是
if(conn.getResponseCode()==){
//此时,流里只有ThreadCount分之一的原文件数据
InputStreamis=conn.getInputStream();
byte[]b=newbyte[];
intlen=;
inttotal=;//total用于保存断点续传的断点
//拿到临时文件的输出流
Filefile=newFile(MultiDownload.filename);
RandomAccessFileraf=newRandomAccessFile(file,"rwd");
//把文件的写入位置移动至startIndex
raf.seek(startIndex);
while((len=is.read(b))!=-){
//每次读取流里数据之后,同步把数据写入临时文件
raf.write(b,,len);
total+=len;
//System.out.println("线程"+threadId+"下载了"+total);
//生成一个一个专门用来记录下载进度的临时文件
RandomAccessFileprogressRaf=newRandomAccessFile(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(inti=;i<MultiDownload.ThreadCount;i++){
Filef=newFile(i+".txt");
f.delete();
}
MultiDownload.finishedThread=;
}
}
}
}catch(Exceptione){
e.printStackTrace();
}
}
}
二、Android手机版带断点续传的多线程下载
Android手机版的带断点续传的多线程下载逻辑与PC版的几乎一样,只不过在Android手机中耗时操作不能放在主线程,网络下载属于耗时操作,所以多线程下载要在Android中开启一个子线程执行。并使用消息队列机制刷新文本进度条。
publicclassMainActivityextendsActivity{
staticintThreadCount=;
staticintFinishedThread=;
intcurrentProgess;
staticStringFilename="QQPlayer.exe";
staticStringPath="http://...:/"+Filename;
staticMainActivityma;
staticProgressBarpb;
staticTextViewtv;
staticHandlerhandler=newHandler(){
publicvoidhandleMessage(android.os.Messagemsg){
tv.setText((long)pb.getProgress()*/pb.getMax()+"%");
};
};
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ma=this;
pb=(ProgressBar)findViewById(R.id.pb);
tv=(TextView)findViewById(R.id.tv);
}
publicvoiddownload(Viewv){
Threadt=newThread(){
publicvoidrun(){
//发送http请求获取文件的长度,创建临时文件
try{
URLurl=newURL(Path);
HttpURLConnectionconn=(HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout();
conn.setReadTimeout();
if(conn.getResponseCode()==){
intlength=conn.getContentLength();
//设置进度条的最大值就是原文件的总长度
pb.setMax(length);
//生成一个与原文件相同大小的临时文件
Filefile=newFile(Environment.getExternalStorageDirectory(),Filename);
RandomAccessFileraf=newRandomAccessFile(file,"rwd");
raf.setLength(length);
raf.close();
//计算每个线程需要下载的数据大小
intsize=length/ThreadCount;
//开启多线程
for(intthreadId=;threadId<ThreadCount;threadId++){
intstartIndex=threadId*size;
intendIndex=(threadId+)*size-;
if(threadId==ThreadCount-){
endIndex=length-;
}
newDownloadThread(startIndex,endIndex,threadId).start();
}
}
}catch(Exceptione){
e.printStackTrace();
}
}
};
t.start();
}
classDownloadThreadextendsThread{
privateintstartIndex;
privateintendIndex;
privateintthreadId;
publicDownloadThread(intstartIndex,intendIndex,intthreadId){
super();
this.startIndex=startIndex;
this.endIndex=endIndex;
this.threadId=threadId;
}
publicvoidrun(){
//每个线程发送http请求自己的数据
try{
//先判断是不是断点续传
FileprogessFile=newFile(Environment.getExternalStorageDirectory(),threadId+".txt");
if(progessFile.exists()){
FileReaderfr=newFileReader(progessFile);
BufferedReaderbr=newBufferedReader(fr);
intlastProgess=Integer.parseInt(br.readLine());
startIndex+=lastProgess;
//把上次下载的进度显示至进度条
currentProgess+=lastProgess;
pb.setProgress(currentProgess);
//发消息,让主线程刷新文本进度
handler.sendEmptyMessage();
br.close();
fr.close();
}
URLurl=newURL(Path);
HttpURLConnectionconn=(HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout();
conn.setReadTimeout();
conn.setRequestProperty("Range","bytes="+startIndex+"-"+endIndex);
if(conn.getResponseCode()==){
InputStreamis=conn.getInputStream();
byte[]buffer=newbyte[];
intlen=;
inttotal=;
Filefile=newFile(Environment.getExternalStorageDirectory(),Filename);
RandomAccessFileraf=newRandomAccessFile(file,"rwd");
raf.seek(startIndex);
while((len=is.read(buffer))!=-){
raf.write(buffer,,len);
total+=len;
//每次读取流里数据之后,把本次读取的数据的长度显示至进度条
currentProgess+=len;
pb.setProgress(currentProgess);
//发消息,让主线程刷新文本进度
handler.sendEmptyMessage();
//生成临时文件保存下载进度,用于断点续传,在所有线程现在完毕后删除临时文件
RandomAccessFileprogressRaf=newRandomAccessFile(progessFile,"rwd");
progressRaf.write((total+"").getBytes());
progressRaf.close();
}
raf.close();
System.out.println("线程"+threadId+"下载完了");
//当所有线程都下在完了之后,删除临时进度文件
FinishedThread++;
synchronized(Path){
if(FinishedThread==ThreadCount){
for(inti=;i<ThreadCount;i++){
Filef=newFile(Environment.getExternalStorageDirectory(),i+".txt");
f.delete();
}
FinishedThread=;
}
}
}
}catch(Exceptione){
e.printStackTrace();
}
}
}
}
以上内容是小编跟大家分享的PC版与Android手机版带断点续传的多线程下载,希望大家喜欢。