多线程下载文件的过程是:
(1)首先获得下载文件的长度,然后设置本地文件的长度。
HttpURLConnection.getContentLength();//获取下载文件的长度
RandomAccessFile file = new RandomAccessFile("QQSetup.exe","rwd");
file.setLength(filesize);//设置本地文件的长度
(2)依据文件长度和线程数计算每条线程下载的数据长度和下载位置。
如:文件的长度为6M,线程数为3,那么。每条线程下载的数据长度为2M,每条线程開始下载的位置例如以下图所看到的。
比如10M大小,使用3个线程来下载,
线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M
下载開始位置:线程id*每条线程下载的数据长度 = ?
下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?
(3)使用Http的Range头字段指定每条线程从文件的什么位置開始下载,下载到什么位置为止,
如:指定从文件的2M位置開始下载。下载到位置(4M-1byte)为止
代码例如以下:HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
(4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置開始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置開始写入数据
/**
*多线程下载,UI更新类
*@author young
* */
public class MultiThreadDownload extends Thread{
private static final String TAG = "MultiThreadDownload";
/**每个线程须要下载的大小 */
private int blockSize;
/*** 线程数量<br> 默觉得5个线程下载*/
private int threadNum = 5;
/*** 文件大小 */
private int fileSize;
/** * 已经下载多少 */
private int downloadSize;
/**文件的url,线程编号。文件名*/
private String UrlStr,ThreadNo,fileName;
/***保存的路径*/
private String savePath;
/**下载的百分比*/
private int downloadPercent = 0;
/**下载的 平均速度*/
private int downloadSpeed = 0;
/**下载用的时间*/
private int usedTime = 0;
/**当前时间*/
private long curTime;
/**是否已经下载完毕*/
private boolean completed = false;
private Handler handler ;
/**
* 下载的构造函数
* @param url 请求下载的URL
* @param handler UI更新使用
* @param savePath 保存文件的路径
*/
public MultiThreadDownload(Handler handler,String url,String savePath)
{
this.handler = handler;
this.UrlStr = url;
this.savePath = savePath;
Log.e(TAG, toString());
}
@Override
public void run() {
FileDownloadThread[] fds = new FileDownloadThread[threadNum];//设置线程数量
try {
URL url = new URL(UrlStr);
URLConnection conn = url.openConnection();
fileSize = conn.getContentLength();
this.fileName = FileUtil.getFileName(UrlStr);
//仅仅创建一个文件,saveFile下载内容
File saveFile = new File(savePath+"/"+fileName);
Log.e(TAG, "文件一共:"+fileSize+" savePath "+savePath+" fileName "+fileName);
RandomAccessFile accessFile = new RandomAccessFile(saveFile,"rwd");
//设置本地文件的长度和下载文件同样
accessFile.setLength(fileSize);
accessFile.close();
//Handler更新UI,发送消息
sendMsg(FileUtil.startDownloadMeg);
//每块线程下载数据
blockSize = ((fileSize%threadNum)==0)?(fileSize/threadNum):(fileSize/threadNum+1);
Log.e(TAG, "每个线程分别下载 :"+blockSize);
for (int i = 0; i < threadNum; i++) {
int curThreadEndPosition = (i+1)!=threadNum ?
((i+1)*blockSize-1) : fileSize; FileDownloadThread fdt = new FileDownloadThread(url, saveFile, i*blockSize, curThreadEndPosition); fdt.setName("thread"+i); fdt.start(); fds[i]=fdt; } /** * 获取数据,更新UI,直到全部下载线程都下载完毕。 */ boolean finished = false; //開始时间,放在循环外,求解的usedTime就是总时间 long startTime = System.currentTimeMillis(); while(!finished) { downloadSize = 0; finished = true; for (int i = 0; i < fds.length; i++) { downloadSize+= fds[i].getDownloadSize(); if(!fds[i].isFinished()) { finished = false; } } downloadPercent = (downloadSize*100)/fileSize; curTime = System.currentTimeMillis(); System.out.println("curTime = "+curTime+" downloadSize = "+downloadSize+" usedTime "+(int) ((curTime-startTime)/1000)); usedTime = (int) ((curTime-startTime)/1000); if(usedTime==0)usedTime = 1; downloadSpeed = (downloadSize/usedTime)/1024; sleep(1000);/*1秒钟刷新一次界面*/ sendMsg(FileUtil.updateDownloadMeg); } Log.e(TAG, "下载完毕"); completed = true; sendMsg(FileUtil.endDownloadMeg); } catch (Exception e) { Log.e(TAG, "multi file error Exception "+e.getMessage()); e.printStackTrace(); } super.run(); } /** * 得到文件的大小 * @return */ public int getFileSize() { return this.fileSize; } /** * 得到已经下载的数量 * @return */ public int getDownloadSize() { return this.downloadSize; } /** * 获取下载百分比 * @return */ public int getDownloadPercent(){ return this.downloadPercent; } /** * 获取下载速度 * @return */ public int getDownloadSpeed(){ return this.downloadSpeed; } /** * 改动默认线程数 * @param threadNum */ public void setThreadNum(int threadNum){ this.threadNum = threadNum; } /** * 分块下载完毕的标志 * @return */ public boolean isCompleted(){ return this.completed; } @Override public String toString() { return "MultiThreadDownload [threadNum=" + threadNum + ", fileSize=" + fileSize + ", UrlStr=" + UrlStr + ", ThreadNo=" + ThreadNo + ", savePath=" + savePath + "]"; } /** * 发送消息。用户提示 * */ private void sendMsg(int what) { Message msg = new Message(); msg.what = what; handler.sendMessage(msg); }
public class FileDownloadThread extends Thread{
private static final String TAG = "FileDownloadThread";
/**缓冲区 */
private static final int BUFF_SIZE = 1024;
/**须要下载的URL*/
private URL url;
/**缓存的FIle*/
private File file;
/**開始位置*/
private int startPosition;
/**结束位置*/
private int endPosition;
/**当前位置*/
private int curPosition;
/**完毕*/
private boolean finished = false;
/**已经下载多少*/
private int downloadSize = 0;
/***
* 分块文件下载,能够创建多线程模式
* @param url 下载的URL
* @param file 下载的文件
* @param startPosition 開始位置
* @param endPosition 结束位置
*/
public FileDownloadThread(URL url, File file, int startPosition,
int endPosition) {
this.url = url;
this.file = file;
this.startPosition = startPosition;
this.curPosition = startPosition;
this.endPosition = endPosition;
Log.e(TAG, toString());
}
@Override
public void run() {
BufferedInputStream bis = null;
RandomAccessFile rAccessFile = null;
byte[] buf = new byte[BUFF_SIZE];
URLConnection conn = null;
try {
conn = url.openConnection();
conn.setConnectTimeout(10000);//设置超时
conn.setReadTimeout(10000);
conn.setAllowUserInteraction(true);
System.out.println(this.getName()+" startPosition "+startPosition+" endPosition "+endPosition);
conn.setRequestProperty("Range", "bytes="+(startPosition)+"-"+endPosition); //取剩余未下载的
rAccessFile = new RandomAccessFile(file,"rwd");//读写
//设置从什么位置開始写入数据
rAccessFile.seek(startPosition);
bis = new BufferedInputStream(conn.getInputStream(), BUFF_SIZE);
while(curPosition<endPosition) //当前位置小于结束位置 继续下载
{
int len = bis.read(buf,0,BUFF_SIZE);
if(len==-1) //下载完毕
{
break;
}
rAccessFile.write(buf,0,len);
curPosition = curPosition +len;
if(curPosition > endPosition)
{ //假设下载多了。则减去多余部分
System.out.println(" curPosition > endPosition !!!!");
int extraLen = curPosition-endPosition;
downloadSize += (len-extraLen+1);
}else{
downloadSize+=len;
}
}
this.finished = true; //当前阶段下载完毕
Log.e(TAG, "当前"+this.getName()+"下载完毕");
} catch (Exception e) {
Log.e(TAG, "download error Exception "+e.getMessage());
e.printStackTrace();
}finally{
//关闭流
FileUtil.closeInputStream(bis);
try {
rAccessFile.close();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("AccessFile", "AccessFile IOException "+e.getMessage());
}
}
super.run();
}
/**
* 是否完毕当前段下载完毕
* @return
*/
public boolean isFinished() {
return finished;
}
/**
* 已经下载多少
* @return
*/
public int getDownloadSize() {
return downloadSize;
}
@Override
public String toString() {
return "FileDownloadThread [url=" + url + ", file=" + file
+ ", startPosition=" + startPosition + ", endPosition="
+ endPosition + ", curPosition=" + curPosition + ", finished="
+ finished + ", downloadSize=" + downloadSize + "]";
}
多线程下载是分段下载,创建保存一个文件,子线程分别通过RandomAccessFile类进行写入操作。
演示样例源代码:demo下载