后台多任务多线程断点下载

上图:



多线程断点下载其实不是很难,主要就是三个方面:

1、根据文件的大小和下载线程的数量,确定每个下载线程要下载的分割文件的大小;

2、记录每个下载线程已经下载完成的进度;

3、将每个线程下载的分割的文件合并到一个文件中。

那么怎么将远程的一个文件分割成三部分来下载呢?其实在HTTP协议中,有一个Range字段,用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位,格式为:Range: bytes x - y,eg:Range: bytes=0-100 下载从第0 -- 100 字节范围的内容;有个这么个东西就可以很容易的实现多线程下载了。

接下来就是记录每个线程下载的进度,在android当中的存储方式基本可以用,像Sqlite、文件等;一个下载线程每次获取到的文件长度累加起来的这个数值就是当前线程到目前为止所下载的进度,而我们所要记录就是这个数值。

最后就是合并了,首先,先在本地创建一个文件,这个文件的大小和我们要下载的文件大小是相等的;然后用java提供的RandomAccessFile这个类,这个类中有这么一个方法seek();表示的是从哪个位置开始写入数据,而这个位置也就是我们要传入的参数是一个int类型的。

通过以上的几个步骤就可以简单实现多线程下载了,接下来稍微说一下主要的一些代码流程:


1、将我们要下载的文件名称和地址传到service中

2、对每一个下载任务创建一个下载器(Downloader),并保存到一个集合当中,这样就可以实现多任务下载了

3、获取下载器的详细信息(文件大小、进度、地址),同时在这个步骤还需初始化下载器,在本地创建一个相同大小的文件,并确定每一个线程要下载的大小,保存到数据库当中

4、开启线程开始下载文件,同时将下载的数据写入创建好的文件当中

5、将每个线程下载的进度传给service,用来更新进度条

详细的代码就不贴了,就贴一下主要的源码,具体要的话可以下一下源码。

[java]  view plain copy
  1. package com.wpy.multithreadeddownload.service;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. import android.app.Service;  
  7. import android.content.Intent;  
  8. import android.os.Handler;  
  9. import android.os.IBinder;  
  10. import android.util.Log;  
  11.   
  12. import com.wpy.multithreadeddownload.constant.Constant;  
  13. import com.wpy.multithreadeddownload.dao.SqliteDao;  
  14. import com.wpy.multithreadeddownload.entity.DownloaderInfo;  
  15. import com.wpy.multithreadeddownload.entity.FileState;  
  16. import com.wpy.multithreadeddownload.util.Downloader;  
  17.   
  18. /** 
  19.  *  
  20.  * 项目名称:MultithreadedDownload 类名称:DownloadService 类描述: 后台下载 创建人:wpy 
  21.  * 创建时间:2014-10-10 下午5:18:31 
  22.  *  
  23.  */  
  24. public class DownloadService extends Service {  
  25.   
  26.     // 下载器  
  27.     private Downloader downloader;  
  28.   
  29.     private SqliteDao dao;  
  30.     /** 
  31.      * 存放各个下载器 
  32.      */  
  33.     private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();  
  34.   
  35.     /** 
  36.      * 存放每个下载文件的总长度 
  37.      */  
  38.     private Map<String, Integer> fileSizes = new HashMap<String, Integer>();  
  39.     /** 
  40.      * 存放每个下载文件完成的长度 
  41.      */  
  42.     private Map<String, Integer> completeSizes = new HashMap<String, Integer>();  
  43.   
  44.     /** 
  45.      * 消息处理 接收Download中每个线程传输过来的数据 
  46.      */  
  47.     private Handler mHandler = new Handler() {  
  48.         public void handleMessage(android.os.Message msg) {  
  49.             if (msg.what == 1) {  
  50.                 String url = (String) msg.obj;  
  51.                 int length = msg.arg1;  
  52.   
  53.                 int completeSize = completeSizes.get(url);  
  54.                 completeSize = completeSize + length;  
  55.                 completeSizes.put(url, completeSize);  
  56.   
  57.                 // Log.e("test>>", "消息处理器Handler当前进度:" + completeSize);  
  58.   
  59.                 int fileSize = fileSizes.get(url);  
  60.                 if (completeSize == fileSize) {// 下载完成  
  61.                     dao.updataStateByUrl(url);  
  62.                     downloaders.get(url).delete(url);  
  63.                     downloaders.remove(url);  
  64.                     if (downloaders.isEmpty()) {// 如果全部下载完成,关闭service  
  65.                         stopSelf();  
  66.                     }  
  67.                 }  
  68.                 // 发送广播更新下载管理的进度  
  69.                 Intent intent = new Intent();  
  70.                 intent.setAction(Constant.DOWNLOADMANAGEACTION);  
  71.                 intent.putExtra("completeSize", completeSize);  
  72.                 intent.putExtra("url", url);  
  73.                 DownloadService.this.sendBroadcast(intent);  
  74.             }  
  75.         };  
  76.     };  
  77.   
  78.     @Override  
  79.     public void onCreate() {  
  80.         super.onCreate();  
  81.         dao = new SqliteDao(this);  
  82.     }  
  83.   
  84.     @Override  
  85.     public int onStartCommand(Intent intent, int flags, int startId) {  
  86.         String urlPath = intent.getStringExtra("downloadUrl");  
  87.         String name = intent.getStringExtra("name");  
  88.         String flag = intent.getStringExtra("flag");  
  89.         if (flag.equals("startDownload")) {  
  90.             startDownload(name, urlPath, true);  
  91.         }  
  92.         if (flag.equals("changeState")) {  
  93.             changeState(name, urlPath);  
  94.         }  
  95.         return super.onStartCommand(intent, flags, startId);  
  96.     }  
  97.   
  98.     @Override  
  99.     public IBinder onBind(Intent intent) {  
  100.         return null;  
  101.     }  
  102.   
  103.     /** 
  104.      * 开始下载 
  105.      *  
  106.      * @param urlPath 
  107.      *            下载地址 
  108.      */  
  109.     private void startDownload(final String name, final String urlPath,  
  110.             final boolean isFirst) {  
  111.         Log.e("test>>""文件的名称:" + name);  
  112.         Log.e("test>>""文件的下载地址:" + urlPath);  
  113.         // 初始化一个下载器  
  114.         downloader = downloaders.get(urlPath);  
  115.         if (null == downloader) {  
  116.             downloader = new Downloader(name, urlPath, Constant.LOCALPATH,  
  117.                     Constant.THREADCOUNT, this, mHandler);  
  118.             downloaders.put(urlPath, downloader);  
  119.         }  
  120.         if (downloader.isDownloading()) {  
  121.             return;  
  122.         }  
  123.   
  124.         new Thread() {  
  125.             public void run() {  
  126.                 DownloaderInfo downloaderInfo = downloader.getDownloaderInfos();  
  127.                 completeSizes.put(urlPath, downloaderInfo.getComplete());  
  128.   
  129.                 if (fileSizes.get(urlPath) == null) {  
  130.                     fileSizes.put(urlPath, downloaderInfo.getFileSize());  
  131.                 }  
  132.   
  133.                 // FileState state = dao.query(urlPath);  
  134.                 if (isFirst) {  
  135.                     Log.e("test>>""文件:" + name + "第一次下载");  
  136.                     FileState fileState = new FileState(name, urlPath, 1,  
  137.                             downloaderInfo.getComplete(),  
  138.                             downloaderInfo.getFileSize());  
  139.                     dao.saveFileState(fileState);  
  140.                 }  
  141.   
  142.                 downloader.download();  
  143.             };  
  144.         }.start();  
  145.     }  
  146.   
  147.     /** 
  148.      * 更改下载状态(若文件正在下载,就暂停;若暂停,则开始下载) 
  149.      *  
  150.      * @param url 
  151.      *            下载地址 
  152.      */  
  153.     public void changeState(String name, String url) {  
  154.         Downloader loader = downloaders.get(url);  
  155.         if (loader != null) {  
  156.             if (loader.isDownloading()) {// 正在下载  
  157.                 loader.setPause();  
  158.             } else if (loader.isPause()) {// 暂停  
  159.                 loader.reset();  
  160.                 this.startDownload(name, url, false);  
  161.             }  
  162.         } else {  
  163.             startDownload(name, url, false);  
  164.         }  
  165.     }  
  166. }  

[java]  view plain copy
  1. package com.wpy.multithreadeddownload.util;  
  2.   
  3. import java.io.File;  
  4. import java.io.InputStream;  
  5. import java.io.RandomAccessFile;  
  6. import java.net.HttpURLConnection;  
  7. import java.net.URL;  
  8. import java.util.ArrayList;  
  9. import java.util.List;  
  10.   
  11. import android.content.Context;  
  12. import android.os.Handler;  
  13. import android.os.Message;  
  14. import android.util.Log;  
  15. import android.widget.Toast;  
  16.   
  17. import com.wpy.multithreadeddownload.dao.SqliteDao;  
  18. import com.wpy.multithreadeddownload.entity.DownloadInfo;  
  19. import com.wpy.multithreadeddownload.entity.DownloaderInfo;  
  20.   
  21. /** 
  22.  *  
  23.  * 项目名称:MultithreadedDownload 类名称:Downloader 类描述: 下载器 创建人:wpy 创建时间:2014-10-11 
  24.  * 上午9:24:01 
  25.  *  
  26.  */  
  27. public class Downloader {  
  28.     private String fileName;// 文件名称  
  29.     private String downloadPath;// 下载地址  
  30.     private String localPath;// 本地保存的地址  
  31.     private int threadCount;// 下载线程的数量  
  32.     private int fileSize;// 下载文件的大小  
  33.     private Context context;// 上下文  
  34.     private Handler mHandler;// 消息处理器  
  35.     private List<DownloadInfo> infos;// 存放下载信息的集合  
  36.   
  37.     // 定义三种下载状态:初始化、下载中、暂停  
  38.     private static final int INIT = 1;  
  39.     private static final int DOWNLOADING = 2;  
  40.     private static final int PAUSE = 3;  
  41.     private int state = INIT;// 设置状态为初始化  
  42.   
  43.     private SqliteDao dao;  
  44.   
  45.     /** 
  46.      * 构造函数 
  47.      *  
  48.      * @param fileName 
  49.      *            文件名称 
  50.      * @param downloadPath 
  51.      *            下载地址 
  52.      * @param localPath 
  53.      *            本地存储地址 
  54.      * @param threadCount 
  55.      *            线程数量 
  56.      * @param context 
  57.      *            上下文 
  58.      * @param mHandler 
  59.      *            消息处理器 
  60.      */  
  61.     public Downloader(String fileName, String downloadPath, String localPath,  
  62.             int threadCount, Context context, Handler mHandler) {  
  63.         this.fileName = fileName;  
  64.         this.downloadPath = downloadPath;  
  65.         this.localPath = localPath;  
  66.         this.threadCount = threadCount;  
  67.         this.context = context;  
  68.         this.mHandler = mHandler;  
  69.   
  70.         dao = new SqliteDao(context);  
  71.     }  
  72.   
  73.     /** 
  74.      * 获取下载器信息。 首先判断是否是第一次下载, 是第一次下载的话要进行初始化操作,并将下载器的信息保存到数据库中; 
  75.      * 如果不是,就从数据库中读取之前下载的信息(起始位置,结束位置,文件大小等),并将下载信息返回给下载器。 
  76.      *  
  77.      * @return 下载器信息(文件的大小、下载的完成度、下载器标识/下载地址) 
  78.      */  
  79.     public DownloaderInfo getDownloaderInfos() {  
  80.         if (isFirstDownload(downloadPath)) {// 第一次下载  
  81.   
  82.             init();  
  83.             if (fileSize > 0) {  
  84.                 int range = fileSize / threadCount;  
  85.   
  86.                 Log.e("test>>""每个线程下载的大小:" + range);  
  87.   
  88.                 infos = new ArrayList<DownloadInfo>();  
  89.   
  90.                 for (int i = 0; i < threadCount - 1; i++) {  
  91.                     DownloadInfo info = new DownloadInfo(i, i * range, (i + 1)  
  92.                             * range - 10, downloadPath);  
  93.   
  94.                     Log.e("test>>""线程<" + i + ">下载的大小:" + i * range + "---"  
  95.                             + ((i + 1) * range - 1));  
  96.   
  97.                     infos.add(info);  
  98.                 }  
  99.                 DownloadInfo info = new DownloadInfo(threadCount - 1,  
  100.                         (threadCount - 1) * range, fileSize - 10,  
  101.                         downloadPath);  
  102.   
  103.                 Log.e("test>>""线程<" + (threadCount - 1) + ">下载的大小:"  
  104.                         + (threadCount - 1) * range + "---" + (fileSize - 1));  
  105.   
  106.                 infos.add(info);  
  107.                 // 保存下载器信息到数据库  
  108.                 dao.saveDownloadInfos(infos);  
  109.   
  110.             }  
  111.             // 创建一个DownloaderInfo记录下载器的具体信息  
  112.             return new DownloaderInfo(fileSize, 0, downloadPath);  
  113.   
  114.         } else {  
  115.             // 不是第一次下载,从数据库中获取已有的downloadPath下载地址的下载器的具体信息  
  116.             infos = dao.getDownloadInfos(downloadPath);  
  117.             int size = 0;// 文件总大小  
  118.             int completeSize = 0;// 下载的总长度  
  119.             for (DownloadInfo info : infos) {  
  120.                 size = size + (info.getEndPos() - info.getStartPos() + 1);  
  121.                 completeSize = completeSize + info.getCompeleteSize();  
  122.             }  
  123.             return new DownloaderInfo(size, completeSize, downloadPath);  
  124.         }  
  125.   
  126.     }  
  127.   
  128.     /** 
  129.      * 初始化下载器(获取要下载文件的大小;根据本地地址,在本地创建一个相同大小的文件) 
  130.      */  
  131.     private void init() {  
  132.         try {  
  133.             URL url = new URL(downloadPath);// 通过给定的下载地址得到一个url  
  134.             HttpURLConnection conn = (HttpURLConnection) url.openConnection();// 得到一个http连接  
  135.             conn.setConnectTimeout(5 * 1000);// 设置连接超时为5秒钟  
  136.             conn.setRequestMethod("GET");// 设置连接方式为GET  
  137.   
  138.             Log.e("test>>""获取前  文件的大小:" + fileSize);  
  139.   
  140.             int code = conn.getResponseCode();  
  141.             Log.e("test>>""网络请求的返回码:" + code);  
  142.             // 如果http返回的代码是200或者206则为连接成功  
  143.             if (conn.getResponseCode() == 200 || conn.getResponseCode() == 206) {  
  144.                 fileSize = conn.getContentLength();// 得到文件的大小  
  145.   
  146.                 Log.e("test>>""文件的大小:" + fileSize);  
  147.   
  148.                 if (fileSize <= 0) {  
  149.                     Toast.makeText(context, "网络故障,无法获取文件大小", Toast.LENGTH_SHORT)  
  150.                             .show();  
  151.                 }  
  152.                 File dir = new File(localPath);  
  153.                 if (!dir.exists()) {// 文件不存在  
  154.                     if (dir.mkdirs()) {  
  155.                         System.out.println("mkdirs success.");  
  156.                     }  
  157.                 }  
  158.                 File file = new File(localPath, fileName);  
  159.                 RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");  
  160.                 accessFile.setLength(fileSize);  
  161.                 accessFile.close();  
  162.             }  
  163.   
  164.             conn.disconnect();  
  165.         } catch (Exception e) {  
  166.             e.printStackTrace();  
  167.         }  
  168.     }  
  169.   
  170.     /** 
  171.      * 开始下载数据 
  172.      */  
  173.     public void download() {  
  174.         if (null != infos) {  
  175.             if (DOWNLOADING == state) {  
  176.                 return;  
  177.             }  
  178.             state = DOWNLOADING;// 将下载状态设置为下载中  
  179.             for (DownloadInfo info : infos) {  
  180.                 new DownloadThread(info.getThreadId(), info.getStartPos(),  
  181.                         info.getEndPos(), info.getCompeleteSize(),  
  182.                         info.getUrl()).start();  
  183.             }  
  184.         }  
  185.     }  
  186.   
  187.     /** 
  188.      *  
  189.      * 项目名称:MultithreadedDownload 类名称:DownloadThread 类描述: 一个内部类,建立一个下载线程 创建人:wpy 
  190.      * 创建时间:2014-10-11 下午1:08:12 
  191.      *  
  192.      */  
  193.     private class DownloadThread extends Thread {  
  194.   
  195.         private int threadId;  
  196.         private int startPos;  
  197.         private int endPos;  
  198.         private int completeSize;  
  199.         private String urlPath;  
  200.   
  201.         /** 
  202.          * 下载线程类的构造函数 
  203.          *  
  204.          * @param threadId 
  205.          *            线程id 
  206.          * @param startPos 
  207.          *            开始下载的节点 
  208.          * @param endPos 
  209.          *            停止下载的节点 
  210.          * @param completeSize 
  211.          *            下载完成的进度 
  212.          * @param urlPath 
  213.          *            下载的地址 
  214.          */  
  215.         public DownloadThread(int threadId, int startPos, int endPos,  
  216.                 int completeSize, String urlPath) {  
  217.             this.threadId = threadId;  
  218.             this.startPos = startPos;  
  219.             this.endPos = endPos;  
  220.             this.completeSize = completeSize;  
  221.             this.urlPath = urlPath;  
  222.         }  
  223.   
  224.         @Override  
  225.         public void run() {  
  226.             HttpURLConnection connection = null;  
  227.             RandomAccessFile accessFile = null;  
  228.             InputStream inputStream = null;  
  229.             File file = new File(localPath, fileName);  
  230.             try {  
  231.                 URL url = new URL(urlPath);  
  232.                 connection = (HttpURLConnection) url.openConnection();  
  233.                 connection.setConnectTimeout(5 * 1000);  
  234.                 connection.setRequestMethod("GET");  
  235.   
  236.                 // 设置http头中的Range字段,格式为:Range: bytes x - y  
  237.                 // Range: 用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式如:  
  238.                 // Range: bytes=0-499 下载第0-499字节范围的内容  
  239.                 connection.setRequestProperty("Range""bytes="  
  240.                         + (startPos + completeSize) + "-" + endPos);  
  241.   
  242.                 connection.connect();  
  243.   
  244.                 if (connection.getResponseCode() == 200  
  245.                         || connection.getResponseCode() == 206) {  
  246.                     accessFile = new RandomAccessFile(file, "rwd");  
  247.                     accessFile.seek(startPos + completeSize);// 设置从哪个位置写入数据  
  248.   
  249.                     inputStream = connection.getInputStream();  
  250.                     byte[] buffer = new byte[4096];  
  251.                     int length = -1;  
  252.                     while ((length = inputStream.read(buffer)) != -1) {  
  253.                         // 写入数据  
  254.                         accessFile.write(buffer, 0, length);  
  255.                         // 累加已经下载的长度  
  256.                         completeSize = completeSize + length;  
  257.                         // 更新数据中的信息  
  258.                         dao.updataDownloadInfos(threadId, completeSize, urlPath);  
  259.                         // 用消息将下载信息传给进度条,对进度条进行更新  
  260.                         Message msg = Message.obtain();  
  261.                         msg.what = 1;  
  262.                         msg.obj = urlPath;  
  263.                         msg.arg1 = length;  
  264.                         mHandler.sendMessage(msg);// 给DownloadService发送消息  
  265.   
  266.                         // Log.e("test>>", "Downloader当前进度:" + completeSize);  
  267.   
  268.                         // 暂停  
  269.                         if (PAUSE == state) {  
  270.                             return;  
  271.                         }  
  272.                     }  
  273.                 }  
  274.             } catch (Exception e) {  
  275.                 e.printStackTrace();  
  276.             } finally {  
  277.                 try {  
  278.                     // 关闭该关闭的东西  
  279.                     inputStream.close();  
  280.                     accessFile.close();  
  281.                     connection.disconnect();  
  282.                     // dao.closeDB();  
  283.                 } catch (Exception e) {  
  284.                     e.printStackTrace();  
  285.                 }  
  286.             }  
  287.         }  
  288.     }  
  289.   
  290.     /** 
  291.      * 判断是否是第一次下载 
  292.      *  
  293.      * @param downloadPath 
  294.      *            下载地址 
  295.      * @return true 第一次下载 false 再次下载 
  296.      */  
  297.     private boolean isFirstDownload(String downloadPath) {  
  298.         return dao.isHasDownloadInfos(downloadPath);  
  299.     }  
  300.   
  301.     /** 
  302.      * 判断是否正在下载 
  303.      *  
  304.      * @return true 是 false 否 
  305.      */  
  306.     public boolean isDownloading() {  
  307.         return state == DOWNLOADING;  
  308.     }  
  309.   
  310.     /** 
  311.      * 判断是否暂停 
  312.      *  
  313.      * @return true 是 false 否 
  314.      */  
  315.     public boolean isPause() {  
  316.         return state == PAUSE;  
  317.     }  
  318.   
  319.     /** 
  320.      * 设置暂停 
  321.      */  
  322.     public void setPause() {  
  323.         state = PAUSE;  
  324.     }  
  325.   
  326.     /** 
  327.      * 根据urlPath删除数据库中对应的下载器信息 
  328.      *  
  329.      * @param urlPath 
  330.      *            下载地址 
  331.      */  
  332.     public void delete(String urlPath) {  
  333.         dao.delete(urlPath);  
  334.     }  
  335.   
  336.     /** 
  337.      * 重置下载状态 
  338.      */  
  339.     public void reset() {  
  340.         state = INIT;  
  341.     }  
  342. }  




我是源码
注意: 如果下载后无法直接运行,则查看电脑是否有F盘,如果没有,可修改jar包中的配置文件,将默认路径设置到其他盘,如C盘 说明: 1、这是myDownloader2.0自制下载器,在1.0版本的基础上做了修正和功能上的扩展; 2、该“myDownloader2.0资源包”包括: (1)“myDownloader_2.0”文件夹,保存下载器的源码, 其中包含的doc文件夹存放的是有MyEclipse,javadoc生成的关于下载器的API文档; (2)“jar包”文件夹,其中myDownloader2.0.jar可以双击运行下载器 (前提,电脑上装有JDK环境)。 3、可查看下载器“功能介绍”了解下载器具体功能。 功能介绍; 1、基本下载功能; 2、支持多任务多线程同时下载; 3、每个任务的线程数由用户在新建任务时自定义,缺省为5个线程; 4、任务下载过程中可以点击“线程+”或“线程-”即时增减线程; 5、选择任务,可以在任务信息栏中查看任务下载的信息; 6、对于正在下载和暂停的任务,可以再下载分块图示中查看文件的分块下载情况; (灰色为未下载,绿色为下载中,蓝色为完成) 7、若下载中删除任务,则任务会先暂停,再被移动到垃圾箱中; 8、整个界面分为三个视图:正在下载、已完成、垃圾箱; 9、下载过程可以暂停任务,点击开始可以继续下载; 10、对于失败的任务可以选择重新下载; 11、删除任务,如任务当前在正在下载或已完成,则任务将被删除到垃圾箱中, 若在垃圾箱中删除任务,则是彻底删除; 12、垃圾箱中的任务可以恢复回正在下载或已下载视图中; 13、可以点击“打开”,打开已完成的任务; 14、右键“打开文件夹”可以打开任务文件被下载到的文件夹; 15、清空垃圾箱; 16、菜单栏“关于”,查看版本及作者等信息; 17、菜单栏“功能介绍”,查看下载器的相关功能介绍; 18、程序退出前会自动暂停所有正在下载的任务,并保存到临时文件中, 下次启动程序可以继续下载; 19、程序启动时,会在电脑中检查创建myDownloader下载器的相应文件夹, 用于保存临时文件和日志文件; 文件夹具体路径可以到myDownloader.ini中进行配置,缺省为:F:/myDownloader 20、功能尚在扩展中,请多提意见和建议。 2009年10月 CASHUANGNING
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值