1.多线程下载原理
多线程下载就是将同一个网络上的原始文件根据线程个数分成均等份,然后每个单独的线程下载对应的一部分,然后再将下载好的文件按照原始文件的顺序“拼接”起来就构成了完整的文件了。这样就大大提高了文件的下载效率。
2.多线程下载步骤
- 获取服务器上的目标文件的大小
- 在本地创建一个跟原始文件同样大小的文件
- 计算每个线程下载的起始位置和结束位置
- 记录下载进度
- 删除临时文件
/**
* 多线程下载器
*/
public class MultiDownloader {
/**
* 开启几个线程从服务器下载数据
*/
public static int threadCount = 3;
public static int runningThreadCount;
// 服务器的文件,准备出来,tomcat服务器上.
public static String path = "http://192.168.1.104:8080/setup.exe";
// 多线程下载
public static void main(String[] args) throws Exception {
// 1.请求服务器获取到服务器的资源大小
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
if (code == 200) {
int length = conn.getContentLength();
System.out.println("服务器文件的长度为:" + length);
// 2.在本地创建一个与服务器资源文件大小相同的资源
RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rw");
raf.setLength(length);
raf.close();
// 3.计算出每个线程的开始下载位置和结束位置
int blocksize = length / threadCount;
runningThreadCount = threadCount;
for (int threadId = 0; threadId < threadCount; threadId++) {
int startIndex = threadId * blocksize;
int endIndex = (threadId + 1) * blocksize - 1;
if (threadId == (threadCount - 1)) {
endIndex = length - 1;
}
// 开启多个子线程开始下载
new DownloadThread(threadId, startIndex, endIndex).start();
}
}
}
private static class DownloadThread extends Thread {
/**
* 线程id
*/
private int threadId;
/**
* 线程下载的理论开始位置
*/
private int startIndex;
/**
* 线程下载的结束位置
*/
private int endIndex;
/**
* 当前线程下载到文件的那一个位置了.
*/
private int currentPosition;
public DownloadThread(int threadId, int startIndex, int endIndex) {
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
System.out.println(threadId + "号线程下载的范围为:" + startIndex+ " ~~ " + endIndex);
currentPosition = startIndex;
}
@Override
public void run() {
try {
// 开始从网上下载自己需要下载的那部分资源
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
// 检查当前线程是否已经下载过一部分的数据了
File info = new File(threadId + ".position");
RandomAccessFile raf = new RandomAccessFile(getFileName(path),
"rw");
if (info.exists() && info.length() > 0) {
FileInputStream fis = new FileInputStream(info);
BufferedReader br = new BufferedReader(
new InputStreamReader(fis));
currentPosition = Integer.valueOf(br.readLine());
conn.setRequestProperty("Range", "bytes=" + currentPosition+ "-" + endIndex);
System.out.println("原来有下载进度,从上一次终止的位置继续下载" + "bytes="+ currentPosition + "-" + endIndex);
fis.close();
raf.seek(currentPosition);// 每个线程写文件的开始位置都是不一样的.
} else {
// 以前没有下载----告诉服务器 只想下载资源的一部分
conn.setRequestProperty("Range", "bytes=" + startIndex+ "-" + endIndex);
System.out.println("原来没有有下载进度,新的下载" + "bytes=" + startIndex+ "-" + endIndex);
raf.seek(startIndex);// 每个线程写文件的开始位置都是不一样的.
}
InputStream is = conn.getInputStream();
byte[] buffer = new byte[1];
int len = -1;
while ((len = is.read(buffer)) != -1) {
// 把每个线程下载的数据放在自己对应下载的文件里面.
// System.out.println("线程:"+threadId+"正在下载:"+new
// String(buffer));
raf.write(buffer, 0, len);
// 5.记录下载的进度
currentPosition += len;
File file = new File(threadId + ".position");
RandomAccessFile fos = new RandomAccessFile(file, "rwd");
// System.out.println("线程:"+threadId+"写到了"+currentPosition);
fos.write(String.valueOf(currentPosition).getBytes());
fos.close();// fileoutstream数据是不一定被写入到底层设备里面的,有可能是存储在缓存里面.
// raf 的 rwd模式,数据是立刻被存储到底层硬盘设备里面.
}
raf.close();
is.close();
System.out.println("线程:" + threadId + "下载完毕了...");
File f = new File(threadId + ".position");
f.renameTo(new File(threadId + ".position.finish"));
// 6.删除临时文件
synchronized (MultiDownloader.class) {
runningThreadCount--;
if (runningThreadCount <= 0) {
for (int i = 0; i < threadCount; i++) {
File ft = new File(i + ".position.finish");
ft.delete();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 获取一个文件名称
*
* @param path
* @return
*/
public static String getFileName(String path) {
int start = path.lastIndexOf("/") + 1;
return path.substring(start);
}
}
注意事项
* 多线程下载文件的请求属性
* 如果我们想请求服务器上某文件的一部分,而不是将整个文件都下载下来,那么就必须设置如下属性:connection.setRequestProperty(“Range”, “bytes=”+currentIndex+”-“+endIndex);
* 请求部分文件返回成功的状态码是206
* 当我们请求部分文件成功后,服务器返回的状态码不是200,而是206。
3.Android 实现多线程下载
1.ProgressBar
ProgressBar 是一个进度条控件,用于显示一项任务的完成进度。
有两种样式:
* 一种是圆形的该种样式是系统默认的,由于无法显示具体的进度值,适合用于不确定要等待多久的情形下
* 另一种是长条形的,,此类进度条有两种颜色,高亮颜色代表任务完成的总进度。对于我们下载任务来说,由于总任务(要下载的字节数)是明确的,当前已经完成的任务(已经下载的字节数)也是明确的,因此特别适合使用后者
ProgressBar.setMax(int max);//设置最大刻度值
ProgressBar.setProgress(int progress);//设置当前进度值
给ProgressBar 设置最大刻度值和修改进度值可以在子线程中操作的,其内部已经特殊处理过了,因此不需要再通过handler发送Message 让主线程修改进度。
2.添加权限
注意添加权限:网络访问、sdcard 存储权限。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>