既然本节是学习如何使用多线程下载,那我们先要明白什么是多线程下载,在搞明白什么是多线程下载之前,需要先知道什么是单线程下载。
上图就是说明了单线程下载的原来,因此单线程下载速度很慢,因为只有一个任务在干活。
这样的话,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
源码下载地址
版权声明:本文为博主原创文章,未经博主允许不得转载。
时间: 2024-11-10 08:22:56