android 多线程断点续传下载 三

今天跟大家一起分享下android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基本要领,我们先一起简单回顾下它的基本原理。

http://blog.csdn.net/shimiso/article/details/6763664  android 多线程断点续传下载 一

http://blog.csdn.net/shimiso/article/details/6763986  android 多线程断点续传下载 二

什么是多线程下载?

多线程下载其实就是迅雷,BT一些下载原理,通过多个线程同时和服务器连接,那么你就可以榨取到较高的带宽了,大致做法是将文件切割成N块,每块交给单独一个线程去下载,各自下载完成后将文件块组合成一个文件,程序上要完成做切割和组装的小算法

什么是断点续传?

断点续传,就是当我们下载未结束时候,退出保存下载进度,当下次打开继续下载的时接着上次的进度继续下载,不用每次下载都重新开始,那么有关断点续传的原理和实现手段,可参考我以前的一篇总结http://blog.csdn.net/shimiso/article/details/5956314 里面详细讲解http协议断点续传的原理,务必要看懂,否则你无法真正理解本节代码

怎么完成多线程断点续传?

将两者合二为一需要程序记住每个文件块的下载进度,并保存入库,当下载程序启动时候你需要判断程序是否已经下载过该文件,并取出各个文件块的保存记录,换算出下载进度继续下载,在这里你需要掌握java多线程的基本知识,handler的使用,以及集合,算法,文件操作等基本技能,同时还要解决sqlite数据库的同步问题,因为它是不太怎么支持多线程操作的,控制不好经常会出现库被锁定的异常,同时在android2.3以后就不能activity中直接操作http,否则你将收到系统送上的NetworkOnMainThreadException异常,在UI体验上一定记住要使用异步完成,既然大致思路已经清楚,下面我们开始分析程序:

[java] view plaincopy
  1. package cn.demo.DBHelper;  
  2.    
  3.  import android.content.Context;  
  4. import android.database.sqlite.SQLiteDatabase;  
  5. import android.database.sqlite.SQLiteOpenHelper;  
  6.    
  7.      /** 
  8.       * 建立一个数据库帮助类 
  9.       */  
  10.  public class DBHelper extends SQLiteOpenHelper {  
  11.      //download.db-->数据库名  
  12.      public DBHelper(Context context) {  
  13.          super(context, "download.db"null1);  
  14.      }  
  15.        
  16.      /** 
  17.       * 在download.db数据库下创建一个download_info表存储下载信息 
  18.       */  
  19.      @Override  
  20.      public void onCreate(SQLiteDatabase db) {  
  21.          db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "  
  22.                  + "start_pos integer, end_pos integer, compelete_size integer,url char)");  
  23.      }  
  24.      @Override  
  25.      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  26.    
  27.      }  
  28.    
  29.  }  

 

 数据库操作要借助单例和同步,来保证线程的执行顺序,以免多个线程争相抢用sqlite资源导致异常出现

[java] view plaincopy
  1. package cn.demo.Dao;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.content.Context;  
  7. import android.database.Cursor;  
  8. import android.database.sqlite.SQLiteDatabase;  
  9. import cn.demo.DBHelper.DBHelper;  
  10. import cn.demo.entity.DownloadInfo;  
  11.   
  12. /** 
  13.  *  
  14.  * 一个业务类 
  15.  */  
  16. public class Dao {    
  17.     private static Dao dao=null;  
  18.     private Context context;   
  19.     private  Dao(Context context) {   
  20.         this.context=context;  
  21.     }  
  22.     public static  Dao getInstance(Context context){  
  23.         if(dao==null){  
  24.             dao=new Dao(context);   
  25.         }  
  26.         return dao;  
  27.     }  
  28.     public  SQLiteDatabase getConnection() {  
  29.         SQLiteDatabase sqliteDatabase = null;  
  30.         try {   
  31.             sqliteDatabase= new DBHelper(context).getReadableDatabase();  
  32.         } catch (Exception e) {    
  33.         }  
  34.         return sqliteDatabase;  
  35.     }  
  36.   
  37.     /** 
  38.      * 查看数据库中是否有数据 
  39.      */  
  40.     public synchronized boolean isHasInfors(String urlstr) {  
  41.         SQLiteDatabase database = getConnection();  
  42.         int count = -1;  
  43.         Cursor cursor = null;  
  44.         try {  
  45.             String sql = "select count(*)  from download_info where url=?";  
  46.             cursor = database.rawQuery(sql, new String[] { urlstr });  
  47.             if (cursor.moveToFirst()) {  
  48.                 count = cursor.getInt(0);  
  49.             }   
  50.         } catch (Exception e) {  
  51.             e.printStackTrace();  
  52.         } finally {  
  53.             if (null != database) {  
  54.                 database.close();  
  55.             }  
  56.             if (null != cursor) {  
  57.                 cursor.close();  
  58.             }  
  59.         }  
  60.         return count == 0;  
  61.     }  
  62.   
  63.     /** 
  64.      * 保存 下载的具体信息 
  65.      */  
  66.     public synchronized void saveInfos(List<DownloadInfo> infos) {  
  67.         SQLiteDatabase database = getConnection();  
  68.         try {  
  69.             for (DownloadInfo info : infos) {  
  70.                 String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";  
  71.                 Object[] bindArgs = { info.getThreadId(), info.getStartPos(),  
  72.                         info.getEndPos(), info.getCompeleteSize(),  
  73.                         info.getUrl() };  
  74.                 database.execSQL(sql, bindArgs);  
  75.             }  
  76.         } catch (Exception e) {  
  77.             e.printStackTrace();  
  78.         } finally {  
  79.             if (null != database) {  
  80.                 database.close();  
  81.             }  
  82.         }  
  83.     }  
  84.   
  85.     /** 
  86.      * 得到下载具体信息 
  87.      */  
  88.     public synchronized List<DownloadInfo> getInfos(String urlstr) {  
  89.         List<DownloadInfo> list = new ArrayList<DownloadInfo>();  
  90.         SQLiteDatabase database = getConnection();  
  91.         Cursor cursor = null;  
  92.         try {  
  93.             String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";  
  94.             cursor = database.rawQuery(sql, new String[] { urlstr });  
  95.             while (cursor.moveToNext()) {  
  96.                 DownloadInfo info = new DownloadInfo(cursor.getInt(0),  
  97.                         cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),  
  98.                         cursor.getString(4));  
  99.                 list.add(info);  
  100.             }  
  101.         } catch (Exception e) {  
  102.             e.printStackTrace();  
  103.         } finally {  
  104.             if (null != database) {  
  105.                 database.close();  
  106.             }  
  107.             if (null != cursor) {  
  108.                 cursor.close();  
  109.             }  
  110.         }  
  111.         return list;  
  112.     }  
  113.   
  114.     /** 
  115.      * 更新数据库中的下载信息 
  116.      */  
  117.     public synchronized void updataInfos(int threadId, int compeleteSize, String urlstr) {  
  118.         SQLiteDatabase database = getConnection();  
  119.         try {  
  120.             String sql = "update download_info set compelete_size=? where thread_id=? and url=?";  
  121.             Object[] bindArgs = { compeleteSize, threadId, urlstr };  
  122.             database.execSQL(sql, bindArgs);  
  123.         } catch (Exception e) {  
  124.             e.printStackTrace();  
  125.         } finally {  
  126.             if (null != database) {  
  127.                 database.close();  
  128.             }  
  129.         }  
  130.     }  
  131.   
  132.     /** 
  133.      * 下载完成后删除数据库中的数据 
  134.      */  
  135.     public synchronized void delete(String url) {  
  136.         SQLiteDatabase database = getConnection();  
  137.         try {  
  138.             database.delete("download_info""url=?"new String[] { url });  
  139.         } catch (Exception e) {  
  140.             e.printStackTrace();  
  141.         } finally {  
  142.             if (null != database) {  
  143.                 database.close();  
  144.             }  
  145.         }  
  146.     }  
  147. }  


 

[java] view plaincopy
  1. package cn.demo.download;  
  2.   
  3. import java.util.List;  
  4. import java.util.Map;  
  5.   
  6. import android.content.Context;  
  7. import android.view.LayoutInflater;  
  8. import android.view.View;  
  9. import android.view.View.OnClickListener;  
  10. import android.view.ViewGroup;  
  11. import android.widget.BaseAdapter;  
  12. import android.widget.Button;  
  13. import android.widget.TextView;  
  14.   
  15.   
  16.   
  17.   
  18. public class DownLoadAdapter extends BaseAdapter{  
  19.        
  20.     private LayoutInflater mInflater;  
  21.     private List<Map<String, String>> data;  
  22.     private Context context;  
  23.     private OnClickListener click;  
  24.       
  25.     public DownLoadAdapter(Context context,List<Map<String, String>> data) {  
  26.         this.context=context;  
  27.         mInflater = LayoutInflater.from(context);  
  28.         this.data=data;  
  29.     }  
  30.     public void refresh(List<Map<String, String>> data) {  
  31.         this.data=data;  
  32.         this.notifyDataSetChanged();  
  33.     }  
  34.     public void setOnclick(OnClickListener click) {  
  35.          this.click=click;  
  36.     }  
  37.       
  38.       
  39.     @Override  
  40.     public int getCount() {  
  41.         return data.size();  
  42.     }  
  43.   
  44.     @Override  
  45.     public Object getItem(int position) {  
  46.         return data.get(position);  
  47.     }  
  48.   
  49.     @Override  
  50.     public long getItemId(int position) {  
  51.         return position;  
  52.     }  
  53.   
  54.     @Override  
  55.     public View getView(final int position, View convertView, ViewGroup parent) {  
  56.         final Map<String, String> bean=data.get(position);  
  57.         ViewHolder holder = null;  
  58.         if (convertView == null) {  
  59.             convertView = mInflater.inflate(R.layout.list_item, null);  
  60.             holder = new ViewHolder();   
  61.             holder.resouceName=(TextView) convertView.findViewById(R.id.tv_resouce_name);  
  62.             holder.startDownload=(Button) convertView.findViewById(R.id.btn_start);  
  63.             holder.pauseDownload=(Button) convertView.findViewById(R.id.btn_pause);  
  64.             convertView.setTag(holder);  
  65.         } else {  
  66.             holder = (ViewHolder) convertView.getTag();  
  67.         }   
  68.         holder.resouceName.setText(bean.get("name"));   
  69.         return convertView;  
  70.     }  
  71.     public OnClickListener getClick() {  
  72.         return click;  
  73.     }  
  74.     public void setClick(OnClickListener click) {  
  75.         this.click = click;  
  76.     }  
  77.     private class ViewHolder {   
  78.         public TextView resouceName;  
  79.         public Button startDownload;  
  80.         public Button pauseDownload;  
  81.         
  82.           
  83.        
  84.     }  
  85.       
  86. }  



注意子线程不要影响主UI线程,灵活运用task和handler,各取所长,保证用户体验,handler通常在主线程中有利于专门负责处理UI的一些工作

[java] view plaincopy
  1. package cn.demo.download;  
  2.    
  3.  import java.util.ArrayList;  
  4. import java.util.HashMap;  
  5. import java.util.List;  
  6. import java.util.Map;  
  7.   
  8. import android.app.ListActivity;  
  9. import android.os.AsyncTask;  
  10. import android.os.Bundle;  
  11. import android.os.Handler;  
  12. import android.os.Message;  
  13. import android.view.View;  
  14. import android.widget.Button;  
  15. import android.widget.LinearLayout;  
  16. import android.widget.LinearLayout.LayoutParams;  
  17. import android.widget.ProgressBar;   
  18. import android.widget.TextView;  
  19. import android.widget.Toast;  
  20. import cn.demo.entity.LoadInfo;  
  21. import cn.demo.service.Downloader;  
  22.   
  23.  public class MainActivity extends ListActivity {   
  24.      // 固定下载的资源路径,这里可以设置网络上的地址  
  25.      private static final String URL = "http://download.haozip.com/";  
  26.      // 固定存放下载的音乐的路径:SD卡目录下  
  27.      private static final String SD_PATH = "/mnt/sdcard/";  
  28.      // 存放各个下载器  
  29.      private Map<String, Downloader> downloaders = new HashMap<String, Downloader>();  
  30.      // 存放与下载器对应的进度条  
  31.      private Map<String, ProgressBar> ProgressBars = new HashMap<String, ProgressBar>();  
  32.      /** 
  33.       * 利用消息处理机制适时更新进度条 
  34.       */  
  35.      private Handler mHandler = new Handler() {  
  36.          public void handleMessage(Message msg) {  
  37.              if (msg.what == 1) {  
  38.                  String url = (String) msg.obj;  
  39.                  int length = msg.arg1;  
  40.                  ProgressBar bar = ProgressBars.get(url);  
  41.                  if (bar != null) {  
  42.                      // 设置进度条按读取的length长度更新  
  43.                      bar.incrementProgressBy(length);  
  44.                      if (bar.getProgress() == bar.getMax()) {  
  45.                          LinearLayout layout = (LinearLayout) bar.getParent();  
  46.                          TextView resouceName=(TextView)layout.findViewById(R.id.tv_resouce_name);  
  47.                          Toast.makeText(MainActivity.this"["+resouceName.getText()+"]下载完成!", Toast.LENGTH_SHORT).show();  
  48.                          // 下载完成后清除进度条并将map中的数据清空  
  49.                          layout.removeView(bar);  
  50.                          ProgressBars.remove(url);  
  51.                          downloaders.get(url).delete(url);  
  52.                          downloaders.get(url).reset();  
  53.                          downloaders.remove(url);  
  54.                            
  55.                          Button btn_start=(Button)layout.findViewById(R.id.btn_start);  
  56.                          Button btn_pause=(Button)layout.findViewById(R.id.btn_pause);  
  57.                          btn_pause.setVisibility(View.GONE);  
  58.                          btn_start.setVisibility(View.GONE);  
  59.                      }  
  60.                  }  
  61.              }  
  62.          }  
  63.      };  
  64.      @Override  
  65.      public void onCreate(Bundle savedInstanceState) {  
  66.          super.onCreate(savedInstanceState);  
  67.          setContentView(R.layout.main);   
  68.          showListView();  
  69.      }  
  70.      // 显示listView,这里可以随便添加  
  71.      private void showListView() {  
  72.          List<Map<String, String>> data = new ArrayList<Map<String, String>>();  
  73.          Map<String, String> map = new HashMap<String, String>();  
  74.          map.put("name""haozip_v3.1.exe");  
  75.          data.add(map);  
  76.          map = new HashMap<String, String>();  
  77.          map.put("name""haozip_v3.1_hj.exe");  
  78.          data.add(map);  
  79.          map = new HashMap<String, String>();  
  80.          map.put("name""haozip_v2.8_x64_tiny.exe");  
  81.          data.add(map);  
  82.          map = new HashMap<String, String>();  
  83.          map.put("name""haozip_v2.8_tiny.exe");  
  84.          data.add(map);  
  85.          DownLoadAdapter adapter=new DownLoadAdapter(this,data);    
  86.          setListAdapter(adapter);  
  87.            
  88.      }  
  89.      /** 
  90.       * 响应开始下载按钮的点击事件 
  91.       */  
  92.      public void startDownload(View v) {  
  93.          // 得到textView的内容   
  94.          LinearLayout layout = (LinearLayout) v.getParent();  
  95.          String resouceName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();  
  96.          String urlstr = URL + resouceName;  
  97.          String localfile = SD_PATH + resouceName;  
  98.          //设置下载线程数为4,这里是我为了方便随便固定的  
  99.          String threadcount = "4";  
  100.          DownloadTask downloadTask=new DownloadTask(v);  
  101.          downloadTask.execute(urlstr,localfile,threadcount);  
  102.          
  103.      };  
  104.     class DownloadTask extends AsyncTask<String, Integer, LoadInfo>{  
  105.         Downloader downloader=null;   
  106.         View v=null;  
  107.         String urlstr=null;  
  108.         public DownloadTask(final View v){  
  109.             this.v=v;  
  110.         }    
  111.         @Override  
  112.         protected void onPreExecute() {   
  113.             Button btn_start=(Button)((View)v.getParent()).findViewById(R.id.btn_start);  
  114.             Button btn_pause=(Button)((View)v.getParent()).findViewById(R.id.btn_pause);  
  115.             btn_start.setVisibility(View.GONE);  
  116.             btn_pause.setVisibility(View.VISIBLE);  
  117.         }  
  118.         @Override  
  119.         protected LoadInfo doInBackground(String... params) {  
  120.             urlstr=params[0];  
  121.             String localfile=params[1];  
  122.             int threadcount=Integer.parseInt(params[2]);  
  123.              // 初始化一个downloader下载器  
  124.              downloader = downloaders.get(urlstr);  
  125.              if (downloader == null) {  
  126.                  downloader = new Downloader(urlstr, localfile, threadcount, MainActivity.this, mHandler);  
  127.                  downloaders.put(urlstr, downloader);  
  128.              }  
  129.              if (downloader.isdownloading())  
  130.                  return null;  
  131.              // 得到下载信息类的个数组成集合  
  132.              return downloader.getDownloaderInfors();   
  133.         }  
  134.         @Override  
  135.         protected void onPostExecute(LoadInfo loadInfo) {  
  136.             if(loadInfo!=null){  
  137.                  // 显示进度条  
  138.                  showProgress(loadInfo, urlstr, v);  
  139.                  // 调用方法开始下载  
  140.                  downloader.download();  
  141.             }   
  142.         }  
  143.            
  144.      };  
  145.      /** 
  146.       * 显示进度条 
  147.       */  
  148.      private void showProgress(LoadInfo loadInfo, String url, View v) {  
  149.          ProgressBar bar = ProgressBars.get(url);  
  150.          if (bar == null) {  
  151.              bar = new ProgressBar(thisnull, android.R.attr.progressBarStyleHorizontal);  
  152.              bar.setMax(loadInfo.getFileSize());  
  153.              bar.setProgress(loadInfo.getComplete());  
  154.              ProgressBars.put(url, bar);  
  155.              LinearLayout.LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, 5);  
  156.              ((LinearLayout) ((LinearLayout) v.getParent()).getParent()).addView(bar, params);  
  157.          }  
  158.      }  
  159.      /** 
  160.       * 响应暂停下载按钮的点击事件 
  161.       */  
  162.      public void pauseDownload(View v) {  
  163.          LinearLayout layout = (LinearLayout) v.getParent();  
  164.          String resouceName = ((TextView) layout.findViewById(R.id.tv_resouce_name)).getText().toString();  
  165.          String urlstr = URL + resouceName;  
  166.          downloaders.get(urlstr).pause();  
  167.          Button btn_start=(Button)((View)v.getParent()).findViewById(R.id.btn_start);  
  168.          Button btn_pause=(Button)((View)v.getParent()).findViewById(R.id.btn_pause);  
  169.          btn_pause.setVisibility(View.GONE);  
  170.          btn_start.setVisibility(View.VISIBLE);  
  171.      }  
  172.  }  


 

 这是一个信息的实体,记录了一些字典信息,可以认为是一个简单bean对象
[java] view plaincopy
  1. package cn.demo.entity;  
  2.  /** 
  3.   *创建一个下载信息的实体类 
  4.   */  
  5.  public class DownloadInfo {  
  6.      private int threadId;//下载器id  
  7.      private int startPos;//开始点  
  8.      private int endPos;//结束点  
  9.      private int compeleteSize;//完成度  
  10.      private String url;//下载器网络标识  
  11.      public DownloadInfo(int threadId, int startPos, int endPos,  
  12.              int compeleteSize,String url) {  
  13.          this.threadId = threadId;  
  14.          this.startPos = startPos;  
  15.          this.endPos = endPos;  
  16.          this.compeleteSize = compeleteSize;  
  17.          this.url=url;  
  18.      }  
  19.      public DownloadInfo() {  
  20.      }  
  21.      public String getUrl() {  
  22.          return url;  
  23.      }  
  24.      public void setUrl(String url) {  
  25.          this.url = url;  
  26.      }  
  27.      public int getThreadId() {  
  28.          return threadId;  
  29.      }  
  30.      public void setThreadId(int threadId) {  
  31.          this.threadId = threadId;  
  32.      }  
  33.      public int getStartPos() {  
  34.          return startPos;  
  35.      }  
  36.      public void setStartPos(int startPos) {  
  37.          this.startPos = startPos;  
  38.      }  
  39.      public int getEndPos() {  
  40.          return endPos;  
  41.      }  
  42.      public void setEndPos(int endPos) {  
  43.          this.endPos = endPos;  
  44.      }  
  45.      public int getCompeleteSize() {  
  46.          return compeleteSize;  
  47.      }  
  48.      public void setCompeleteSize(int compeleteSize) {  
  49.          this.compeleteSize = compeleteSize;  
  50.      }  
  51.    
  52.      @Override  
  53.      public String toString() {  
  54.          return "DownloadInfo [threadId=" + threadId  
  55.                  + ", startPos=" + startPos + ", endPos=" + endPos  
  56.                  + ", compeleteSize=" + compeleteSize +"]";  
  57.      }  
  58.  }  

[java] view plaincopy
  1. package cn.demo.entity;  
  2.  /** 
  3.   *自定义的一个记载下载器详细信息的类  
  4.   */  
  5.  public class LoadInfo {  
  6.      public int fileSize;//文件大小  
  7.      private int complete;//完成度  
  8.      private String urlstring;//下载器标识  
  9.      public LoadInfo(int fileSize, int complete, String urlstring) {  
  10.          this.fileSize = fileSize;  
  11.          this.complete = complete;  
  12.          this.urlstring = urlstring;  
  13.      }  
  14.      public LoadInfo() {  
  15.      }  
  16.      public int getFileSize() {  
  17.          return fileSize;  
  18.      }  
  19.      public void setFileSize(int fileSize) {  
  20.          this.fileSize = fileSize;  
  21.      }  
  22.      public int getComplete() {  
  23.          return complete;  
  24.      }  
  25.      public void setComplete(int complete) {  
  26.          this.complete = complete;  
  27.      }  
  28.      public String getUrlstring() {  
  29.          return urlstring;  
  30.      }  
  31.      public void setUrlstring(String urlstring) {  
  32.          this.urlstring = urlstring;  
  33.      }  
  34.      @Override  
  35.      public String toString() {  
  36.          return "LoadInfo [fileSize=" + fileSize + ", complete=" + complete  
  37.                  + ", urlstring=" + urlstring + "]";  
  38.      }  
  39.  }  

 


这是一个核心类,专门用来处理下载的

[java] view plaincopy
  1. package cn.demo.service;  
  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.AsyncTask;  
  13. import android.os.Handler;  
  14. import android.os.Message;  
  15. import android.util.Log;  
  16. import cn.demo.Dao.Dao;  
  17. import cn.demo.entity.DownloadInfo;  
  18. import cn.demo.entity.LoadInfo;  
  19.    
  20.  public class Downloader {  
  21.      private String urlstr;// 下载的地址  
  22.      private String localfile;// 保存路径  
  23.      private int threadcount;// 线程数  
  24.      private Handler mHandler;// 消息处理器   
  25.      private int fileSize;// 所要下载的文件的大小  
  26.      private Context context;   
  27.      private List<DownloadInfo> infos;// 存放下载信息类的集合  
  28.      private static final int INIT = 1;//定义三种下载的状态:初始化状态,正在下载状态,暂停状态  
  29.      private static final int DOWNLOADING = 2;  
  30.      private static final int PAUSE = 3;  
  31.      private int state = INIT;  
  32.    
  33.      public Downloader(String urlstr, String localfile, int threadcount,  
  34.              Context context, Handler mHandler) {  
  35.          this.urlstr = urlstr;  
  36.          this.localfile = localfile;  
  37.          this.threadcount = threadcount;  
  38.          this.mHandler = mHandler;  
  39.          this.context = context;  
  40.      }  
  41.      /** 
  42.       *判断是否正在下载  
  43.       */  
  44.      public boolean isdownloading() {  
  45.          return state == DOWNLOADING;  
  46.      }  
  47.      /** 
  48.       * 得到downloader里的信息 
  49.       * 首先进行判断是否是第一次下载,如果是第一次就要进行初始化,并将下载器的信息保存到数据库中 
  50.       * 如果不是第一次下载,那就要从数据库中读出之前下载的信息(起始位置,结束为止,文件大小等),并将下载信息返回给下载器 
  51.       */  
  52.      public LoadInfo getDownloaderInfors() {  
  53.          if (isFirst(urlstr)) {  
  54.              Log.v("TAG""isFirst");  
  55.              init();  
  56.              int range = fileSize / threadcount;  
  57.              infos = new ArrayList<DownloadInfo>();  
  58.              for (int i = 0; i < threadcount - 1; i++) {  
  59.                  DownloadInfo info = new DownloadInfo(i, i * range, (i + 1)* range - 10, urlstr);  
  60.                  infos.add(info);  
  61.              }  
  62.              DownloadInfo info = new DownloadInfo(threadcount - 1,(threadcount - 1) * range, fileSize - 10, urlstr);  
  63.              infos.add(info);  
  64.              //保存infos中的数据到数据库  
  65.              Dao.getInstance(context).saveInfos(infos);  
  66.              //创建一个LoadInfo对象记载下载器的具体信息  
  67.              LoadInfo loadInfo = new LoadInfo(fileSize, 0, urlstr);  
  68.              return loadInfo;  
  69.          } else {  
  70.              //得到数据库中已有的urlstr的下载器的具体信息  
  71.              infos = Dao.getInstance(context).getInfos(urlstr);  
  72.              Log.v("TAG""not isFirst size=" + infos.size());  
  73.              int size = 0;  
  74.              int compeleteSize = 0;  
  75.              for (DownloadInfo info : infos) {  
  76.                  compeleteSize += info.getCompeleteSize();  
  77.                  size += info.getEndPos() - info.getStartPos() + 1;  
  78.              }  
  79.              return new LoadInfo(size, compeleteSize, urlstr);  
  80.          }  
  81.      }  
  82.    
  83.      /** 
  84.       * 初始化 
  85.       */  
  86.      private void init() {  
  87.          try {  
  88.              URL url = new URL(urlstr);  
  89.              HttpURLConnection connection = (HttpURLConnection) url.openConnection();  
  90.              connection.setConnectTimeout(5000);  
  91.              connection.setRequestMethod("GET");  
  92.              fileSize = connection.getContentLength();  
  93.    
  94.              File file = new File(localfile);  
  95.              if (!file.exists()) {  
  96.                  file.createNewFile();  
  97.              }  
  98.              // 本地访问文件  
  99.              RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");  
  100.              accessFile.setLength(fileSize);  
  101.              accessFile.close();  
  102.              connection.disconnect();  
  103.          } catch (Exception e) {  
  104.              e.printStackTrace();  
  105.          }  
  106.      }    
  107.      /** 
  108.       * 判断是否是第一次 下载 
  109.       */  
  110.      private boolean isFirst(String urlstr) {  
  111.          return Dao.getInstance(context).isHasInfors(urlstr);  
  112.      }  
  113.    
  114.      /** 
  115.       * 利用线程开始下载数据 
  116.       */  
  117.      public void download() {  
  118.          if (infos != null) {  
  119.              if (state == DOWNLOADING)  
  120.                  return;  
  121.              state = DOWNLOADING;  
  122.              for (DownloadInfo info : infos) {  
  123.                  new MyThread(info.getThreadId(), info.getStartPos(),  
  124.                          info.getEndPos(), info.getCompeleteSize(),  
  125.                          info.getUrl()).start();  
  126.              }  
  127.          }  
  128.      }  
  129.    
  130.      public class MyThread extends Thread {  
  131.          private int threadId;  
  132.          private int startPos;  
  133.          private int endPos;  
  134.          private int compeleteSize;  
  135.          private String urlstr;  
  136.    
  137.          public MyThread(int threadId, int startPos, int endPos,  
  138.                  int compeleteSize, String urlstr) {  
  139.              this.threadId = threadId;  
  140.              this.startPos = startPos;  
  141.              this.endPos = endPos;  
  142.              this.compeleteSize = compeleteSize;  
  143.              this.urlstr = urlstr;  
  144.          }  
  145.          @Override  
  146.          public void run() {  
  147.              HttpURLConnection connection = null;  
  148.              RandomAccessFile randomAccessFile = null;  
  149.              InputStream is = null;  
  150.              try {  
  151.                  URL url = new URL(urlstr);  
  152.                  connection = (HttpURLConnection) url.openConnection();  
  153.                  connection.setConnectTimeout(5000);  
  154.                  connection.setRequestMethod("GET");  
  155.                  // 设置范围,格式为Range:bytes x-y;  
  156.                  connection.setRequestProperty("Range""bytes="+(startPos + compeleteSize) + "-" + endPos);  
  157.    
  158.                  randomAccessFile = new RandomAccessFile(localfile, "rwd");  
  159.                  randomAccessFile.seek(startPos + compeleteSize);  
  160.                  // 将要下载的文件写到保存在保存路径下的文件中  
  161.                  is = connection.getInputStream();  
  162.                  byte[] buffer = new byte[4096];  
  163.                  int length = -1;  
  164.                  while ((length = is.read(buffer)) != -1) {  
  165.                      randomAccessFile.write(buffer, 0, length);  
  166.                      compeleteSize += length;  
  167.                      // 更新数据库中的下载信息  
  168.                      Dao.getInstance(context).updataInfos(threadId, compeleteSize, urlstr);  
  169.                      // 用消息将下载信息传给进度条,对进度条进行更新  
  170.                      Message message = Message.obtain();  
  171.                      message.what = 1;  
  172.                      message.obj = urlstr;  
  173.                      message.arg1 = length;  
  174.                      mHandler.sendMessage(message);  
  175.                      if (state == PAUSE) {  
  176.                          return;  
  177.                      }  
  178.                  }  
  179.              } catch (Exception e) {  
  180.                  e.printStackTrace();  
  181.              }    
  182.          }  
  183.      }  
  184.      //删除数据库中urlstr对应的下载器信息  
  185.      public void delete(String urlstr) {  
  186.          Dao.getInstance(context).delete(urlstr);  
  187.      }  
  188.      //设置暂停  
  189.      public void pause() {  
  190.          state = PAUSE;  
  191.      }  
  192.      //重置下载状态  
  193.      public void reset() {  
  194.          state = INIT;  
  195.      }  
  196.  }  


以下是些xml文件

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.             android:orientation="vertical"  
  4.             android:layout_width="fill_parent"  
  5.             android:layout_height="wrap_content">  
  6.      <LinearLayout  
  7.             android:orientation="horizontal"  
  8.             android:layout_width="fill_parent"  
  9.             android:layout_height="wrap_content"  
  10.             android:layout_marginBottom="5dip">  
  11.          <TextView   
  12.              android:layout_width="fill_parent"  
  13.              android:layout_height="wrap_content"  
  14.              android:layout_weight="1"  
  15.              android:id="@+id/tv_resouce_name"/>  
  16.          <Button  
  17.              android:layout_width="fill_parent"  
  18.              android:layout_height="wrap_content"  
  19.              android:layout_weight="1"  
  20.              android:text="下载"  
  21.              android:id="@+id/btn_start"  
  22.              android:onClick="startDownload"/>  
  23.          <Button  
  24.              android:layout_width="fill_parent"  
  25.              android:layout_height="wrap_content"  
  26.              android:layout_weight="1"  
  27.              android:text="暂停"  
  28.              android:visibility="gone"  
  29.              android:id="@+id/btn_pause"  
  30.              android:onClick="pauseDownload"/>  
  31.        </LinearLayout>  
  32.  </LinearLayout>  


 

[html] view plaincopy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.      android:orientation="vertical"  
  4.      android:layout_width="fill_parent"  
  5.      android:layout_height="fill_parent"  
  6.      android:id="@+id/llRoot">  
  7.      <ListView android:id="@android:id/list"  
  8.          android:layout_width="fill_parent"  
  9.          android:layout_height="fill_parent">  
  10.      </ListView>  
  11.  </LinearLayout>  


 

[html] view plaincopy
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     package="cn.demo.download"  
  3.     android:versionCode="1"  
  4.     android:versionName="1.0">  
  5.   
  6.     <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="8" />  
  7.     <uses-permission android:name="android.permission.INTERNET"/>   
  8.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
  9.     <application android:label="@string/app_name"  
  10.         android:icon="@drawable/ic_launcher"  
  11.         android:theme="@style/AppTheme">  
  12.         <activity  
  13.                 android:name=".MainActivity"   
  14.                 android:label="@string/app_name" >  
  15.                 <intent-filter>  
  16.                     <action android:name="android.intent.action.MAIN" />   
  17.                     <category android:name="android.intent.category.LAUNCHER" />  
  18.                 </intent-filter>  
  19.             </activity>  
  20.     </application>  
  21.   
  22. </manifest>  


运行效果如下

android 多线程断点续传下载 三

 

 

源码下载地址

上一篇:Android多进程之Binder的意外死亡及权限校验


下一篇:周四直播预告:基于 OAM 和 Kubernetes 快速构建开放 Serverless 平台