多线程多任务断点下载

简单的说,只要利用了HTTP协议(http://www.ietf.org/rfc/rfc2616.txt)中的如下字段来和服务器端交互,就可以实现文件下载的断点续传:


Range : 用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式如:


Range: bytes=0-499 下载第0-499字节范围的内容
Range: bytes=500-999  下载第500-999字节范围的内容
Range: bytes=-500  下载最后500字节的内容
Range: bytes=500-  下载从第500字节开始到文件结束部分的内容(这是最常用的一种格式)
Range: bytes=0-0,-1  下载第一以及最后一个字节的内容(这个看上去有点变态...)

 

Accept-Ranges : 用于服务器端到客户端的应答,客户端通过该字段可以判断服务器是否支持断点续传(注意RFC中注明了这一部分并不是必须的)。格式如下:


Accept-Ranges: bytes  表示支持以bytes为单位进行传输。
Accept-Ranges: none  表示不支持

 

Content-Ranges : 用于服务器端到客户端的应答,与Accept-Ranges在同一个报文内,通过该字段指定了返回的文件资源的字节范围。格式如下:

Content-Ranges: bytes 0-499/1234  大小为1234的文件的第0-499字节范围的内容
Content-Ranges: bytes 734-1233/1234  大小为1234字节的文件的第734-结尾范围的内容

 

据此我们可以知道,断点续传这个功能是需要客户端和服务器端同时支持才能完成。

 

Android平台面向开发者提供了DownloadManager这个服务(service),可以用来完成下载,同时异步地得到下载进度的实时更新提示。原生的浏览器,Android Market以及GMail等客户端都使用了该接口。该接口也部分的提供了断点续传功能:如果在下载过程中遇到网络错误,如信号中断等,DownloadManager会在网络恢复时尝试断点续传继续下载该文件。但不支持由用户发起的暂停然后断点续传。

 

要扩展该功能也不难,只要为下载任务新增一种状态(类似paused_by_user),以及相关逻辑即可,这里暂不赘述,把话题引到一些常见问题上。

 

1. 关于ETag

RFC中的定义有些抽象,简单的说,ETag可以用来标识/保证文件的唯一性或完整性,你可以把它看作是服务器为某个文件生产的唯一标识值,每次文件有更新该值就会变化。通过这种机制客户端可以检查某个文件在断点续传(当然它不仅仅用于断点续传)的前后是否有所改动:如果ETag改变了就应该重新下载整个文件以保证它的完整性。

 

但是在现实环境中,有一些服务器并不返回ETag字段,同时它又是支持断点续传的,这种情况下原生的Android就会认为服务器端不支持断点续传。这应该不是什么bug,仅仅是这么实现而已。还有更麻烦的情况是,有些服务器给了错误的ETag,但文件是从未更改的,这时候要想从客户端修改这个“bug”,估计只能忽略ETag值了。

 

2. 关于HTTP 206

RFC中定义了断点续传时服务器端的应答情况:如果支持且返回的内容如请求所要求的那样,是该文件的一部分,则使用HTTP 206状态码;如果不支持,或需要返回整个文件,则使用HTTP 200状态码。但是现实网络中有些服务器不管三七二十一,都返回200。没办法,如果还是想从客户端来修改这个“bug”,那就多做一些判断处理吧:如果服务器指定了“Content-Ranges”,就忽略HTTP 200的状态码。

 

附图一张,简述流程。

 

多线程多任务断点下载_第1张图片

 


补记:有一次被问起如何在原生的Android手机上暂停一个下载任务,回头再断点续传。我想是不是可以在下载过程中将手机信号关闭,下次再打开手机信号时,那个下载任务就可以自动接着续传了(当然前提是服务器支持)...这个用例没多大实用价值,懒得实测了。


多线程下载的原理是这样的:通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。如果你通过多个线程同时与服务器连接,那么你就可以榨取到较高的带宽了。

多线程多任务断点下载_第2张图片多线程多任务断点下载_第3张图片多线程多任务断点下载_第4张图片

FileDownload

任务类:
1:联网获取下载文件的信息,在SD卡上建立相应文件
2:给每个任务创建固定数目的线程,并给每个线程分配任务,然后开启
3:暂停、重启线程,根据每个线程的运行情况,通过Handler反馈信息到主线程(进度、是否结束)
源码:

[java]  view plain  copy
 
  1. public class FileDownloader {  
  2. private static final String TAG = "FileDownloader"//方便调试语句的编写  
  3. private String urlStr; //文件下载位置  
  4. private int fileLength; //下载文件长度  
  5. private int downloadLength; //文件已下载长度  
  6. private File saveFile; //SD卡上的存储文件  
  7. private int block; //每个线程下载的大小  
  8. private int threadCount = 2//该任务的线程数目  
  9. private DownloadThread[] loadThreads;   
  10. private Handler handler;  
  11. private Boolean isFinished = false//该任务是否完毕  
  12. private Boolean isPause = false;  
  13. private Context context;  
  14. RandomAccessFile accessFile;  
  15.   
  16. protected synchronized void append(int size){ //多线程访问需加锁  
  17. this.downloadLength += size;  
  18. }  
  19. /*protected synchronized void update(int threadId , int thisThreadDownloadlength){ //写入数据库 
  20.  
  21. }*/  
  22.   
  23. public FileDownloader(String urlStr, Handler handler,Context context){  
  24. this.urlStr = urlStr;  
  25. this.handler = handler;   
  26. this.loadThreads = new DownloadThread[threadCount];  
  27. this.context = context;  
  28. try {  
  29. URL url = new URL(urlStr);  
  30. HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  31. conn.setConnectTimeout(3000); //设置超时  
  32.   
  33. if(conn.getResponseCode() == 200){  
  34. fileLength = conn.getContentLength(); //获取下载文件长度  
  35. String tmpFile = urlStr.substring(urlStr.lastIndexOf('/')+1); //获取文件名  
  36. print(tmpFile);  
  37. saveFile = new File(Environment.getExternalStorageDirectory(),tmpFile);  
  38. accessFile= new RandomAccessFile(saveFile,"rws");  
  39. accessFile.setLength(fileLength); //设置本地文件和下载文件长度相同  
  40. accessFile.close();  
  41. }  
  42. catch (Exception e) {  
  43. // TODO Auto-generated catch block  
  44. e.printStackTrace();  
  45. }   
  46. }  
  47. public void Download(CompleteListener listener) throws Exception{  
  48.   
  49. URL url = new URL(urlStr);  
  50. HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  51. conn.setConnectTimeout(3000); //设置超时  
  52.   
  53. if(conn.getResponseCode() == 200){  
  54.   
  55. //发送消息设置进度条最大长度  
  56. Message msg = new Message();  
  57. msg.what = 0;  
  58. msg.getData().putInt("filelength", fileLength);  
  59. handler.sendMessage(msg);  
  60.   
  61. block = fileLength%threadCount == 0? fileLength/threadCount : fileLength/threadCount+1//计算线程下载量  
  62. print(Integer.toString(block));  
  63. for(int i = 0 ; i < threadCount ; i++){  
  64. print("哈哈,你妹啊");  
  65. this.loadThreads = new DownloadThread(context,this,urlStr,saveFile,block,i); //新建线程开始下载  
  66. print("嘿嘿");  
  67. this.loadThreads.start();  
  68. print("线程:"+Integer.toString(i)+" 开始下载");  
  69. }  
  70.   
  71. while(!isPause && !this.isFinished){  
  72. this.isFinished = true;   
  73. Thread.sleep(900);  
  74. for(int i = 0 ; i < threadCount ; i++){  
  75. if(loadThreads != null && !loadThreads.isFinished()){  
  76. this.isFinished = false;  
  77. }  
  78. }  
  79.   
  80. Message msg2 = new Message();  
  81. msg2.what = 1;  
  82. msg2.getData().putInt("currentlength", downloadLength); //  
  83. handler.sendMessage(msg2); //发送 消息更新进度条  
  84. //print(Integer.toString(downloadLength));  
  85. }  
  86. if(this.isFinished && listener != null){  
  87. listener.isComplete(downloadLength);  
  88. }  
  89. }  
  90. }  
  91.   
  92. private void print(String msg){ //打印提示消息  
  93. Log.d(FileDownloader.TAG,msg);  
  94. }  
  95.   
  96. public void setPause(){  
  97. isPause = true//该任务暂停  
  98. for(int i = 0 ;i < threadCount ; i++){  
  99. if(loadThreads!= null && !loadThreads.isFinished()){  
  100. loadThreads.setPause(); //设置该线程暂停  
  101. print(Integer.toString(i)+"草泥马");  
  102. }  
  103. }  
  104. }  
  105.   
  106. public void setResume(final CompleteListener listener) throws Exception{  
  107. isPause = false;  
  108. this.downloadLength = 0;  
  109. this.Download(new CompleteListener(){  
  110. public void isComplete(int size) {  
  111. listener.isComplete(size);  
  112. print("listener");  
  113. }  
  114. });  
  115. }  
  116. public Boolean isFinished(){  
  117. return this.isFinished;  
  118. }  
  119. }  



DownloadThread
下载线程类:
1:需要参数:下载地址 、存储位置、起始结束下载位置、结束位置。  (时间、数据库等信息)
2:读取数据库判断是否有下载记录,然后根据情况计算下载的起始与结束位置,进行下载
3:每读取一定数据需要通过任务类更新任务显示,并写入数据库(多线程访问数据库需要加锁)
4:根据任务类的需要,暂停则让该线程运行结束,而非阻塞该线程(考虑到android内存的需要)
源码:

[java]  view plain  copy
 
  1. public class DownloadThread extends Thread{  
  2. private String urlStr; //下载地址  
  3. private int startPosition; //开始下载位置  
  4. private int downloadLength=0//已下载字节数  
  5. private int endPosition; //结束位置  
  6. private File saveFile; //文件存储位置  
  7. private int threadId; //线程ID  
  8. private FileDownloader fileDownloader; //文件任务类  
  9. private Boolean isFinished = false//文件快是否下载完毕  
  10. private DatabaseUtil databaseUtil;  
  11. private Boolean isPause = false//默认为运行(没有暂停)  
  12. private Context context;  
  13.   
  14. public DownloadThread(String urlStr){  
  15. this.urlStr = urlStr;  
  16. }  
  17. public DownloadThread(Context context,FileDownloader fileDownloader,String urlStr , File file , int block ,int threadId){  
  18. this.context = context;  
  19. this.fileDownloader = fileDownloader; //  
  20. this.urlStr = urlStr;  
  21. this.saveFile = file;  
  22. this.threadId = threadId;  
  23. this.startPosition = threadId * block; //计算起始下载位置  
  24. this.endPosition = (threadId+1) * block -1//计算下载结束位置  
  25. }  
  26.   
  27. public void run(){  
  28. try{  
  29. RandomAccessFile accessFile = new RandomAccessFile(saveFile,"rwd");  
  30.   
  31. databaseUtil = new DatabaseUtil(context); //context设置需思考  
  32. ItemRecord record = databaseUtil.query(urlStr, threadId); //查询是否有记录存在  
  33. if(record != null){   
  34. downloadLength = record.getDownloadLength(); //读取已下载字节数   
  35. fileDownloader.append(downloadLength); //更新总下载字节数  
  36. print("线程"+Integer.toString(threadId)+"存在记录"+Integer.toString(downloadLength));  
  37. }else{  
  38. synchronized(DatabaseUtil.lock){  
  39. databaseUtil.insert(urlStr, threadId, downloadLength); //插入未完成线程任务  
  40. print("线程"+Integer.toString(threadId)+"不存在记录");  
  41. }  
  42. }  
  43. accessFile.seek(startPosition+downloadLength); //设置写入起始位置  
  44. if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){  
  45. print(Integer.toString(endPosition)+"endPosition");  
  46. print(Integer.toString(startPosition)+"startPosition");  
  47. print(Integer.toString(downloadLength)+"downloadLength");  
  48. isFinished = true//更新下载标志  
  49. print("线程:"+Integer.toString(threadId)+" 曾成功下载");  
  50. }else{  
  51. URL url = new URL(urlStr);  
  52. HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  53. conn.setConnectTimeout(3000); //设置超时  
  54. conn.setRequestMethod("GET");  
  55. int tmpStartPosition = startPosition + downloadLength;  
  56. conn.setRequestProperty("Range""bytes=" + tmpStartPosition + "-" + endPosition);  
  57.   
  58. if(conn.getResponseCode()==206){  
  59. InputStream inStream = conn.getInputStream();  
  60. byte[] buffer = new byte[1024];  
  61. int len = 0;  
  62. while(!isPause && (len = inStream.read(buffer))!= -1){  
  63. accessFile.write(buffer, 0, len); //写入文件  
  64. fileDownloader.append(len); //动态更新下载数、  
  65. downloadLength += len; //已下载数更新  
  66. //写入数据库  
  67. synchronized(DatabaseUtil.lock){  
  68. databaseUtil.update(urlStr, threadId, downloadLength);  
  69. }  
  70. }  
  71. if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){  
  72. print(Integer.toString(endPosition)+"endPosition");  
  73. print(Integer.toString(startPosition)+"startPosition");  
  74. print(Integer.toString(downloadLength)+"downloadLength");  
  75. isFinished = true//更新下载标志  
  76. print("线程:"+Integer.toString(threadId)+" 下载完毕");  
  77. }else{  
  78. print(Integer.toString(endPosition)+"endPosition");  
  79. print(Integer.toString(startPosition)+"startPosition");  
  80. print(Integer.toString(downloadLength)+"downloadLength");  
  81. print("线程:"+Integer.toString(threadId)+" 未下载完!");  
  82. }  
  83. }else{  
  84. print(conn.getResponseMessage());  
  85. print(Integer.toString(conn.getResponseCode()));  
  86. print("擦,线程:"+Integer.toString(threadId)+" 没开始下载");  
  87. }  
  88. }  
  89. }catch(Exception e){  
  90. e.printStackTrace();  
  91. }  
  92. }  
  93.   
  94. //返回下载结束的标志  
  95. public Boolean isFinished(){  
  96. return this.isFinished;  
  97. }  
  98. //设置暂停  
  99. public void setPause(){  
  100. this.isPause = true;  
  101.   
  102. }  
  103. private void print(String msg){  
  104. Log.d("DownloadThrea",msg);  
  105. }  
  106. }  


DatabaseHelper
建立数据库文件,并建表
源码:

[java]  view plain  copy
 
  1. public class DatabaseHelper extends SQLiteOpenHelper{  
  2. //private SQLiteDatabase database;  
  3. public DatabaseHelper(Context context , String name ,CursorFactory factory , int version) {  
  4. super(context, "downloadFile.db"null,1);  
  5. // TODO Auto-generated constructor stub  
  6. }  
  7.   
  8. @Override  
  9. public void onCreate(SQLiteDatabase db) {  
  10. Cursor cursor = null;  
  11. try {  
  12. Log.d("哈哈哈","leixun");  
  13. cursor = db.rawQuery("SELECT * FROM sqlite_master WHERE type = ? AND name = ?"new String[]{"table","info"});  
  14. if(cursor.moveToNext()){  
  15. Log.d("DatabaseHelper","该表已经存在");  
  16. }else{  
  17. Log.d("DatabaseHelper","该表不存在 ,马上建立");  
  18. db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +  
  19. "downloadlength INTEGER , PRIMARY KEY(path,threadid))");  
  20. }  
  21. cursor.close();  
  22. catch (Exception e) {  
  23. e.printStackTrace();  
  24. }  
  25. /*db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " + 
  26. "downloadlength INTEGER , PRIMARY KEY(path,threadid))");*/  
  27. }  
  28.   
  29. @Override  
  30. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  31. // TODO Auto-generated method stub  
  32.   
  33. }  
  34. }  



DatabaseUtil
1:增删改查任务列表
2:通过静态常量lock实现数据库写入时的并发锁功能
源码:
[java]  view plain  copy
 
  1. public class DatabaseUtil {  
  2. private DatabaseHelper helper;  
  3. public static final String lock = "访问";  
  4. public DatabaseUtil(Context context){  
  5. helper = new DatabaseHelper(context, "downloadFile.db"null,1);  
  6. }  
  7. //添加记录  
  8. public void insert(String path , int threadid , int downloadlength){  
  9. SQLiteDatabase db = helper.getWritableDatabase();  
  10. db.execSQL("INSERT INTO info(path,threadid,downloadlength) VALUES (?,?,?)"new Object[]{path,threadid,downloadlength});  
  11. print("成功添加");  
  12. db.close();  
  13. }  
  14. //删除记录  
  15. public void delete(String path , int threadid){  
  16. SQLiteDatabase db = helper.getWritableDatabase();  
  17. db.execSQL("DELETE FROM info WHERE path = ? AND threadid = ?",new Object[]{path,threadid});  
  18. print("成功删除");  
  19. db.close();  
  20. }  
  21. //更新记录  
  22. public void update(String path , int threadid , int downloadlength){  
  23. SQLiteDatabase db = helper.getWritableDatabase();  
  24. //print("准备更新1");  
  25. db.execSQL("UPDATE info SET downloadlength = ? WHERE path = ? AND threadid = ?",new Object[]{downloadlength,path,threadid});  
  26. //print("成功更新2");  
  27. db.close();  
  28. }  
  29. //查询线程是否存在  
  30. public ItemRecord query(String path, int threadid){  
  31. SQLiteDatabase db = helper.getWritableDatabase();  
  32. Cursor c = db.rawQuery("SELECT path,threadid,downloadlength FROM info WHERE path = ? AND threadid = ?"new String[]{path,Integer.toString(threadid)});  
  33. ItemRecord record = null;  
  34. if(c.moveToNext()){  
  35. record = new ItemRecord(c.getString(0),c.getInt(1),c.getInt(2));  
  36. }  
  37. c.close();  
  38. db.close();  
  39. return record;  
  40. }  
  41. //查询未完成任务  
  42. public List<String> query(String path){  
  43. print("List<String> query 开始");  
  44. SQLiteDatabase db = helper.getWritableDatabase();  
  45. Cursor c = db.rawQuery("SELECT DISTINCT path FROM info"new String[]{path});  
  46. List<String> arrayList = new ArrayList<String>();  
  47. while(c.moveToNext()){  
  48. arrayList.add(c.getString(0));  
  49. }  
  50. c.close();  
  51. db.close();  
  52. print("List<String> query 结束");  
  53. return arrayList;  
  54. }  
  55. //调试信息输出  
  56. private void print(String msg){  
  57. Log.d("DatabaseUtil",msg);  
  58. }  
  59. }  

ItemRecord
任务记录类,方便数据库操作而建立的任务记录类

[java]  view plain  copy
 
  1. public class ItemRecord {  
  2.   
  3. private String path;  
  4. private int threadId;  
  5. private int downloadLength;  
  6.   
  7. public ItemRecord(String path,int threadId,int downloadLength){  
  8. this.path = path;  
  9. this.threadId = threadId;  
  10. this.downloadLength = downloadLength;  
  11. }  
  12.   
  13. public String getPath() {  
  14. return path;  
  15. }  
  16.   
  17. public void setPath(String path) {  
  18. this.path = path;  
  19. }  
  20.   
  21. public int getThreadId() {  
  22. return threadId;  
  23. }  
  24.   
  25. public void setThreadId(int threadId) {  
  26. this.threadId = threadId;  
  27. }  
  28.   
  29. public int getDownloadLength() {  
  30. return downloadLength;  
  31. }  
  32.   
  33. public void setDownloadLength(int downloadLength) {  
  34. this.downloadLength = downloadLength;  
  35. }  
  36.   
  37.   
  38. }  


ApplicationUrlSpinner操作用来完成应用名称与下载地址的对应关系源码:

[java]  view plain  copy
 
  1. public class ApplicationUrl {  
  2. private String applicationName = "";  
  3. private String applicationUrl = "";  
  4.   
  5. public String getApplicationName() {  
  6. return applicationName;  
  7. }  
  8. public void setApplicationName(String applicationName) {  
  9. this.applicationName = applicationName;  
  10. }  
  11. public String getApplicationUrl() {  
  12. return applicationUrl;  
  13. }  
  14. public void setApplicationUrl(String applicationUrl) {  
  15. this.applicationUrl = applicationUrl;  
  16. }  
  17.   
  18. public String toString() {  
  19. // TODO Auto-generated method stub  
  20. return applicationName;  
  21. }  
  22. }  


CompleteListener下载完成判断接口:

[java]  view plain  copy
 
  1. public interface CompleteListener {  
  2.    public void isComplete(int size);  
  3. }  



MainActivity
1:动态添加下载任务
2:根据任务动态添加布局,并给相应按钮添加监听,开启下载任务
源码:

[java]  view plain  copy
 
  1. public class MaintActivity extends Activity{  
  2.   
  3. private LayoutInflater inflater;  
  4. private LinearLayout root;  
  5. private Spinner spinnerApp;  
  6. private List<ApplicationUrl> appUrl;  
  7. private ArrayAdapter appAdapter;  
  8. private Button startButton;  
  9. FileDownloader fileDownloader;  
  10. private String url5 = "http://update.android.doplive.com.cn/dopoolv2.4.player.apk";  
  11. private String url5App = "Dopool手机电视";  
  12. private String url = "http://www.ipmsg.org.cn/downloads/ipmsg.apk";  
  13. private String urlApp = "飞鸽传书";  
  14. private String url2 = "http://down1.cnmo.com/app/a135/aiku_2.0.apk";  
  15. private String url2App = "爱酷天气";  
  16. private ApplicationUrl userChoose;  
  17.   
  18. @Override  
  19. public void onCreate(Bundle savedInstanceState){  
  20. super.onCreate(savedInstanceState);  
  21. setContentView(R.layout.main_layout);  
  22.   
  23. inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);  
  24. root = (LinearLayout)findViewById(R.id.root);  
  25.   
  26. spinnerApp = (Spinner)findViewById(R.id.urlSpinner);  
  27. startButton = (Button)findViewById(R.id.startBtn);  
  28.   
  29. //createDownloadTask(url); //创建任务 添加View  
  30. //createDownloadTask(url2);  
  31.   
  32. init();  
  33. }  
  34.   
  35. public void init(){  
  36. //-----------------给Spinner制作数据----------------  
  37. appUrl = new ArrayList<ApplicationUrl>();   
  38. ApplicationUrl[] tmp = new ApplicationUrl[3];  
  39. for(int i = 0 ; i<3;i++){  
  40. tmp = new ApplicationUrl();  
  41. }  
  42. tmp[0].setApplicationName(urlApp);  
  43. tmp[0].setApplicationUrl(url);  
  44. tmp[1].setApplicationName(url2App);  
  45. tmp[1].setApplicationUrl(url2);  
  46. tmp[2].setApplicationName(url5App);  
  47. tmp[2].setApplicationUrl(url5);  
  48. appUrl.add(tmp[0]);  
  49. appUrl.add(tmp[1]);  
  50. appUrl.add(tmp[2]);  
  51.   
  52. userChoose = new ApplicationUrl();  
  53. userChoose.setApplicationName(tmp[0].getApplicationName());  
  54. userChoose.setApplicationUrl(tmp[0].getApplicationUrl());  
  55. appAdapter = new ArrayAdapter(MaintActivity.this,android.R.layout.simple_spinner_item,appUrl);  
  56. appAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);  
  57. spinnerApp.setAdapter(appAdapter);  
  58. spinnerApp.setSelection(0true);  
  59. spinnerApp.setOnItemSelectedListener(new OnItemSelectedListener(){  
  60.   
  61. @Override  
  62. public void onItemSelected(AdapterView<?> arg0, View arg1,  
  63. int position, long arg3) {  
  64. print("onItemSelected:"+appUrl.get(position).getApplicationName()+Integer.toString(position));  
  65. userChoose.setApplicationName(appUrl.get(position).getApplicationName());  
  66. userChoose.setApplicationUrl(appUrl.get(position).getApplicationUrl());  
  67. }  
  68. @Override  
  69. public void onNothingSelected(AdapterView<?> arg0) {  
  70. // TODO Auto-generated method stub  
  71.   
  72. }  
  73. });  
  74. startButton.setOnClickListener(new View.OnClickListener() {   
  75. @Override  
  76. public void onClick(View v) {  
  77. if(!appUrl.isEmpty()){  
  78. createDownloadTask(userChoose);  
  79. }else{  
  80. Toast.makeText(MaintActivity.this"没有应用程序可下载!", Toast.LENGTH_LONG).show();  
  81. }  
  82. int count = appUrl.size();  
  83. print(Integer.toString(count));  
  84. for(int i = 0 ;i< count;i++){  
  85. if(appUrl.get(i).getApplicationName().equals(userChoose.getApplicationName())){  
  86. print("+++++++++++++++++"+Integer.toString(i)+"++++++++++++++++");  
  87. printappUrl();  
  88. appUrl.remove(i);  
  89. printappUrl();  
  90. count = appUrl.size();  
  91. print("for循环找到"+Integer.toString(count));  
  92. appAdapter.notifyDataSetChanged();  
  93. if(!appUrl.isEmpty()){  
  94. spinnerApp.setSelection(0,true);  
  95. userChoose.setApplicationName(appUrl.get(0).getApplicationName()); //擦 必须这样赋值  
  96. userChoose.setApplicationUrl(appUrl.get(0).getApplicationUrl());  
  97.   
  98. }  
  99. printappUrl();  
  100. print(userChoose.getApplicationName()+"数据源已更新"+Integer.toString(count));  
  101. }else{  
  102. print("数据源未更新");  
  103. }  
  104. }  
  105. }  
  106. });  
  107.   
  108. }  
  109.   
  110. @Override  
  111. public boolean onCreateOptionsMenu(Menu menu) {  
  112. getMenuInflater().inflate(R.menu.main_layout, menu);  
  113. return true;  
  114. }  
  115.   
  116.   
  117. private void createDownloadTask(ApplicationUrl path){  
  118. LayoutInflater inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE); //获取系统服务layoutinflater  
  119.   
  120. LinearLayout linearLayout = (LinearLayout)inflater.inflate(R.layout.download_layout, null);  
  121.   
  122. LinearLayout childLayout = (LinearLayout)linearLayout.getChildAt(0);  
  123. ProgressBar progressBar = (ProgressBar)childLayout.getChildAt(0); //获取进度条  
  124. TextView textView = (TextView)childLayout.getChildAt(1); //获取textview  
  125.   
  126. Button button = (Button)linearLayout.getChildAt(1); //获取下载按钮  
  127. try{  
  128. button.setOnClickListener(new MyListener(progressBar,textView,path));  
  129. root.addView(linearLayout); //将创建的View添加到主线程 的布局页面中  
  130. }catch(Exception e){  
  131. e.printStackTrace();  
  132. }  
  133. }  
  134.   
  135. private final class MyListener implements OnClickListener{  
  136. private ProgressBar pb ;  
  137. private TextView tv;  
  138. private String url;  
  139. private FileDownloader fileDownloader;  
  140. private String name ;   
  141.   
  142. public MyListener(ProgressBar pb ,TextView tv, ApplicationUrl url){  
  143. this.pb = pb;  
  144. this.tv = tv;  
  145. this.url = url.getApplicationUrl();  
  146. this.name = url.getApplicationName();  
  147. }  
  148.   
  149. public void onClick(View v) {  
  150. final Button pauseButton = (Button)v;  
  151. final Handler mainHandler = new Handler(){  
  152.   
  153. @Override  
  154. public void handleMessage(Message msg) {  
  155. switch(msg.what){  
  156. case 2:  
  157. pauseButton.setText("安装");  
  158. break;  
  159. }  
  160. }  
  161.   
  162. };  
  163. if(pauseButton.getText().equals("开始")){  
  164. pauseButton.setText("暂停");  
  165. new Thread(){  
  166. public void run(){  
  167. try{  
  168. fileDownloader = new FileDownloader(url,handler,MaintActivity.this);  
  169. fileDownloader.Download(new CompleteListener(){  
  170. public void isComplete(int size) {  
  171. Message msg = new Message();  
  172. msg.what = 2;  
  173. mainHandler.sendMessage(msg);  
  174. }  
  175. });  
  176. }catch(Exception e){  
  177. e.printStackTrace();  
  178. }  
  179. }  
  180. }.start();  
  181. }else if(pauseButton.getText().equals("暂停")){  
  182. print("暂停");  
  183. fileDownloader.setPause();  
  184. pauseButton.setText("继续");  
  185. }else if(pauseButton.getText().equals("继续")){  
  186. print("继续");  
  187. pauseButton.setText("暂停");  
  188. new Thread(){  
  189. public void run(){  
  190. try{  
  191. fileDownloader.setResume(new CompleteListener(){  
  192. public void isComplete(int size) {  
  193. print("妹纸");  
  194. Message msg = new Message();  
  195. msg.what = 2;  
  196. mainHandler.sendMessage(msg);  
  197. }  
  198. });  
  199. }catch(Exception e){  
  200. e.printStackTrace();  
  201. }  
  202. }  
  203. }.start();  
  204. }else if(pauseButton.getText().equals("安装")){  
  205. Intent intent = new Intent(Intent.ACTION_VIEW);  
  206. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
  207. String installPath = Environment.getExternalStorageDirectory() + "/"+url.substring(url.lastIndexOf('/')+1);  
  208. print(installPath);  
  209. intent.setDataAndType(Uri.fromFile(new File(installPath)), "application/vnd.android.package-archive");  
  210. MaintActivity.this.startActivity(intent);  
  211. }  
  212. }  
  213.   
  214. private Handler handler = new Handler(){  
  215. int fileLength = 1;  
  216. @Override  
  217. public void handleMessage(Message msg) {  
  218. switch(msg.what){  
  219. case 0//得到进度条的最大长度  
  220. fileLength = msg.getData().getInt("filelength");  
  221. pb.setMax(fileLength); //设置进度条最大长度  
  222. break;  
  223. case 1//设置进度条现在的长度  
  224. int currentLength = msg.getData().getInt("currentlength");  
  225. pb.setProgress(currentLength); //设置当前进度条长度  
  226. tv.setText(name+" 已下载:"+currentLength*100 / fileLength+"%");  
  227. if(currentLength == fileLength){  
  228. tv.setText(name+" 下载完成:"+currentLength*100 / fileLength+"%");  
  229. }  
  230. break;  
  231. default: print("handleMessage msg.what 没有曲子好");  
  232. }  
  233. }  
  234.   
  235. };  
  236.   
  237. }  
  238.   
  239. /** 
  240. * 调试信息输出 
  241. * @param msg 
  242. */  
  243. private void print(String msg){  
  244. Log.d("MainActivity",msg);  
  245. }  
  246.   
  247. private void printappUrl(){  
  248. Log.d("=========================","=========================");  
  249. for(int i = 0;appUrl!=null && i<appUrl.size();i++){  
  250. print(appUrl.get(i).getApplicationName());  
  251. }  
  252. Log.d("=========================","=========================");  
  253. }  
  254. }  

源码下载


你可能感兴趣的:(多线程多任务断点下载)