你可以在这里看到这个demo的源码: 
https://github.com/onlynight/MultiThreadDownloader

效果图

这张效果图是同时开启三个下载任务,限制下载线程数量的效果图。

多线程下载原理

多线程下载的原理就是将下载任务分割成一个个小片段再将每个小片段分配给各个线程进行下载。 
例如一个文件大小为100M,我们决定使用4个线程下载,那么每个线程下载的大小即为25M,每个线程的起始以及结束位置依次如下:

0: 0-25M 
1: 25-50M 
2: 50-75M 
3: 75-END

下载请求使用的是Http GET方法,GET请求允许我们设置参数请求的数据的范围:

  1. HttpURLConnection conn = (HttpURLConnection)
  2. new URL("download url").openConnection();
  3. // 设置http为get方法
  4. conn.setRequestMethod("GET");
  5. // 设置http请求的范围
  6. conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);

多线程访问文件需要创建RandomAccessFile:

  1. RandomAccessFile threadFile = new RandomAccessFile(
  2. fileDownloader.getFileSavePath() + File.separator +
  3. fileDownloader.getFilename(), "rwd");
  4. threadFile.seek(startPos);

断点续传原理

端点续传要求我们将之前下载过的文件片段保存下来,并且记录上次最后的下载位置。下次继续下载的时候从上次记录的位置开始下载即可,无需再从头下载。

线程池作用

线程池的原理分析和使用请查看者几篇文章: 
http://blog.csdn.net/u010687392/article/details/49850803 
http://blog.csdn.net/qq_17250009/article/details/50906508

试想如果我们有100个下载任务,我们让每个任务分成3个线程下载,那么每个任务都需要4个线程,如果100个任务同时开启下载那么就意味着需要同时启动400个线程执行下载任务,这样势必会影响app性能。

像上面这样大量的创建线程的操作势必会影响操作系统的性能,等这些任务执行完成后销毁线程同样也会消耗很多的系统资源,所以Java中提出了线程池的概念。

下面我们来分析线程池是如何节约系统资源的。

从时间角度:

  • 创线程的时间我们假定它为: t1
  • 线程执行的时间我们假定为: t2
  • 销毁线程的时间我们假定为: t3

就用上例我们计算其耗时为:

T = 100(t1+t2+t3)

我们使用固定上限线程数量的线程池耗时为:

T1 = 5(t1+t3)+100t2

显然固定线程上限线程池所需的时间短了很多,固定数量线程池节约了线程创建和销毁的时间,使用线程复用方法避免了线程的频繁创建和销毁,不仅节约了时间同时节约了系统资源。

关键代码

下载线程:

  1. import java.io.File;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.RandomAccessFile;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7.  
  8. /**
  9. * Created by lion on 2017/2/10.
  10. */
  11.  
  12. public class DownloadRunnable implements Runnable {
  13. private int threadId = -;
  14. private FileDownloader fileDownloader;
  15. private int downloadedSize = ;
  16.  
  17. private int startPos = -;
  18. private int endPos = -;
  19. private int downloadLength = ;
  20.  
  21. private boolean isFinish;
  22. private boolean isStart;
  23.  
  24. public DownloadRunnable(FileDownloader fileDownloader, int threadId, int blockSize,
  25. int downloadedSize) {
  26. this.fileDownloader = fileDownloader;
  27. this.threadId = threadId;
  28. int fileSize = fileDownloader.getFileSize();
  29. this.startPos = blockSize * threadId + downloadedSize;
  30. this.endPos = blockSize * (threadId + ) < fileSize ?
  31. blockSize * (threadId + ) : fileSize;
  32. this.downloadedSize = downloadedSize;
  33. }
  34.  
  35. @Override
  36. public void run() {
  37. if (startPos >= endPos) {
  38. isFinish = true;
  39. } else {
  40. try {
  41. isStart = true;
  42. isFinish = false;
  43. HttpURLConnection conn = (HttpURLConnection)
  44. new URL(fileDownloader.getDownloadUrl()).openConnection();
  45. conn.setConnectTimeout(FileDownloader.getConnectionTimeOut());
  46. conn.setRequestMethod("GET");
  47.  
  48. //set accept file meta-data type
  49. conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg," +
  50. " image/pjpeg, application/x-shockwave-flash, application/xaml+xml, " +
  51. "application/vnd.ms-xpsdocument, application/x-ms-xbap, " +
  52. "application/x-ms-application, application/vnd.ms-excel, " +
  53. "application/vnd.ms-powerpoint, application/msword, */*");
  54.  
  55. conn.setRequestProperty("Accept-Language", "zh-CN");
  56. conn.setRequestProperty("Referer", fileDownloader.getDownloadUrl());
  57. conn.setRequestProperty("Charset", "UTF-8");
  58. conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; " +
  59. "Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; " +
  60. ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
  61. conn.setRequestProperty("Connection", "Keep-Alive");
  62.  
  63. conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
  64. conn.connect();
  65.  
  66. RandomAccessFile threadFile = new RandomAccessFile(
  67. fileDownloader.getFileSavePath() + File.separator +
  68. fileDownloader.getFilename(), "rwd");
  69. threadFile.seek(startPos);
  70. InputStream inputStream = conn.getInputStream();
  71. byte[] buffer = new byte[];
  72. int offset;
  73. downloadLength = downloadedSize;
  74. while ((offset = inputStream.read(buffer, , )) != -) {
  75. threadFile.write(buffer, , offset);
  76. downloadLength += offset;
  77. fileDownloader.appendDownloadSize(offset);
  78. }
  79. threadFile.close();
  80. inputStream.close();
  81.  
  82. isFinish = true;
  83. isStart = false;
  84. } catch (IOException e) {
  85. e.printStackTrace();
  86. downloadLength = -;
  87. }
  88. }
  89. }
  90.  
  91. public int getDownloadLength() {
  92. return downloadLength;
  93. }
  94.  
  95. public int getThreadId() {
  96. return threadId;
  97. }
  98.  
  99. public boolean isFinish() {
  100. return isFinish;
  101. }
  102.  
  103. public boolean isStart() {
  104. return isStart;
  105. }
  106. }

下载器:

  1. import android.content.Context;
  2. import android.util.SparseIntArray;
  3.  
  4. import java.io.IOException;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7. import java.util.Date;
  8. import java.util.UUID;
  9. import java.util.regex.Matcher;
  10. import java.util.regex.Pattern;
  11.  
  12. /**
  13. * Created by lion on 2017/2/7.
  14. */
  15.  
  16. public class FileDownloader {
  17.  
  18. public static final String TAG = "FileDownloader";
  19.  
  20. /**
  21. * http connection timeout
  22. */
  23. private static int CONNECTION_TIME_OUT = * ;
  24.  
  25. private DownloadProgressManager downloadProgressManager;
  26.  
  27. private DownloadRunnable[] downloadThreads;
  28.  
  29. private String tagName = "";
  30.  
  31. private String downloadUrl;
  32. private String fileSavePath;
  33. private String filename;
  34. private int threadNum = ;
  35. private int fileSize = ;
  36. private int currentDownloadSize = ;
  37.  
  38. private SparseIntArray currentDownloads;
  39.  
  40. public DownloadRunnable[] getDownloadThreads() {
  41. return downloadThreads;
  42. }
  43.  
  44. public DownloadProgressManager getDownloadProgressManager() {
  45. return downloadProgressManager;
  46. }
  47.  
  48. public int getFileSize() {
  49. return fileSize;
  50. }
  51.  
  52. public String getFileSavePath() {
  53. return fileSavePath;
  54. }
  55.  
  56. public String getFilename() {
  57. return filename;
  58. }
  59.  
  60. public String getDownloadUrl() {
  61. return downloadUrl;
  62. }
  63.  
  64. public int getCurrentDownloadSize() {
  65. return currentDownloadSize;
  66. }
  67.  
  68. public int getThreadNum() {
  69. return threadNum;
  70. }
  71.  
  72. synchronized int appendDownloadSize(int size) {
  73. currentDownloadSize += size;
  74. return currentDownloadSize;
  75. }
  76.  
  77. public FileDownloader(Context context) {
  78. this.currentDownloads = new SparseIntArray();
  79. this.downloadProgressManager = new DownloadProgressManager(context);
  80. }
  81.  
  82. private void requestFileInfo(String downloadUrl) throws RuntimeException {
  83. try {
  84. HttpURLConnection connection = (HttpURLConnection)
  85. new URL(downloadUrl).openConnection();
  86. connection.setConnectTimeout(CONNECTION_TIME_OUT);
  87. connection.setRequestMethod("GET");
  88.  
  89. //set accept file meta-data type
  90. connection.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg," +
  91. " image/pjpeg, application/x-shockwave-flash, application/xaml+xml, " +
  92. "application/vnd.ms-xpsdocument, application/x-ms-xbap, " +
  93. "application/x-ms-application, application/vnd.ms-excel, " +
  94. "application/vnd.ms-powerpoint, application/msword, */*");
  95.  
  96. connection.setRequestProperty("Accept-Language", "zh-CN");
  97. connection.setRequestProperty("Referer", downloadUrl);
  98. connection.setRequestProperty("Charset", "UTF-8");
  99. connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; " +
  100. "Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; " +
  101. ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
  102. // connection.setRequestProperty("Connection", "Keep-Alive");
  103.  
  104. connection.connect();
  105.  
  106. if (connection.getResponseCode() == ) {
  107. fileSize = connection.getContentLength();
  108. if (fileSize <= ) {
  109. throw new RuntimeException(TAG + " Unknown file size");
  110. }
  111.  
  112. filename = getFilename(connection);
  113. } else {
  114. throw new RuntimeException(TAG + " Server Response Code is "
  115. + connection.getResponseCode());
  116. }
  117. } catch (IOException e) {
  118. e.printStackTrace();
  119. }
  120. }
  121.  
  122. private String getFilename(HttpURLConnection connection) {
  123. String filename = downloadUrl != null ?
  124. downloadUrl.substring(downloadUrl.lastIndexOf("/") + ) : null;
  125. if (filename == null || "".equals(filename.trim())) {//如果获取不到文件名称
  126. for (int i = ; ; i++) {
  127. String mine = connection.getHeaderField(i);
  128. if (mine == null) break;
  129. if ("content-disposition".equals(connection.getHeaderFieldKey(i).toLowerCase())) {
  130. Matcher m = Pattern.compile(".*filename=(.*)").
  131. matcher(mine.toLowerCase());
  132. if (m.find()) return m.group();
  133. }
  134. }
  135. filename = UUID.randomUUID() + ".tmp";//默认取一个文件名
  136. }
  137. return filename;
  138. }
  139.  
  140. public void prepare(String downloadUrl, String fileSavePath, int threadNum) {
  141. this.downloadUrl = downloadUrl;
  142. this.fileSavePath = fileSavePath;
  143. requestFileInfo(downloadUrl);
  144. SparseIntArray progresses = downloadProgressManager.getProgress(downloadUrl);
  145.  
  146. if (threadNum <= ) {
  147. threadNum = this.threadNum;
  148. } else {
  149. this.threadNum = threadNum;
  150. }
  151.  
  152. if (progresses != null && progresses.size() > ) {
  153. threadNum = progresses.size();
  154. for (int i = ; i < progresses.size(); i++) {
  155. currentDownloadSize += progresses.get(i);
  156. }
  157. }
  158.  
  159. int block = fileSize % threadNum == ?
  160. fileSize / threadNum : fileSize / threadNum + ;
  161.  
  162. downloadThreads = new DownloadRunnable[threadNum];
  163.  
  164. for (int i = ; i < threadNum; i++) {
  165. downloadThreads[i] = new DownloadRunnable(this, i, block,
  166. progresses != null && progresses.size() == threadNum ?
  167. progresses.valueAt(progresses.keyAt(i)) == - ? :
  168. progresses.valueAt(progresses.keyAt(i)) : );
  169. }
  170. }
  171.  
  172. public void start(OnDownloadListener listener) {
  173. boolean isFinish = false;
  174. int lastDownloadSize = ;
  175. int speed = ;
  176. Date current = new Date();
  177. while (!isFinish) {
  178. if (listener != null) {
  179. int percent = (int) (currentDownloadSize / (float) fileSize * );
  180. long time = new Date().getTime() - current.getTime();
  181. speed = (int) ((currentDownloadSize - lastDownloadSize) / 1024f / time * 1000f);
  182. listener.onUpdate(fileSize, currentDownloadSize, speed, percent);
  183. if (percent == ) {
  184. downloadProgressManager.finishDownload(downloadUrl);
  185. break;
  186. }
  187. }
  188. current = new Date();
  189. lastDownloadSize = currentDownloadSize;
  190. updateProgress();
  191. isFinish = checkFinish();
  192. try {
  193. Thread.sleep();
  194. } catch (InterruptedException e) {
  195. e.printStackTrace();
  196. }
  197. }
  198.  
  199. // System.out.println(tagName + " DOWNLOAD FINISH");
  200. if (listener != null) {
  201. listener.onUpdate(fileSize, fileSize, , );
  202. }
  203. }
  204.  
  205. private boolean checkFinish() {
  206. if (downloadThreads != null && downloadThreads.length > ) {
  207. for (DownloadRunnable downloadThread : downloadThreads) {
  208. if (!downloadThread.isFinish()) {
  209. System.out.println("checkFinish false");
  210. return false;
  211. }
  212. }
  213.  
  214. return true;
  215. }
  216. System.out.println("checkFinish true");
  217. return false;
  218. }
  219.  
  220. public boolean isFinish() {
  221. return checkFinish();
  222. }
  223.  
  224. void updateProgress() {
  225. for (DownloadRunnable downloadThread : downloadThreads) {
  226. updateProgress(downloadThread.getThreadId(), downloadThread.getDownloadLength());
  227. }
  228. }
  229.  
  230. synchronized void updateProgress(int threadId, int downloaded) {
  231. currentDownloads.put(threadId, downloaded);
  232. downloadProgressManager.saveProgress(downloadUrl, currentDownloads);
  233. // SparseIntArray progress = downloadProgressManager.getProgress(downloadUrl);
  234. // for (int i = 0; i < progress.size(); i++) {
  235. // System.out.println("prepare progress = " + progress.valueAt(progress.keyAt(i)));
  236. // }
  237. }
  238.  
  239. public boolean isStart() {
  240. for (DownloadRunnable runnable : downloadThreads) {
  241. if (runnable.isStart()) {
  242. return true;
  243. }
  244. }
  245.  
  246. return false;
  247. }
  248.  
  249. static int getConnectionTimeOut() {
  250. return CONNECTION_TIME_OUT;
  251. }
  252.  
  253. static void setConnectionTimeOut(int timeOut) {
  254. CONNECTION_TIME_OUT = timeOut;
  255. }
  256.  
  257. public interface OnDownloadListener {
  258. void onUpdate(int totalSize, int currentSize, int speed, int percent);
  259. }
  260.  
  261. public void setTagName(String tagName) {
  262. this.tagName = tagName;
  263. }
  264. }

下载管理器:

  1. import android.content.Context;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.concurrent.Executor;
  5. import java.util.concurrent.Executors;
  6.  
  7. /**
  8. * Created by lion on 2017/2/8.
  9. */
  10.  
  11. public class DownloadManager {
  12.  
  13. private static int PARALLEL_DOWNLOAD_SIZE = ;
  14. private static DownloadManager instance;
  15.  
  16. private Context context;
  17. private Executor downloadExecutor;
  18. private ArrayList<FileDownloader> fileDownloaders;
  19.  
  20. public static DownloadManager getInstance(Context context) {
  21. if (instance == null) {
  22. instance = new DownloadManager(context);
  23. }
  24. return instance;
  25. }
  26.  
  27. public DownloadManager(Context context) {
  28. this.context = context;
  29. downloadExecutor = Executors.newFixedThreadPool(PARALLEL_DOWNLOAD_SIZE);
  30. // downloadExecutor = Executors.newCachedThreadPool();
  31. fileDownloaders = new ArrayList<>();
  32. }
  33.  
  34. public void download(String name, final String downloadUrl, final String fileSavePath, final int threadNum,
  35. final FileDownloader.OnDownloadListener listener) {
  36. for (FileDownloader downloader : fileDownloaders) {
  37. if (downloader.isFinish()) {
  38. downloader.setTagName(name);
  39. startDownload(downloader, downloadUrl, fileSavePath, threadNum, listener);
  40. return;
  41. }
  42. }
  43.  
  44. FileDownloader currentDownloader = new FileDownloader(context);
  45. currentDownloader.setTagName(name);
  46. fileDownloaders.add(currentDownloader);
  47. startDownload(currentDownloader, downloadUrl, fileSavePath, threadNum, listener);
  48. }
  49.  
  50. public void download(final String downloadUrl, final String fileSavePath, final int threadNum,
  51. final FileDownloader.OnDownloadListener listener) {
  52. for (FileDownloader downloader : fileDownloaders) {
  53. if (downloader.isFinish()) {
  54. startDownload(downloader, downloadUrl, fileSavePath, threadNum, listener);
  55. return;
  56. }
  57. }
  58.  
  59. FileDownloader currentDownloader = new FileDownloader(context);
  60. fileDownloaders.add(currentDownloader);
  61. startDownload(currentDownloader, downloadUrl, fileSavePath, threadNum, listener);
  62. }
  63.  
  64. private synchronized void startDownload(final FileDownloader currentDownloader,
  65. final String downloadUrl, final String fileSavePath,
  66. final int threadNum,
  67. final FileDownloader.OnDownloadListener listener) {
  68. downloadExecutor.execute(new Runnable() {
  69. @Override
  70. public void run() {
  71. currentDownloader.prepare(downloadUrl, fileSavePath,
  72. threadNum);
  73. if (currentDownloader.getDownloadThreads() != null) {
  74. for (DownloadRunnable runnable :
  75. currentDownloader.getDownloadThreads()) {
  76. downloadExecutor.execute(runnable);
  77. }
  78. }
  79. currentDownloader.start(listener);
  80. }
  81. });
  82. }
  83.  
  84. public static void setConnectionTimeOut(int timeOut) {
  85. FileDownloader.setConnectionTimeOut(timeOut);
  86. }
  87.  
  88. public static void setParallelDownloadSize(int size) {
  89. PARALLEL_DOWNLOAD_SIZE = size;
  90. }
  91. }

Android 多线程下载,断点续传,线程池的更多相关文章

  1. android 多线程下载 断点续传

    来源:网易云课堂Android极客班第八次作业练习 练习内容: 多线程 asyncTask handler 多线程下载的原理 首先获取到目标文件的大小,然后在磁盘上申请一块空间用于保存目标文件,接着把 ...

  2. 【多线程】Android多线程学习笔记——线程池

    Java线程池采用了享元设计模式,在系统中维持一定数量的线程,用于处理异步或并发需求,在平时处理异步或并发任务时被广泛使用.这里基于JDK1.8和Android28来整理一些关于线程池的知识点. 一. ...

  3. android程序---->android多线程下载(一)

    多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务,可以使任务的执行速度变快.多线程的任务下载时常都会使用得到断点续传下载,就是我们在一次下载未结束时退出下载,第二次下载时会接着第一次 ...

  4. android程序---->android多线程下载(二)

    上篇我们讲到了android中下载的断点续传问题,今天我们开始学习下载的多线程问题.本次的多线程源码下载:androdi中多线程下载的实现代码.有关断点续传的问题,请参见博客:android程序--- ...

  5. 无废话Android之smartimageview使用、android多线程下载、显式意图激活另外一个activity,检查网络是否可用定位到网络的位置、隐式意图激活另外一个activity、隐式意图的配置,自定义隐式意图、在不同activity之间数据传递(5)

    1.smartimageview使用 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android&q ...

  6. Andoid 更好的Android多线程下载框架

    概述 为什么是更好的Android多线程下载框架呢,原因你懂的,广告法嘛! 本篇我们我们就来聊聊多线程下载框架,先聊聊我们框架的特点: 多线程 多任务 断点续传 支持大文件 可以自定义下载数据库 高度 ...

  7. 更好的Android多线程下载框架

    /** * 作者:Pich * 原文链接:http://me.woblog.cn/ * QQ群:129961195 * Github:https://github.com/lifengsofts */ ...

  8. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  9. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

随机推荐

  1. 理解ThreadLocal类

    1 ThreadLocal是什么 早在JDK 1.2的版本号中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路. 使用这个工具类能够 ...

  2. 《AndroidStudio每日一贴》7. 怎样将本地变更文件移到其他的changelist?

    操作方法: 进入Version Control -> Local Changes ,会显示本地变更列表分组. 假设你想将某个changelist中的文件转移到其他的changelist, 选中此 ...

  3. modSecurity规则学习(八)——防止CC攻击

    modSecurity日志收集:在phase 5阶段处理. 由于CC攻击主要考虑对动态请求的防护,所以要排除静态资源的请求,或者自定义动态请求的后缀或者关键字做接口针对性的防护. 定义需要排除的请求u ...

  4. POJ 3626 BFS

    思路:easy BFS //By SiriusRen #include <queue> #include <cstdio> #include <algorithm> ...

  5. Windows平台下使用pthreads开发多线程应用

    pthreads简介 POSIX 1003.1-2001标准定义了编写多线程应用程序的API(应用程序编程接口),这个接口通常被称为pthreads.在常见的操作系统中,例如Unix.Linux.Ma ...

  6. Semi-Prime(半素数)

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2723 Semi-Prime Time Limit: 2 Seconds   ...

  7. python音频处理相关类库

    一.eyeD3 以下是eyed3的官方介绍 eyeD3 is a Python tool for working with audio files, specifically mp3 files co ...

  8. Vue数据驱动表单渲染,轻松搞定form表单

    form-create 具有动态渲染.数据收集.校验和提交功能的表单生成器,支持双向数据绑定.事件扩展以及自定义组件,可快速生成包含有省市区三级联动.时间选择.日期选择等17种功能组件. Github ...

  9. 【Linux系统引导过程】

    *** 第一步 开机自检 根据主板BIOS中的启动顺序,移交系统控制权. 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它. 这是因为BIO ...

  10. 紫书 习题 10-32 UVa 1414 ( 迷之规律)

    看了其他人博客,貌似i个盘子的方案数满足 f[i] = f[i-1] * x + y ??????? 神来之笔 貌似没有找到严格的证明-- 牛逼-- 如果这样的话暴力求出x和y然后递推完事 #incl ...