android 多任务多线程断点下载

很多时候我们需要在Android设备上下载远程服务器上的文件进安装,前两天晚上我看到一个视频,写了两个晚上,大概理解一下。   直接通过Android提供的Http类访问远程服务器,这里AndroidHttpClient是SDK 2.2中新出的方法,

下载断点文件。

下面让我们看一下图先。

  1. 让我们看一下代码的实现方法。 
  2. package com.smart.db; 
  3. import java.util.HashMap; 
  4. import java.util.Map; 
  5. import android.content.Context; 
  6. import android.database.Cursor; 
  7. import android.database.sqlite.SQLiteDatabase; 
  8. /** 
  9. * 业务bean 
  10. * 
  11. */ 
  12. public class FileService { 
  13. private DBOpenHelper openHelper; 
  14. public FileService(Context context) { 
  15.   openHelper = new DBOpenHelper(context); 
  16. /** 
  17.   * 获取每条线程已经下载的文件长度 
  18.   * @param path 
  19.   * @return 
  20.   */ 
  21. public Map<Integer, Integer> getData(String path){ 
  22.   SQLiteDatabase db = openHelper.getReadableDatabase(); 
  23.   Cursor cursor = db.rawQuery("select threadid, downlength from SmartFileDownlog where downpath=?"new String[]{path}); 
  24.   Map<Integer, Integer> data = new HashMap<Integer, Integer>(); 
  25.   while(cursor.moveToNext()){ 
  26.    data.put(cursor.getInt(0), cursor.getInt(1)); 
  27.   } 
  28.   cursor.close(); 
  29.   db.close(); 
  30.   return data; 
  31. /** 
  32.   * 保存每条线程已经下载的文件长度 
  33.   * @param path 
  34.   * @param map 
  35.   */ 
  36. public void save(String path,  Map<Integer, Integer> map){//int threadid, int position 
  37.   SQLiteDatabase db = openHelper.getWritableDatabase(); 
  38.   db.beginTransaction(); 
  39.   try
  40.    for(Map.Entry<Integer, Integer> entry : map.entrySet()){ 
  41.     db.execSQL("insert into SmartFileDownlog(downpath, threadid, downlength) values(?,?,?)"
  42.       new Object[]{path, entry.getKey(), entry.getValue()}); 
  43.    } 
  44.    db.setTransactionSuccessful(); 
  45.   }finally
  46.    db.endTransaction(); 
  47.   } 
  48.   db.close(); 
  49. /** 
  50.   * 实时更新每条线程已经下载的文件长度 
  51.   * @param path 
  52.   * @param map 
  53.   */ 
  54. public void update(String path, Map<Integer, Integer> map){ 
  55.   SQLiteDatabase db = openHelper.getWritableDatabase(); 
  56.   db.beginTransaction(); 
  57.   try
  58.    for(Map.Entry<Integer, Integer> entry : map.entrySet()){ 
  59.     db.execSQL("update SmartFileDownlog set downlength=? where downpath=? and threadid=?"
  60.       new Object[]{entry.getValue(), path, entry.getKey()}); 
  61.    } 
  62.    db.setTransactionSuccessful(); 
  63.   }finally
  64.    db.endTransaction(); 
  65.   } 
  66.   db.close(); 
  67. /** 
  68.   * 当文件下载完成后,删除对应的下载记录 
  69.   * @param path 
  70.   */ 
  71. public void delete(String path){ 
  72.   SQLiteDatabase db = openHelper.getWritableDatabase(); 
  73.   db.execSQL("delete from SmartFileDownlog where downpath=?"new Object[]{path}); 
  74.   db.close(); 
  75.  
  76.  
  77.  
  78. package com.smart.impl; 
  79. import java.io.File; 
  80. import java.io.RandomAccessFile; 
  81. import java.net.HttpURLConnection; 
  82. import java.net.URL; 
  83. import java.util.LinkedHashMap; 
  84. import java.util.Map; 
  85. import java.util.UUID; 
  86. import java.util.concurrent.ConcurrentHashMap; 
  87. import java.util.regex.Matcher; 
  88. import java.util.regex.Pattern; 
  89. import android.content.Context; 
  90. import android.util.Log; 
  91. import com.smart.db.FileService; 
  92. /** 
  93. * 文件下载器 
  94. * @author lihuoming@sohu.com 
  95. */ 
  96. public class SmartFileDownloader { 
  97. private static final String TAG = "SmartFileDownloader"
  98. private Context context; 
  99. private FileService fileService; 
  100. /* 已下载文件长度 */ 
  101. private int downloadSize = 0
  102. /* 原始文件长度 */ 
  103. private int fileSize = 0
  104. /* 线程数 */ 
  105. private SmartDownloadThread[] threads; 
  106. /* 本地保存文件 */ 
  107. private File saveFile; 
  108. /* 缓存各线程下载的长度*/ 
  109. private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>(); 
  110. /* 每条线程下载的长度 */ 
  111. private int block; 
  112. /* 下载路径  */ 
  113. private String downloadUrl; 
  114. /** 
  115.   * 获取线程数 
  116.   */ 
  117. public int getThreadSize() { 
  118.   return threads.length; 
  119. /** 
  120.   * 获取文件大小 
  121.   * @return 
  122.   */ 
  123. public int getFileSize() { 
  124.   return fileSize; 
  125. /** 
  126.   * 累计已下载大小 
  127.   * @param size 
  128.   */ 
  129. protected synchronized void append(int size) { 
  130.   downloadSize += size; 
  131. /** 
  132.   * 更新指定线程最后下载的位置 
  133.   * @param threadId 线程id 
  134.   * @param pos 最后下载的位置 
  135.   */ 
  136. protected void update(int threadId, int pos) { 
  137.   this.data.put(threadId, pos); 
  138. /** 
  139.   * 保存记录文件 
  140.   */ 
  141. protected synchronized void saveLogFile() { 
  142.   this.fileService.update(this.downloadUrl, this.data); 
  143. /** 
  144.   * 构建文件下载器 
  145.   * @param downloadUrl 下载路径 
  146.   * @param fileSaveDir 文件保存目录 
  147.   * @param threadNum 下载线程数 
  148.   */ 
  149. public SmartFileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) { 
  150.   try { 
  151.    this.context = context; 
  152.    this.downloadUrl = downloadUrl; 
  153.    fileService = new FileService(this.context); 
  154.    URL url = new URL(this.downloadUrl); 
  155.    if(!fileSaveDir.exists()) fileSaveDir.mkdirs(); 
  156.    this.threads = new SmartDownloadThread[threadNum];      
  157.    HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 
  158.    conn.setConnectTimeout(5*1000); 
  159.    conn.setRequestMethod("GET"); 
  160.    conn.setRequestProperty("Accept""image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"); 
  161.    conn.setRequestProperty("Accept-Language""zh-CN"); 
  162.    conn.setRequestProperty("Referer", downloadUrl); 
  163.    conn.setRequestProperty("Charset""UTF-8"); 
  164.    conn.setRequestProperty("User-Agent""Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); 
  165.    conn.setRequestProperty("Connection""Keep-Alive"); 
  166.    conn.connect(); 
  167.    printResponseHeader(conn); 
  168.    if (conn.getResponseCode()==200) { 
  169.     this.fileSize = conn.getContentLength();//根据响应获取文件大小 
  170.     if (this.fileSize <= 0throw new RuntimeException("Unkown file size "); 
  171.        
  172.     String filename = getFileName(conn); 
  173.     this.saveFile = new File(fileSaveDir, filename);/* 保存文件 */ 
  174.     Map<Integer, Integer> logdata = fileService.getData(downloadUrl); 
  175.     if(logdata.size()>0){ 
  176.      for(Map.Entry<Integer, Integer> entry : logdata.entrySet()) 
  177.       data.put(entry.getKey(), entry.getValue()); 
  178.     } 
  179.     this.block = (this.fileSize % this.threads.length)==0this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1
  180.     if(this.data.size()==this.threads.length){ 
  181.      for (int i = 0; i < this.threads.length; i++) { 
  182.       this.downloadSize += this.data.get(i+1); 
  183.      } 
  184.      print("已经下载的长度"this.downloadSize); 
  185.     }    
  186.    }else
  187.     throw new RuntimeException("server no response "); 
  188.    } 
  189.   } catch (Exception e) { 
  190.    print(e.toString()); 
  191.    throw new RuntimeException("don't connection this url"); 
  192.   } 
  193. /** 
  194.   * 获取文件名 
  195.   */ 
  196. private String getFileName(HttpURLConnection conn) { 
  197.   String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1); 
  198.   if(filename==null || "".equals(filename.trim())){//如果获取不到文件名称 
  199.    for (int i = 0;; i++) { 
  200.     String mine = conn.getHeaderField(i); 
  201.     if (mine == nullbreak
  202.     if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){ 
  203.      Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase()); 
  204.      if(m.find()) return m.group(1); 
  205.     } 
  206.    } 
  207.    filename = UUID.randomUUID()+ ".tmp";//默认取一个文件名 
  208.   } 
  209.   return filename; 
  210.  
  211. /** 
  212.   *  开始下载文件 
  213.   * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null 
  214.   * @return 已下载文件大小 
  215.   * @throws Exception 
  216.   */ 
  217. public int download(SmartDownloadProgressListener listener) throws Exception{ 
  218.   try { 
  219.    RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw"); 
  220.    if(this.fileSize>0) randOut.setLength(this.fileSize); 
  221.    randOut.close(); 
  222.    URL url = new URL(this.downloadUrl); 
  223.    if(this.data.size() != this.threads.length){ 
  224.     this.data.clear();//清除数据 
  225.     for (int i = 0; i < this.threads.length; i++) { 
  226.      this.data.put(i+10); 
  227.     } 
  228.    } 
  229.    for (int i = 0; i < this.threads.length; i++) { 
  230.     int downLength = this.data.get(i+1); 
  231.     if(downLength < this.block && this.downloadSize<this.fileSize){ //该线程未完成下载时,继续下载       
  232.      this.threads = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1); 
  233.      this.threads.setPriority(7); 
  234.      this.threads.start(); 
  235.     }else
  236.      this.threads = null
  237.     } 
  238.    } 
  239.    this.fileService.save(this.downloadUrl, this.data); 
  240.    boolean notFinish = true;//下载未完成 
  241.    while (notFinish) {// 循环判断是否下载完毕 
  242.     Thread.sleep(900); 
  243.     notFinish = false;//假定下载完成 
  244.     for (int i = 0; i < this.threads.length; i++){ 
  245.      if (this.threads != null && !this.threads.isFinish()) { 
  246.       notFinish = true;//下载没有完成 
  247.       if(this.threads.getDownLength() == -1){//如果下载失败,再重新下载 
  248.        this.threads = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1); 
  249.        this.threads.setPriority(7); 
  250.        this.threads.start(); 
  251.       } 
  252.      } 
  253.     }    
  254.     if(listener!=null) listener.onDownloadSize(this.downloadSize); 
  255.    } 
  256.    fileService.delete(this.downloadUrl); 
  257.   } catch (Exception e) { 
  258.    print(e.toString()); 
  259.    throw new Exception("file download fail"); 
  260.   } 
  261.   return this.downloadSize; 
  262. /** 
  263.   * 获取Http响应头字段 
  264.   * @param http 
  265.   * @return 
  266.   */ 
  267. public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) { 
  268.   Map<String, String> header = new LinkedHashMap<String, String>(); 
  269.   for (int i = 0;; i++) { 
  270.    String mine = http.getHeaderField(i); 
  271.    if (mine == nullbreak
  272.    header.put(http.getHeaderFieldKey(i), mine); 
  273.   } 
  274.   return header; 
  275. /** 
  276.   * 打印Http头字段 
  277.   * @param http 
  278.   */ 
  279. public static void printResponseHeader(HttpURLConnection http){ 
  280.   Map<String, String> header = getHttpResponseHeader(http); 
  281.   for(Map.Entry<String, String> entry : header.entrySet()){ 
  282.    String key = entry.getKey()!=null ? entry.getKey()+ ":" : ""
  283.    print(key+ entry.getValue()); 
  284.   } 
  285. //打印日志 
  286. private static void print(String msg){ 
  287.   Log.i(TAG, msg); 
  288.  
  289.  
  290. package com.smart.impl; 
  291. import java.io.File; 
  292. import java.io.InputStream; 
  293. import java.io.RandomAccessFile; 
  294. import java.net.HttpURLConnection; 
  295. import java.net.URL; 
  296. import android.util.Log; 
  297. public class SmartDownloadThread extends Thread { 
  298. private static final String TAG = "SmartDownloadThread"
  299. private File saveFile; 
  300. private URL downUrl; 
  301. private int block; 
  302. /* *下载开始位置  */ 
  303. private int threadId = -1
  304. private int downLength; 
  305. private boolean finish = false
  306. private SmartFileDownloader downloader; 
  307. public SmartDownloadThread(SmartFileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) { 
  308.   this.downUrl = downUrl; 
  309.   this.saveFile = saveFile; 
  310.   this.block = block; 
  311.   this.downloader = downloader; 
  312.   this.threadId = threadId; 
  313.   this.downLength = downLength; 
  314.  
  315. @Override 
  316. public void run() { 
  317.   if(downLength < block){//未下载完成 
  318.    try { 
  319.     HttpURLConnection http = (HttpURLConnection) downUrl.openConnection(); 
  320.     http.setConnectTimeout(5 * 1000); 
  321.     http.setRequestMethod("GET"); 
  322.     http.setRequestProperty("Accept""image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"); 
  323.     http.setRequestProperty("Accept-Language""zh-CN"); 
  324.     http.setRequestProperty("Referer", downUrl.toString()); 
  325.     http.setRequestProperty("Charset""UTF-8"); 
  326.     int startPos = block * (threadId - 1) + downLength;//开始位置 
  327.     int endPos = block * threadId -1;//结束位置 
  328.     http.setRequestProperty("Range""bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围 
  329.     http.setRequestProperty("User-Agent""Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"); 
  330.     http.setRequestProperty("Connection""Keep-Alive"); 
  331.     
  332.     InputStream inStream = http.getInputStream(); 
  333.     byte[] buffer = new byte[1024]; 
  334.     int offset = 0
  335.     print("Thread " + this.threadId + " start download from position "+ startPos); 
  336.     RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd"); 
  337.     threadfile.seek(startPos); 
  338.     while ((offset = inStream.read(buffer, 01024)) != -1) { 
  339.      threadfile.write(buffer, 0, offset); 
  340.      downLength += offset; 
  341.      downloader.update(this.threadId, downLength); 
  342.      downloader.saveLogFile(); 
  343.      downloader.append(offset); 
  344.     } 
  345.     threadfile.close(); 
  346.     inStream.close();    
  347.     print("Thread " + this.threadId + " download finish"); 
  348.     this.finish = true
  349.    } catch (Exception e) { 
  350.     this.downLength = -1
  351.     print("Thread "this.threadId+ ":"+ e); 
  352.    } 
  353.   } 
  354. private static void print(String msg){ 
  355.   Log.i(TAG, msg); 
  356. /** 
  357.   * 下载是否完成 
  358.   * @return 
  359.   */ 
  360. public boolean isFinish() { 
  361.   return finish; 
  362. /** 
  363.   * 已经下载的内容大小 
  364.   * @return 如果返回值为-1,代表下载失败 
  365.   */ 
  366. public long getDownLength() { 
  367.   return downLength; 
  368. package com.smart.activoty.download; 
  369. import java.io.File; 
  370. import android.app.Activity; 
  371. import android.os.Bundle; 
  372. import android.os.Environment; 
  373. import android.os.Handler; 
  374. import android.os.Message; 
  375. import android.view.View; 
  376. import android.widget.Button; 
  377. import android.widget.EditText; 
  378. import android.widget.ProgressBar; 
  379. import android.widget.TextView; 
  380. import android.widget.Toast; 
  381. import com.smart.impl.SmartDownloadProgressListener; 
  382. import com.smart.impl.SmartFileDownloader; 
  383. public class SmartDownloadActivity extends Activity { 
  384.     private ProgressBar downloadbar; 
  385.     private EditText pathText; 
  386.     private TextView resultView; 
  387.     private Handler handler = new Handler(){ 
  388.   @Override//信息 
  389.   public void handleMessage(Message msg) { 
  390.    switch (msg.what) { 
  391.    case 1
  392.     int size = msg.getData().getInt("size"); 
  393.     downloadbar.setProgress(size); 
  394.     float result = (float)downloadbar.getProgress()/ (float)downloadbar.getMax(); 
  395.     int p = (int)(result*100); 
  396.     resultView.setText(p+"%"); 
  397.     if(downloadbar.getProgress()==downloadbar.getMax()) 
  398.      Toast.makeText(SmartDownloadActivity.this, R.string.success, 1).show(); 
  399.     break
  400.    case -1
  401.     Toast.makeText(SmartDownloadActivity.this, R.string.error, 1).show(); 
  402.     break
  403.    } 
  404.     
  405.   }      
  406.     }; 
  407.     
  408.     @Override 
  409.     public void onCreate(Bundle savedInstanceState) { 
  410.         super.onCreate(savedInstanceState); 
  411.         setContentView(R.layout.main); 
  412.          
  413.         Button button = (Button)this.findViewById(R.id.button); 
  414.         downloadbar = (ProgressBar)this.findViewById(R.id.downloadbar); 
  415.         pathText = (EditText)this.findViewById(R.id.path); 
  416.         resultView = (TextView)this.findViewById(R.id.result); 
  417.         button.setOnClickListener(new View.OnClickListener() {    
  418.    @Override 
  419.    public void onClick(View v) { 
  420.     String path = pathText.getText().toString(); 
  421.     if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ 
  422.      File dir = Environment.getExternalStorageDirectory();//文件保存目录 
  423.      download(path, dir); 
  424.     }else
  425.      Toast.makeText(SmartDownloadActivity.this, R.string.sdcarderror, 1).show(); 
  426.     } 
  427.    } 
  428.   }); 
  429.     } 
  430.     //对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错 
  431.     private void download(final String path, final File dir){ 
  432.      new Thread(new Runnable() { 
  433.    @Override 
  434.    public void run() { 
  435.     try { 
  436.      SmartFileDownloader loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3); 
  437.      int length = loader.getFileSize();//获取文件的长度 
  438.      downloadbar.setMax(length); 
  439.      loader.download(new SmartDownloadProgressListener(){ 
  440.       @Override 
  441.       public void onDownloadSize(int size) {//可以实时得到文件下载的长度 
  442.        Message msg = new Message(); 
  443.        msg.what = 1
  444.        msg.getData().putInt("size", size);       
  445.        handler.sendMessage(msg); 
  446.       }}); 
  447.     } catch (Exception e) { 
  448.      Message msg = new Message();//信息提示 
  449.      msg.what = -1
  450.      msg.getData().putString("error""下载失败");//如果下载错误,显示提示失败! 
  451.      handler.sendMessage(msg); 
  452.     } 
  453.    } 
  454.   }).start();//开始 
  455.       
  456.     } 
  457. }


出处:http://llb988.blog.51cto.com/1940549/510035

posted on 2011-05-31 10:51  neil-zhao  阅读(696)  评论(1编辑  收藏  举报

导航