【0042】Android基础-30-JavaSE多线程下载(重要)
【概述】
首先使用JavaSE书写一个多线程下载的实例,然后将该实例移植到Android中;
【1】步骤

【2】获取要下载的文件的大小



【3】设置随机文件的大小
在本地磁盘新建一个与要下载的文件大小相同的空文件;






【4】要分配每个线程下载文件的开始位置和结束位置


【5】开启线程去执行下载
通过UrlConnection下载部分资源。
注意:
1.需要Range头,key:Range value:bytes:0-499
urlconnection.setRequestPropety("Range","bytes:0-499")
2.需要设置每个线程在本地文件的保存的开始位置
RandomAccessFile randomfile =new RandomAccessFile(File file,String mode)
randomfile.seek(int startPostion);//本次线程下载保存的开始位置。
1 public static class DownloadThread extends Thread{ 2 3 4 private int threadId; 5 private int startIndex; 6 private int endIndex; 7 private int lastPostion; 8 public DownloadThread(int threadId,int startIndex,int endIndex){ 9 this.threadId = threadId; 10 this.startIndex = startIndex; 11 this.endIndex = endIndex; 12 } 13 14 @Override 15 public void run() { 16 17 //分段请求网络连接,分段保存文件到本地 18 try{ 19 URL url = new URL(path); 20 HttpURLConnection openConnection = (HttpURLConnection) url.openConnection(); 21 openConnection.setRequestMethod("GET"); 22 openConnection.setConnectTimeout(10*1000); 23 24 System.out.println("理论上下载: 线程:"+threadId+",开始位置:"+startIndex+";结束位置:"+endIndex); 25 26 27 lastPostion = startIndex; 28 //设置分段下载的头信息。 Range:做分段数据请求用的。 29 openConnection.setRequestProperty("Range", "bytes:"+lastPostion+"-"+endIndex);//bytes:0-500:请求服务器资源中0-500之间的字节信息 501-1000: 30 System.out.println("实际下载: 线程:"+threadId+",开始位置:"+lastPostion+";结束位置:"+endIndex); 31 32 if(openConnection.getResponseCode() == 206){//200:请求全部资源成功, 206代表部分资源请求成功 33 InputStream inputStream = openConnection.getInputStream(); 34 //请求成功将流写入本地文件中,已经创建的占位那个文件中 35 36 RandomAccessFile randomAccessFile = new RandomAccessFile(new File(getFileName(path)), "rw"); 37 randomAccessFile.seek(lastPostion);//设置随机文件从哪个位置开始写。 38 //将流中的数据写入文件 39 byte[] buffer = new byte[1024]; 40 int length = -1; 41 int total = 0;//记录本次线程下载的总大小 42 43 while((length= inputStream.read(buffer)) !=-1){ 44 randomAccessFile.write(buffer, 0, length); 45 //创建随机文件保存当前线程下载的位置 46 File file = new File(threadId+".txt"); 47 RandomAccessFile accessfile = new RandomAccessFile(file, "rwd"); 48 accessfile.write(String.valueOf(currentThreadPostion).getBytes()); 49 accessfile.close(); 50 51 } 52 //关闭相关的流信息 53 inputStream.close(); 54 randomAccessFile.close(); 55 56 System.out.println("线程:"+threadId+",下载完毕"); 57 58 //当所有线程下载结束,删除存放下载位置的文件。 59 synchronized (DownloadThread.class) { 60 runningTrheadCount = runningTrheadCount -1;//标志着一个线程下载结束。 61 if(runningTrheadCount == 0 ){ 62 System.out.println("所有线程下载完成"); 63 for(int i =0 ;i< threadCount;i++){ 64 File file = new File(i+".txt"); 65 System.out.println(file.getAbsolutePath()); 66 file.delete(); 67 } 68 } 69 } 70 71 } 72 73 }catch (Exception e) { 74 e.printStackTrace(); 75 } 76 super.run(); 77 } 78 79 } 80 }
【6】断点续传的功能:
【6.1】断点位置的保存


【6.2】断点信息在下次开始下载时读取

【关键】计算好每次开始断点下载的位置与下载开始下载的位置要衔接好;
【7】注意-每次下载完成之后需要删除原来的显示的进度的文件

【8】源码
1 package com.itheima.muchthreaddown; 2 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java.io.InputStream; 8 import java.io.InputStreamReader; 9 import java.io.RandomAccessFile; 10 import java.net.HttpURLConnection; 11 import java.net.URL; 12 import java.net.URLConnection; 13 14 public class MuchThreadDown { 15 16 private static int threadCount = 3;// 开启3个线程 17 private static int blockSize = 0;// 每个线程下载的大小 18 private static int runningTrheadCount = 0;// 当前运行的线程数 19 private static String path = "http://192.168.13.83:8080/itheima74/feiq.exe"; 20 21 /** 22 * @param args 23 */ 24 public static void main(String[] args) { 25 26 try { 27 // 1.请求url地址获取服务端资源的大小 28 URL url = new URL(path); 29 HttpURLConnection openConnection = (HttpURLConnection) url 30 .openConnection(); 31 openConnection.setRequestMethod("GET"); 32 openConnection.setConnectTimeout(10 * 1000); 33 34 int code = openConnection.getResponseCode(); 35 if (code == 200) { 36 // 获取资源的大小 37 int filelength = openConnection.getContentLength(); 38 // 2.在本地创建一个与服务端资源同样大小的一个文件(占位) 39 RandomAccessFile randomAccessFile = new RandomAccessFile( 40 new File(getFileName(path)), "rw"); 41 randomAccessFile.setLength(filelength);// 设置随机访问文件的大小 42 43 // 3.要分配每个线程下载文件的开始位置和结束位置。 44 blockSize = filelength / threadCount;// 计算出每个线程理论下载大小 45 for (int threadId = 0; threadId < threadCount; threadId++) { 46 int startIndex = threadId * blockSize;// 计算每个线程下载的开始位置 47 int endIndex = (threadId + 1) * blockSize - 1;// 计算每个线程下载的结束位置 48 // 如果是最后一个线程,结束位置需要单独计算 49 if (threadId == threadCount - 1) { 50 endIndex = filelength - 1; 51 } 52 53 // 4.开启线程去执行下载 54 new DownloadThread(threadId, startIndex, endIndex).start(); 55 56 } 57 58 } 59 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 64 } 65 66 public static class DownloadThread extends Thread { 67 68 private int threadId; 69 private int startIndex; 70 private int endIndex; 71 private int lastPostion; 72 73 public DownloadThread(int threadId, int startIndex, int endIndex) { 74 this.threadId = threadId; 75 this.startIndex = startIndex; 76 this.endIndex = endIndex; 77 } 78 79 @Override 80 public void run() { 81 82 synchronized (DownloadThread.class) { 83 84 runningTrheadCount = runningTrheadCount + 1;// 开启一线程,线程数加1 85 } 86 87 // 分段请求网络连接,分段保存文件到本地 88 try { 89 URL url = new URL(path); 90 HttpURLConnection openConnection = (HttpURLConnection) url 91 .openConnection(); 92 openConnection.setRequestMethod("GET"); 93 openConnection.setConnectTimeout(10 * 1000); 94 95 System.out.println("理论上下载: 线程:" + threadId + ",开始位置:" 96 + startIndex + ";结束位置:" + endIndex); 97 98 // 读取上次下载结束的位置,本次从这个位置开始直接下载。 99 File file2 = new File(threadId + ".txt"); 100 if (file2.exists()) { 101 BufferedReader bufferedReader = new BufferedReader( 102 new InputStreamReader(new FileInputStream(file2))); 103 String lastPostion_str = bufferedReader.readLine(); 104 lastPostion = Integer.parseInt(lastPostion_str);// 读取文件获取上次下载的位置 105 106 // 设置分段下载的头信息。 Range:做分段数据请求用的。 107 openConnection.setRequestProperty("Range", "bytes:" 108 + lastPostion + "-" + endIndex);// bytes:0-500:请求服务器资源中0-500之间的字节信息 109 // 501-1000: 110 System.out.println("实际下载: 线程:" + threadId + ",开始位置:" 111 + lastPostion + ";结束位置:" + endIndex); 112 bufferedReader.close(); 113 } else { 114 115 lastPostion = startIndex; 116 // 设置分段下载的头信息。 Range:做分段数据请求用的。 117 openConnection.setRequestProperty("Range", "bytes:" 118 + lastPostion + "-" + endIndex);// bytes:0-500:请求服务器资源中0-500之间的字节信息 119 // 501-1000: 120 System.out.println("实际下载: 线程:" + threadId + ",开始位置:" 121 + lastPostion + ";结束位置:" + endIndex); 122 } 123 124 if (openConnection.getResponseCode() == 206) {// 200:请求全部资源成功, 125 // 206代表部分资源请求成功 126 InputStream inputStream = openConnection.getInputStream(); 127 // 请求成功将流写入本地文件中,已经创建的占位那个文件中 128 129 RandomAccessFile randomAccessFile = new RandomAccessFile( 130 new File(getFileName(path)), "rw"); 131 randomAccessFile.seek(lastPostion);// 设置随机文件从哪个位置开始写。 132 // 将流中的数据写入文件 133 byte[] buffer = new byte[1024]; 134 int length = -1; 135 int total = 0;// 记录本次线程下载的总大小 136 137 while ((length = inputStream.read(buffer)) != -1) { 138 randomAccessFile.write(buffer, 0, length); 139 140 total = total + length; 141 // 去保存当前线程下载的位置,保存到文件中 142 int currentThreadPostion = lastPostion + total;// 计算出当前线程本次下载的位置 143 // 创建随机文件保存当前线程下载的位置 144 File file = new File(threadId + ".txt"); 145 RandomAccessFile accessfile = new RandomAccessFile( 146 file, "rwd"); 147 accessfile.write(String.valueOf(currentThreadPostion) 148 .getBytes()); 149 accessfile.close(); 150 151 } 152 // 关闭相关的流信息 153 inputStream.close(); 154 randomAccessFile.close(); 155 156 System.out.println("线程:" + threadId + ",下载完毕"); 157 158 // 当所有线程下载结束,删除存放下载位置的文件。 159 synchronized (DownloadThread.class) { 160 runningTrheadCount = runningTrheadCount - 1;// 标志着一个线程下载结束。 161 if (runningTrheadCount == 0) { 162 System.out.println("所有线程下载完成"); 163 for (int i = 0; i < threadCount; i++) { 164 File file = new File(i + ".txt"); 165 System.out.println(file.getAbsolutePath()); 166 file.delete(); 167 } 168 } 169 170 } 171 172 } 173 174 } catch (Exception e) { 175 e.printStackTrace(); 176 } 177 178 super.run(); 179 } 180 181 } 182 183 public static String getFileName(String url) {//通过找到最后一个斜杠的位置,然后提取出文件的名称; 184 185 return url.substring(url.lastIndexOf("/")); 186 187 } 188 189 }
【9】注意事项:
(1)在下次开始下载时候的位置应该从上次记录下载的位置开始下载:应该是lastPostion,不是startPostion;
randomAccessFile.seek(lastPostion);// 设置随机文件从哪个位置开始写。
(2)在文件下载结束之后需要将保存位置的文件删除,在删除前应该关闭文件流,否则文件无法删除;
1 bufferedReader.close();
(3) 为了使程序更通用,写了 专门的提取文件名称的方法,从url地址中提取
1 RandomAccessFile randomAccessFile = new RandomAccessFile(new File(getFileName(path)), "rw"); 3 ---------------------------------------------------
4 public static String getFileName(String url)//通过找到最后一个斜杠的位置,然后提取出文件的名称;
(4)在线程个数读取的时候需要注意锁,多个线程同时读取数据或者更改数据容易出现数据不准确


浙公网安备 33010602011771号