Android使用AsyncTask實現能夠斷點續傳的DownloadManager功能

  最近作項目卡殼了,要作個Android的應用市場,其餘方面都還好說,惟獨這個下載管理算是給我難住了,究其緣由,一是以前沒有作過相似的功能,二是這個項目催的着實的急促,以致於都沒什麼時間能仔細研究這方面的內容,三是我這二把刀的基本功實在是不太紮實啊。不過好在經高人指點,再加上bing以及stackoverflow的幫助,好歹算是有些成果,下面就將這小小的成果分享一下,雖然是使用的AsyncTask來完成,可是我的以爲仍是service要更靠譜些,不過那個得等有空兒再研究了。php

  AsyncTask是何物我就再也不贅述了,度娘,谷哥,必應都會告訴你的,不過建議你們看看文章最後參考資料的第二個連接,寫的仍是很是詳細的。我認爲它實際上就是個簡單的迷你的Handler,反正把一些異步操做扔給它之後,就只須要等着它執行完就齊活了。html

  那麼怎麼用這玩意兒實現一個下載管理的功能?大致的思路是這樣的:
  1.點擊下載按鈕之後,除了要讓AsyncTask開始執行外,還要把下載的任務放到HashMap裏面保存,這樣作的好處就是可以在列表頁進行管理,好比暫停、繼續下載、取消。
  2.下載管理頁的列表,使用ScrollView,而非ListView。這樣作的好處就是爲了能方便的更新ProgressBar進度。
java

  那咱先來講說啓動下載任務。 android

  
  
  
  
  1. btnDownload.setOnClickListener(new OnClickListener() { 
  2.     public void onClick(View v) { 
  3.         String url = datas.get(position).get("url"); 
  4.         Async asyncTask = null; // 下載renwu
  5.         boolean isHas = false
  6.         // 判斷當前要下載的這個鏈接是否已經正在進行,若是正在進行就阻止在此啓動一個下載任務 
  7.         for (String urlString : AppConstants.listUrl) { 
  8.             if (url.equalsIgnoreCase(urlString)) { 
  9.                 isHas = true
  10.                 break
  11.             } 
  12.         } 
  13.          
  14.         // 若是這個鏈接的下載任務尚未開始,就建立一個新的下載任務啓動下載,並這個下載任務加到下載列表中 
  15.         if(isHas == false) { 
  16.             asyncTask = new Async();  // 建立新異步 
  17.             asyncTask.setDataMap(datas.get(position)); 
  18.             asyncTask.setContext(context); 
  19.             AppConstants.mapTask.put(url, asyncTask); 
  20.             // 當調用AsyncTask的方法execute時,就會去自動調用doInBackground方法 
  21.             asyncTask.executeOnExecutor(Executors.newCachedThreadPool(), url); 
  22.         } 
  23.     } 
  24. }); 

  這裏我爲何寫asyncTask.executeOnExecutor(Executors.newCachedThreadPool(), url);而不是asyncTask.execute(url);呢?先賣個關子,回頭咱再說。sql

  下面來看看Async裏都幹了啥。數據庫

  
  
  
  
  1. package com.test.muldownloadtest.task; 
  2.  
  3. import java.io.File; 
  4. import java.io.IOException; 
  5. import java.io.InputStream; 
  6. import java.io.RandomAccessFile; 
  7. import java.net.HttpURLConnection; 
  8. import java.net.MalformedURLException; 
  9. import java.net.URL; 
  10. import java.util.HashMap; 
  11.  
  12. import com.test.muldownloadtest.AppConstants; 
  13. import com.test.muldownloadtest.bean.DBHelper; 
  14.  
  15. import android.content.Context; 
  16. import android.database.Cursor; 
  17. import android.database.sqlite.SQLiteDatabase; 
  18. import android.os.AsyncTask; 
  19. import android.os.Environment; 
  20. import android.os.Handler; 
  21. import android.os.Message; 
  22. import android.widget.ListView; 
  23. import android.widget.ProgressBar; 
  24. import android.widget.TextView; 
  25.  
  26. // AsyncTask<Params, Progress, Result>  
  27. public class Async extends AsyncTask<String, Integer, String> { 
  28.      
  29.      /* 用於查詢數據庫 */   
  30.     private DBHelper dbHelper; 
  31.      
  32.     // 下載的文件的map,也能夠是實體Bean 
  33.     private HashMap<String, String> dataMap = null
  34.     private Context context; 
  35.      
  36.     private boolean finished = false
  37.     private boolean paused = false
  38.      
  39.     private int curSize = 0
  40.      
  41.     private int length = 0
  42.      
  43.     private Async startTask = null
  44.     private boolean isFirst = true
  45.      
  46.     private String strUrl; 
  47.      
  48.     @Override 
  49.     protected String doInBackground(String... Params) { 
  50.          
  51.         dbHelper = new DBHelper(context); 
  52.          
  53.         strUrl = Params[0]; 
  54.         String name = dataMap.get("name"); 
  55.         String appid = dataMap.get("appid"); 
  56.         int startPosition = 0
  57.          
  58.         URL url = null
  59.         HttpURLConnection httpURLConnection = null
  60.         InputStream inputStream = null
  61.         RandomAccessFile outputStream = null
  62.         // 文件保存路徑 
  63.         String path = Environment.getExternalStorageDirectory().getPath(); 
  64.         // 文件名 
  65.         String fileName = strUrl.substring(strUrl.lastIndexOf('/')); 
  66.         try { 
  67.             length = getContentLength(strUrl); 
  68.             startPosition = getDownloadedLength(strUrl, name); 
  69.              
  70.             /** 判斷是不是第一次啓動任務,true則保存數據到數據庫並下載, 
  71.              *  false則更新數據庫中的數據 start  
  72.              */ 
  73.             boolean isHas = false
  74.             for (String urlString : AppConstants.listUrl) { 
  75.                 if (strUrl.equalsIgnoreCase(urlString)) { 
  76.                     isHas = true
  77.                     break
  78.                 } 
  79.             } 
  80.             if (false == isHas) { 
  81.                 saveDownloading(name, appid, strUrl, path, fileName, startPosition, length, 1); 
  82.             } 
  83.             else if (true == isHas) { 
  84.                 updateDownloading(curSize, name, strUrl); 
  85.             } 
  86.             /** 判斷是不是第一次啓動任務,true則保存數據到數據庫並下載, 
  87.              *  false則更新數據庫中的數據 end  
  88.              */ 
  89.              
  90.             // 設置斷點續傳的開始位置 
  91.             url = new URL(strUrl); 
  92.             httpURLConnection = (HttpURLConnection)url.openConnection(); 
  93.             httpURLConnection.setAllowUserInteraction(true); 
  94.             httpURLConnection.setRequestMethod("GET"); 
  95.             httpURLConnection.setReadTimeout(5000); 
  96.             httpURLConnection.setRequestProperty("User-Agent","NetFox"); 
  97.             httpURLConnection.setRequestProperty("Range""bytes=" + startPosition + "-"); 
  98.             inputStream = httpURLConnection.getInputStream(); 
  99.              
  100.             File outFile = new File(path+fileName); 
  101.             // 使用java中的RandomAccessFile 對文件進行隨機讀寫操做 
  102.             outputStream = new RandomAccessFile(outFile,"rw"); 
  103.             // 設置開始寫文件的位置 
  104.             outputStream.seek(startPosition); 
  105.              
  106.             byte[] buf = new byte[1024*100]; 
  107.             int read = 0
  108.             curSize = startPosition; 
  109.             while(false == finished) { 
  110.                 while(true == paused) { 
  111.                     // 暫停下載 
  112.                     Thread.sleep(500); 
  113.                 } 
  114.                 read = inputStream.read(buf); 
  115.                 if(read==-1) { 
  116.                     break
  117.                 } 
  118.                 outputStream.write(buf,0,read); 
  119.                 curSize = curSize+read; 
  120.                 // 當調用這個方法的時候會自動去調用onProgressUpdate方法,傳遞下載進度 
  121.                 publishProgress((int)(curSize*100.0f/length)); 
  122.                 if(curSize == length) { 
  123.                     break
  124.                 } 
  125.                 Thread.sleep(500); 
  126.                 updateDownloading(curSize, name, strUrl); 
  127.             } 
  128.             if (false == finished) { 
  129.                 finished = true
  130.                 deleteDownloading(strUrl, name); 
  131.             } 
  132.             inputStream.close(); 
  133.             outputStream.close(); 
  134.             httpURLConnection.disconnect(); 
  135.         } 
  136.         catch (MalformedURLException e) { 
  137.             e.printStackTrace(); 
  138.         }  
  139.         catch (IOException e) { 
  140.             e.printStackTrace(); 
  141.         }  
  142.         catch (InterruptedException e) { 
  143.             e.printStackTrace(); 
  144.         } 
  145.         finally { 
  146.             finished = true
  147.             deleteDownloading(strUrl, name); 
  148.             if(inputStream!=null) { 
  149.                 try { 
  150.                     inputStream.close(); 
  151.                     if(outputStream!=null) { 
  152.                         outputStream.close(); 
  153.                     } 
  154.                     if(httpURLConnection!=null) { 
  155.                         httpURLConnection.disconnect(); 
  156.                     } 
  157.                 } 
  158.                 catch (IOException e) { 
  159.                     e.printStackTrace(); 
  160.                 } 
  161.             } 
  162.         } 
  163.         // 這裏的返回值將會被做爲onPostExecute方法的傳入參數 
  164.         return strUrl; 
  165.     } 
  166.      
  167.     /** 
  168.      * 暫停下載 
  169.      */ 
  170.     public void pause() { 
  171.         paused = true
  172.     } 
  173.      
  174.     /** 
  175.      * 繼續下載 
  176.      */ 
  177.     public void continued() { 
  178.         paused = false
  179.     } 
  180.      
  181.     /** 
  182.      * 中止下載 
  183.      */ 
  184.     @Override 
  185.     protected void onCancelled() { 
  186.         finished = true
  187.         deleteDownloading(dataMap.get("url"), dataMap.get("name")); 
  188.         super.onCancelled(); 
  189.     } 
  190.      
  191.     /** 
  192.      * 當一個下載任務成功下載完成的時候回來調用這個方法, 
  193.      * 這裏的result參數就是doInBackground方法的返回值 
  194.      */ 
  195.     @Override 
  196.     protected void onPostExecute(String result) { 
  197.         try { 
  198.             String name = dataMap.get("name"); 
  199.             System.out.println("name===="+name); 
  200.             // 判斷當前結束的這個任務在任務列表中是否還存在,若是存在就移除 
  201.             if (AppConstants.mapTask.containsKey(result)) { 
  202.                 if (AppConstants.mapTask.get(result) != null) { 
  203.                     finished = true
  204.                     deleteDownloading(result, name); 
  205.                 } 
  206.             } 
  207.         }  
  208.         catch (NumberFormatException e) { 
  209.             e.printStackTrace(); 
  210.         } 
  211.         super.onPostExecute(result); 
  212.     } 
  213.  
  214.     @Override 
  215.     protected void onPreExecute() { 
  216.         super.onPreExecute(); 
  217.     } 
  218.      
  219.     /** 
  220.      * 更新下載進度,當publishProgress方法被調用的時候就會自動來調用這個方法 
  221.      */ 
  222.     @Override 
  223.     protected void onProgressUpdate(Integer... values) { 
  224.         super.onProgressUpdate(values); 
  225.     } 
  226.      
  227.      
  228.     /** 
  229.      * 獲取要下載內容的長度 
  230.      * @param urlString 
  231.      * @return 
  232.      */ 
  233.     private int getContentLength(String urlString){ 
  234.         try { 
  235.             URL url = new URL(urlString); 
  236.             HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 
  237.             return connection.getContentLength(); 
  238.         }  
  239.         catch (MalformedURLException e) { 
  240.             e.printStackTrace(); 
  241.         }  
  242.         catch (IOException e) { 
  243.             e.printStackTrace(); 
  244.         } 
  245.         return 0
  246.     } 
  247.      
  248.     /** 
  249.      * 從數據庫獲取已經下載的長度 
  250.      * @param url 
  251.      * @param name 
  252.      * @return 
  253.      */ 
  254.     private int getDownloadedLength(String url, String name) { 
  255.         int downloadedLength = 0
  256.         SQLiteDatabase db = dbHelper.getReadableDatabase();   
  257.         String sql = "SELECT downloadBytes FROM fileDownloading WHERE downloadUrl=? AND name=?";   
  258.         Cursor cursor = db.rawQuery(sql, new String[] { url, name });   
  259.         while (cursor.moveToNext()) {   
  260.             downloadedLength = cursor.getInt(0);    
  261.         }   
  262.         db.close();   
  263.         return downloadedLength;   
  264.     } 
  265.      
  266.     /** 
  267.      * 保存下載的數據 
  268.      * @param name 
  269.      * @param appid 
  270.      * @param url 
  271.      * @param downloadedLength 
  272.      */ 
  273.     private void saveDownloading(String name, String appid, String url, String savePath, String fileName, int downloadBytes, int totalBytes, int status) {   
  274.         SQLiteDatabase db = dbHelper.getWritableDatabase();   
  275.         try {   
  276.             db.beginTransaction();   
  277.             String sql = "INSERT INTO fileDownloading(name, appid, downloadUrl, savePath, fileName, downloadBytes, totalBytes, downloadStatus) " + 
  278.                     "values(?,?,?,?,?,?,?,?)";   
  279.             db.execSQL(sql, new Object[]{ name, appid, url, savePath, fileName, downloadBytes, totalBytes, status});   
  280.             db.setTransactionSuccessful(); 
  281.             boolean isHas = false
  282.             // 判斷當前要下載的這個鏈接是否已經正在進行,若是正在進行就阻止在此啓動一個下載任務 
  283.             for (String urlString : AppConstants.listUrl) { 
  284.                 if (url.equalsIgnoreCase(urlString)) { 
  285.                     isHas = true
  286.                     break
  287.                 } 
  288.             } 
  289.             if (false == isHas) { 
  290.                 AppConstants.listUrl.add(url); 
  291.             } 
  292.             if (false == isFirst) { 
  293.                 AppConstants.mapTask.put(url, startTask); 
  294.             } 
  295.         }  
  296.         finally {   
  297.             db.endTransaction();   
  298.             db.close();   
  299.         }   
  300.     } 
  301.      
  302.     /** 
  303.      * 更新下載數據 
  304.      * @param cursize 
  305.      * @param name 
  306.      * @param url 
  307.      */ 
  308.     private void updateDownloading(int cursize, String name, String url) { 
  309.         SQLiteDatabase db = dbHelper.getWritableDatabase();   
  310.         try {   
  311.             db.beginTransaction();   
  312.             String sql = "UPDATE fileDownloading SET downloadBytes=? WHERE name=? AND downloadUrl=?";   
  313.             db.execSQL(sql, new String[] { cursize + "", name, url });   
  314.             db.setTransactionSuccessful();   
  315.         } finally {   
  316.             db.endTransaction();   
  317.             db.close();   
  318.         }   
  319.     } 
  320.      
  321.     /** 
  322.      * 刪除下載數據 
  323.      * @param url 
  324.      * @param name 
  325.      */ 
  326.     private void deleteDownloading(String url, String name) { 
  327.         if (true == finished) { 
  328.             // 刪除保存的URL。這個listurl主要是爲了在列表中按添加下載任務的順序進行顯示 
  329.             for (int i = 0; i < AppConstants.listUrl.size(); i++) { 
  330.                 if (url.equalsIgnoreCase(AppConstants.listUrl.get(i))) { 
  331.                     AppConstants.listUrl.remove(i); 
  332.                 } 
  333.             } 
  334.             // 刪除已經完成的下載任務 
  335.             if (AppConstants.mapTask.containsKey(url)) { 
  336.                 AppConstants.mapTask.remove(url); 
  337.             } 
  338.         } 
  339.         SQLiteDatabase db = dbHelper.getWritableDatabase();   
  340.         String sql = "DELETE FROM fileDownloading WHERE downloadUrl=? AND name=?";   
  341.         db.execSQL(sql, new Object[] { url, name });   
  342.         db.close();   
  343.     }  
  344.  
  345.     public void setDataMap(HashMap<String, String> dataMap) { 
  346.         this.dataMap = dataMap; 
  347.     } 
  348.  
  349.     public HashMap<String, String> getDataMap() { 
  350.         return dataMap; 
  351.     } 
  352.  
  353.     public boolean isPaused() { 
  354.         return paused; 
  355.     } 
  356.  
  357.     public int getCurSize() { 
  358.         return curSize; 
  359.     } 
  360.  
  361.     public int getLength() { 
  362.         return length; 
  363.     } 
  364.  
  365.     public void setContext(Context context) { 
  366.         this.context = context; 
  367.     } 
  368.      
  369.     public Context getContext() { 
  370.         return context; 
  371.     } 
  372.  
  373.     public void setListView(ListView listView) { 
  374.         this.listView = listView; 
  375.     } 

  好了,下載任務已經啓動了,接下來就該開始管理了。先說說以前錯誤的思路,估計大多數的網友可能跟我同樣,一想到列表首先想到的就是ListView,這多簡單啊,放一個ListView,繼承BaseAdapter寫個本身的Adapter,而後一展示,完事了,so easy。我也是這麼想的,這省事啊,用了之後才發現,確實省事,不過更新ProgressBar的時候但是給我愁死了,不管怎麼着都不能正常更新ProgressBar。在這個地方鑽了一週的牛角尖,昨兒個忽然靈光乍現,幹嗎給本身挖個坑,誰說列表就非得用ListView了,我本身寫個列表不就得了。app

  先來看看列表頁都有些什麼dom

  
  
  
  
  1. package com.test.muldownloadtest; 
  2.  
  3. import android.app.Activity; 
  4. import android.os.Bundle; 
  5. import android.view.View; 
  6. import android.view.View.OnClickListener; 
  7. import android.view.Window; 
  8. import android.widget.Button; 
  9. import android.widget.LinearLayout; 
  10. import android.widget.ScrollView; 
  11.  
  12. public class DownloadManagerActivity extends Activity implements OnClickListener { 
  13.  
  14.     private ScrollView scDownload; 
  15.     private LinearLayout llDownloadLayout; 
  16.      
  17.     @Override 
  18.     protected void onCreate(Bundle savedInstanceState) { 
  19.         super.onCreate(savedInstanceState); 
  20.          
  21.         this.requestWindowFeature(Window.FEATURE_NO_TITLE); 
  22.          
  23.         this.setContentView(R.layout.download_manager_layout); 
  24.          
  25.         initView(); 
  26.     } 
  27.      
  28.     @Override 
  29.     protected void onResume() { 
  30.         super.onResume(); 
  31.          
  32.         refreshItemView(); 
  33.     } 
  34.      
  35.     private void initView(){ 
  36.          
  37.         Button btnGoback = (Button) this.findViewById(R.id.btnGoback); 
  38.         btnGoback.setOnClickListener(this); 
  39.          
  40.         scDownload = (ScrollView) this.findViewById(R.id.svDownload); 
  41.         scDownload.setSmoothScrollingEnabled(true); 
  42.          
  43.         llDownloadLayout = (LinearLayout) this.findViewById(R.id.llDownloadLyout); 
  44.     } 
  45.      
  46.     /** 
  47.      * 列表中的每一項 
  48.      */ 
  49.     private void refreshItemView(){ 
  50.         for (int i = 0; i < AppConstants.listUrl.size(); i++) { 
  51.             DownloadItemView downloadItemView = new DownloadItemView(this, AppConstants.listUrl.get(i), i); 
  52.             downloadItemView.setId(i); 
  53.             downloadItemView.setTag("downloadItemView_"+i); 
  54.             llDownloadLayout.addView(downloadItemView); 
  55.         } 
  56.     } 
  57.  
  58.     public void onClick(View v) { 
  59.         switch (v.getId()) { 
  60.         case R.id.btnExit: 
  61.             this.finish(); 
  62.             break
  63.         case R.id.btnGoback: 
  64.             this.finish(); 
  65.             break
  66.         default
  67.             break
  68.         } 
  69.     } 

  很簡單,一個ScrollView,在這個ScrollView中在內嵌一個LinearLayout,用這個LinearLayout來存儲每個列表項。其實列表項很簡單,最基本只要三個控件就好了——ProgressBar、TextView、Button。一個是進度條,一個顯示百分比,一個用來暫停/繼續,偷個懶,這個佈局文件就不列出來了,咱就看看這個Button都幹嗎了。 異步

  
  
  
  
  1. public void onClick(View v) { 
  2.     switch (v.getId()) { 
  3.         case R.id.btnPauseOrResume: 
  4.             String btnTag = (String) btnPauseOrResume.getTag(); 
  5.             if (btnTag.equals("pause")) { 
  6.                 resumeDownload(); 
  7.             } 
  8.             else if (btnTag.equals("resume")) { 
  9.                 pauseDownload(); 
  10.             } 
  11.             break; 
  12.         default: 
  13.             break; 
  14.     } 
  15.  
  16. private void pauseDownload(){ 
  17.     btnPauseOrResume.setTag("pause"); 
  18.     btnPauseOrResume.setText(R.string.download_resume); 
  19.      
  20.     Async pauseTask = null
  21.     // 判斷當前被中止的這個任務在任務列表中是否存在,若是存在就暫停 
  22.     if (AppConstants.linkedMapDownloading.containsKey(urlString)) { 
  23.         pauseTask = AppConstants.linkedMapDownloading.get(urlString); 
  24.         if (pauseTask != null) { 
  25.             pauseTask.pause(); 
  26.         } 
  27.     } 
  28.  
  29. private void resumeDownload(){ 
  30.     btnPauseOrResume.setTag("resume"); 
  31.     btnPauseOrResume.setText(R.string.download_pause); 
  32.      
  33.     Async continueTask = null
  34.     // 判斷當前被中止的這個任務在任務列表中是否存在,若是存在就繼續 
  35.     if (AppConstants.linkedMapDownloading.containsKey(urlString)) { 
  36.         continueTask = AppConstants.linkedMapDownloading.get(urlString); 
  37.         if (continueTask != null) { 
  38.             continueTask.continued(); 
  39.         } 
  40.     } 
  41.     handler.postDelayed(runnable, 1000); 

  簡單吧,就是判斷一下當前按鈕的Tag,而後根據Tag的值,來判斷是繼續下載,仍是暫停下載。而這個暫停仍是繼續,其實只是修改下Async中的暫停標記的值,即paused是true仍是false。async

  到此,核心功能展現完畢。附效果圖一張

 

 

  Demo下載地址 http://www.eoeandroid.com/forum.php?mod=viewthread&tid=230817&page=1&extra=#pid2067386

  參考資料:
  1.http://developer.android.com/reference/android/os/AsyncTask.html
  2.http://www.myexception.cn/android/725267.html
  3.http://blog.csdn.net/shimiso/article/details/6763664
  4.http://blog.csdn.net/shimiso/article/details/6763986
  5.http://lichen.blog.51cto.com/697816/486868

相關文章
相關標籤/搜索