Java / Android 基于Http的多线程下载的实现

先说下原理,原理明白了,其实很简单:

a、对于网络上的一个资源,首先发送一个请求,从返回的Content-Length中回去需要下载文件的大小,然后根据文件大小创建一个文件。

  1. this.fileSize = conn.getContentLength(); // 根据响应获取文件大小
  2. File dir = new File(dirStr);
  3. this.localFile = new File(dir, filename);
  4. RandomAccessFile raf = new RandomAccessFile( this.localFile, "rw");
  5. raf.setLength(fileSize);
  6. raf.close();

b、根据线程数和文件大小,为每个线程分配下载的字节区间,然后每个线程向服务器发送请求,获取这段字节区间的文件内容。

  1. conn.setRequestProperty( "Range", "bytes=" + startPos + "-"
  2. + endPos); // 设置获取实体数据的范围

c、利用RandomAccessFile的seek方法,多线程同时往一个文件中写入字节。

  1. raf.seek(startPos);
  2. while ((len = is.read(buf)) != - 1)
  3. {
  4. raf.write(buf, 0, len);
  5. }
分析完了原理就很简单了,我封装了一个类,利用这个类的实例进行下载,所需参数:下载资源的URI, 本地文件路径,线程的数量。

  1. package com.zhy.mutilthread_download;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.RandomAccessFile;
  6. import java.net.HttpURLConnection;
  7. import java.net.URL;
  8. public class MultipartThreadDownloador
  9. {
  10. /**
  11. * 需要下载资源的地址
  12. */
  13. private String urlStr;
  14. /**
  15. * 下载的文件
  16. */
  17. private File localFile;
  18. /**
  19. * 需要下载文件的存放的本地文件夹路径
  20. */
  21. private String dirStr;
  22. /**
  23. * 存储到本地的文件名
  24. */
  25. private String filename;
  26. /**
  27. * 开启的线程数量
  28. */
  29. private int threadCount;
  30. /**
  31. * 下载文件的大小
  32. */
  33. private long fileSize;
  34. public MultipartThreadDownloador(String urlStr, String dirStr,
  35. String filename, int threadCount)
  36. {
  37. this.urlStr = urlStr;
  38. this.dirStr = dirStr;
  39. this.filename = filename;
  40. this.threadCount = threadCount;
  41. }
  42. public void download() throws IOException
  43. {
  44. createFileByUrl();
  45. /**
  46. * 计算每个线程需要下载的数据长度
  47. */
  48. long block = fileSize % threadCount == 0 ? fileSize / threadCount
  49. : fileSize / threadCount + 1;
  50. for ( int i = 0; i < threadCount; i++)
  51. {
  52. long start = i * block;
  53. long end = start + block >= fileSize ? fileSize : start + block - 1;
  54. new DownloadThread( new URL(urlStr), localFile, start, end).start();
  55. }
  56. }
  57. /**
  58. * 根据资源的URL获取资源的大小,以及在本地创建文件
  59. */
  60. public void createFileByUrl() throws IOException
  61. {
  62. URL url = new URL(urlStr);
  63. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  64. conn.setConnectTimeout( 15 * 1000);
  65. conn.setRequestMethod( "GET");
  66. conn.setRequestProperty(
  67. "Accept",
  68. "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
  69. conn.setRequestProperty( "Accept-Language", "zh-CN");
  70. conn.setRequestProperty( "Referer", urlStr);
  71. conn.setRequestProperty( "Charset", "UTF-8");
  72. conn.setRequestProperty(
  73. "User-Agent",
  74. "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
  75. conn.setRequestProperty( "Connection", "Keep-Alive");
  76. conn.connect();
  77. if (conn.getResponseCode() == 200)
  78. {
  79. this.fileSize = conn.getContentLength(); // 根据响应获取文件大小
  80. if (fileSize <= 0)
  81. throw new RuntimeException(
  82. "the file that you download has a wrong size ... ");
  83. File dir = new File(dirStr);
  84. if (!dir.exists())
  85. dir.mkdirs();
  86. this.localFile = new File(dir, filename);
  87. RandomAccessFile raf = new RandomAccessFile( this.localFile, "rw");
  88. raf.setLength(fileSize);
  89. raf.close();
  90. System.out.println( "需要下载的文件大小为 :" + this.fileSize + " , 存储位置为: "
  91. + dirStr + "/" + filename);
  92. } else
  93. {
  94. throw new RuntimeException( "url that you conneted has error ...");
  95. }
  96. }
  97. private class DownloadThread extends Thread
  98. {
  99. /**
  100. * 下载文件的URI
  101. */
  102. private URL url;
  103. /**
  104. * 存的本地路径
  105. */
  106. private File localFile;
  107. /**
  108. * 是否结束
  109. */
  110. private boolean isFinish;
  111. /**
  112. * 开始的位置
  113. */
  114. private Long startPos;
  115. /**
  116. * 结束位置
  117. */
  118. private Long endPos;
  119. public DownloadThread(URL url, File savefile, Long startPos, Long endPos)
  120. {
  121. this.url = url;
  122. this.localFile = savefile;
  123. this.startPos = startPos;
  124. this.endPos = endPos;
  125. }
  126. @Override
  127. public void run()
  128. {
  129. System.out.println(Thread.currentThread().getName() + "开始下载...");
  130. try
  131. {
  132. HttpURLConnection conn = (HttpURLConnection) url
  133. .openConnection();
  134. conn.setConnectTimeout( 15 * 1000);
  135. conn.setRequestMethod( "GET");
  136. conn.setRequestProperty(
  137. "Accept",
  138. "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
  139. conn.setRequestProperty( "Accept-Language", "zh-CN");
  140. conn.setRequestProperty( "Referer", url.toString());
  141. conn.setRequestProperty( "Charset", "UTF-8");
  142. conn.setRequestProperty( "Range", "bytes=" + startPos + "-"
  143. + endPos); // 设置获取实体数据的范围
  144. conn.setRequestProperty(
  145. "User-Agent",
  146. "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
  147. conn.setRequestProperty( "Connection", "Keep-Alive");
  148. conn.connect();
  149. /**
  150. * 代表服务器已经成功处理了部分GET请求
  151. */
  152. if (conn.getResponseCode() == 206)
  153. {
  154. InputStream is = conn.getInputStream();
  155. int len = 0;
  156. byte[] buf = new byte[ 1024];
  157. RandomAccessFile raf = new RandomAccessFile(localFile,
  158. "rwd");
  159. raf.seek(startPos);
  160. while ((len = is.read(buf)) != - 1)
  161. {
  162. raf.write(buf, 0, len);
  163. }
  164. raf.close();
  165. is.close();
  166. System.out.println(Thread.currentThread().getName()
  167. + "完成下载 : " + startPos + " -- " + endPos);
  168. this.isFinish = true;
  169. } else
  170. {
  171. throw new RuntimeException(
  172. "url that you conneted has error ...");
  173. }
  174. } catch (IOException e)
  175. {
  176. e.printStackTrace();
  177. }
  178. }
  179. }
  180. }

createFileByUrl方法,就是我们上述的原理的步骤1,得到文件大小和创建本地文件。我在程序使用了一个内部类DownloadThread继承Thread,专门负责下载。download()方法,根据线程数量和文件大小计算每个线程需要下载的字节区间,然后开启线程去下载。

服务器端:我就扔了几个文件在Tomcat根目录做实验,下面是测试代码:

  1. package com.zhy.mutilthread_download;
  2. import java.io.IOException;
  3. public class Test
  4. {
  5. public static void main(String[] args)
  6. {
  7. try
  8. {
  9. new MultipartThreadDownloador( "http://localhost:8080/nexus.zip",
  10. "f:/backup/nexus", "nexus.zip", 2).download();
  11. } catch (IOException e)
  12. {
  13. e.printStackTrace();
  14. }
  15. }
  16. }

输出结果:

  1. 需要下载的文件大小为 : 31143237 , 存储位置为: f:/backup/nexus/nexus.zip
  2. Thread- 1开始下载...
  3. Thread- 2开始下载...
  4. Thread- 3开始下载...
  5. Thread- 4开始下载...
  6. Thread- 4完成下载 : 23357430 -- 31143237
  7. Thread- 2完成下载 : 7785810 -- 15571619
  8. Thread- 1完成下载 : 0 -- 7785809
  9. Thread- 3完成下载 : 15571620 -- 23357429

截图:



猜你喜欢

转载自blog.csdn.net/suyimin2010/article/details/81025046