閱讀733 返回首頁    go 阿裏雲 go 技術社區[雲棲]


多線程下載

樓主三年磨劍(當然不是磨著一把劍),傾血奉獻Android多線程下載Demo。有的人就問了“怎麼寫來寫去還是Demo?”,因為老哥我實在太忙了,
每天寫一點,寫到現在也才寫了個下載器,也就差下載管理類就是個完整的模塊了。對於新手學習這已經足夠了,不對,是完全足夠了。

這不僅僅隻是一個簡單的Demo,這絕對是你前所未見的商業級別的範例,集支持多線程下載,斷點續傳,隻使用wifi網絡下載,顯示下載速度,人性化提示
及超強的容錯機製多功能於一體,絕對的實用,絕對的專業。

當然我寫這個是為了下載apk的,大家稍微改一改就可以寫成更通用的下載器。唯一有點不足的地方就是在Android上使用RandomAccessFile在創建大文件的時候
速度有些慢,導致前幾秒的進度都為0。不知道有沒有人可以幫我解決這個問題。

下麵給出關鍵代碼。

[java] view plaincopy
  1. package com.h3c.DownloadEngine;  
  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.URL;  
  9. import java.util.ArrayList;  
  10.   
  11. import android.content.Context;  
  12. import android.os.Environment;  
  13. import android.util.Log;  
  14.   
  15. import com.h3c.DownloadEngine.common.DownloaderErrorException;  
  16. import com.h3c.DownloadEngine.common.EngineConstants;  
  17. import com.h3c.DownloadEngine.common.EngineUtil;  
  18. import com.h3c.DownloadEngine.common.EngineVariable;  
  19. import com.h3c.DownloadEngine.db.EngineDBOperator;  
  20. import com.h3c.DownloadEngine.entity.DownloadBean;  
  21.   
  22. public class Downloader {  
  23.     private final static String TAG = "Downloader";  
  24.     private final static byte[] lock_getFileSize = new byte[1];  
  25.     private final static byte[] lock_refresh_progress = new byte[1];  
  26.   
  27.     private int mThreadCount = 4;// 默認子線程數為4個  
  28.     private int bufferSize = 1024 * 16// 16K 一個塊  
  29.   
  30.     private DownloadBean mBean;// 注意,這裏是dwonloader的bean不是其subBean  
  31.     private Context mContext;  
  32.     private DownloadEngineCallback mCallback;  
  33.     private EngineDBOperator mDBOper;  
  34.     private int mDoneThreadCount = 0;// 完成的線程數  
  35.     private int mState = EngineConstants.DOWNLOAD_STATE_INIT;// 下載器狀態  
  36.     private ArrayList<DownloadBean> mBeans = new ArrayList<DownloadBean>(  
  37.             mThreadCount);  
  38.   
  39.     public Downloader(DownloadBean bean, Context context,  
  40.             DownloadEngineCallback callback) throws DownloaderErrorException {  
  41.         this.mBean = bean;  
  42.         this.mContext = context;  
  43.         this.mCallback = callback;  
  44.         this.mDBOper = EngineDBOperator.getInstance(context);  
  45.   
  46.         if (this.mDBOper != null) {  
  47.             if (this.mDBOper.isHasDownloadTaskByUrl(bean.url)) {// 如果此任務已經存放進數據庫  
  48.                 getDownloaderInfoFromDB(bean);  
  49.             } else {// 插入信息至數據庫  
  50.                 addDownloaderInfoToDB(bean);  
  51.             }  
  52.         } else {  
  53.             callBackError("Downloader錯誤,可能是EngineDBOperator為Null.");  
  54.             throw new DownloaderErrorException(  
  55.                     "Downloader錯誤,可能是EngineDBOperator為Null.");  
  56.         }  
  57.     }  
  58.   
  59.     public DownloadBean getDownloaderInfo() {  
  60.         return mBean;  
  61.     }  
  62.   
  63.     public int getDownloaderState() {  
  64.         return mState;  
  65.     }  
  66.   
  67.     /** 
  68.      * 請求初始化 
  69.      *  
  70.      * @param state 
  71.      */  
  72.     protected void setDownloaderState(int state) {  
  73.         mState = state;  
  74.         if (state == EngineConstants.DOWNLOAD_STATE_INIT) {  
  75.             mBean.currentPosition = 0;  
  76.         }  
  77.     }  
  78.   
  79.     /** 
  80.      * 加入下載信息進入數據庫,此方法用於剛剛初始化Downloader,且數據庫中沒有該任務的時候 
  81.      *  
  82.      * @param bean 
  83.      * @throws DownloaderErrorException 
  84.      */  
  85.     private void addDownloaderInfoToDB(DownloadBean bean)  
  86.             throws DownloaderErrorException {  
  87.         if (mState != EngineConstants.DOWNLOAD_STATE_INIT  
  88.                 && mState != EngineConstants.DOWNLOAD_STATE_STOP  
  89.                 && mState != EngineConstants.DOWNLOAD_STATE_ERROR) {  
  90.             callBackError("這個任務已經加入到數據庫中了");  
  91.             throw new DownloaderErrorException("這個任務已經加入到數據庫中了");  
  92.         }  
  93.   
  94.         if (mDBOper != null) {  
  95.             long fileSize = bean.fileSize;  
  96.             if (mBeans.size() > 0) {  
  97.                 mBeans.clear();  
  98.             }  
  99.   
  100.             try {  
  101.                 if (fileSize > 0) {// 判斷傳入的fileSize大小,如果大於0,就不用從網絡中獲取,直接初始化N個子下載器  
  102.                     if (!hasSpaceInSDCard()) {  
  103.                         return;  
  104.                     }  
  105.                     long range = fileSize / mThreadCount;// 文件分段值  
  106.                     for (int i = 0; i < mThreadCount - 1; i++) {  
  107.                         DownloadBean subBean = (DownloadBean) bean.clone();  
  108.                         subBean.threadId = i;  
  109.                         subBean.startPosition = i * range;  
  110.                         subBean.endPosition = (i + 1) * range - 1;  
  111.                         mBeans.add(subBean);  
  112.                     }  
  113.   
  114.                     DownloadBean subBean = (DownloadBean) bean.clone();  
  115.                     subBean.threadId = mThreadCount - 1;  
  116.                     subBean.startPosition = (mThreadCount - 1) * range;  
  117.                     subBean.endPosition = fileSize - 1;  
  118.                     mBeans.add(subBean);  
  119.                 } else {// 如果等於0,就直接初始化N個0大小的子下載器  
  120.                     for (int n = 0; n < mThreadCount - 1; n++) {  
  121.                         DownloadBean subBean = (DownloadBean) bean.clone();  
  122.                         subBean.threadId = n;  
  123.                         mBeans.add(subBean);  
  124.                     }  
  125.   
  126.                     DownloadBean subBean = (DownloadBean) bean.clone();  
  127.                     subBean.threadId = mThreadCount - 1;  
  128.                     mBeans.add(subBean);  
  129.                 }  
  130.   
  131.                 mDBOper.addDownloadTask(mBeans);  
  132.                 if (bean.fileSize > 0) {// 如果文件大小已經獲取就進入等待狀態  
  133.                     mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下載器進入等待狀態  
  134.                 } else {// 文件大小未獲取就開啟線程去獲取文件大小並更新子下載器中的內容  
  135.                     new Thread(new Runnable() {  
  136.                         @Override  
  137.                         public void run() {  
  138.                             boolean flag = false;  
  139.                             synchronized (lock_getFileSize) {  
  140.                                 flag = getFileSizeByNetwork(mBean);  
  141.                             }  
  142.                             if (flag) {  
  143.                                 mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下載器進入等待狀態  
  144.                             } else {  
  145.                                 Log.e(TAG, "從網絡中獲取文件大小失敗 1");  
  146.                             }  
  147.                         }  
  148.                     }).start();  
  149.   
  150.                 }  
  151.             } catch (CloneNotSupportedException e) {  
  152.                 e.printStackTrace();  
  153.             }  
  154.         } else {  
  155.             callBackError("addDownloaderInfoToDB錯誤,可能是EngineDBOperator為Null.");  
  156.             throw new DownloaderErrorException(  
  157.                     "addDownloaderInfoToDB錯誤,可能是EngineDBOperator為Null.");  
  158.         }  
  159.     }  
  160.   
  161.     /** 
  162.      * 從數據庫中讀取下載器信息 
  163.      *  
  164.      * @param bean 
  165.      * @throws DownloaderErrorException 
  166.      */  
  167.     private void getDownloaderInfoFromDB(DownloadBean bean)  
  168.             throws DownloaderErrorException {  
  169.         if (mDBOper != null) {  
  170.             mBeans.clear();  
  171.             mBeans = mDBOper.getDownloadTaskByUrl(bean.url);  
  172.   
  173.             mBean.currentPosition = 0;  
  174.             mBean.fileSize = 0;  
  175.             mThreadCount = mBeans.size();  
  176.             for (DownloadBean subBean : mBeans) {  
  177.                 mBean.currentPosition += subBean.currentPosition;  
  178.                 if (subBean.fileSize > mBean.fileSize) {  
  179.                     mBean.fileSize = subBean.fileSize;  
  180.                 }  
  181.             }  
  182.   
  183.             if (mBean.fileSize < 1) {  
  184.                 new Thread(new Runnable() {  
  185.                     @Override  
  186.                     public void run() {  
  187.                         boolean flag = false;  
  188.                         synchronized (lock_getFileSize) {  
  189.                             flag = getFileSizeByNetwork(mBean);  
  190.                         }  
  191.                         if (flag) {  
  192.                             mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下載器進入等待狀態  
  193.                         } else {  
  194.                             Log.e(TAG, "從網絡中獲取文件大小失敗 2");  
  195.                         }  
  196.                     }  
  197.                 }).start();  
  198.             } else {  
  199.                 mState = EngineConstants.DOWNLOAD_STATE_WAITTING;// 下載器進入等待狀態  
  200.             }  
  201.         } else {  
  202.             callBackError("getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");  
  203.             throw new DownloaderErrorException(  
  204.                     "getDownloaderInfoFromDB Error,May be EngineDBOperator is Null.");  
  205.         }  
  206.     }  
  207.   
  208.     /** 
  209.      * 從網絡中獲取文件大小,並更新listBeans 
  210.      */  
  211.     private boolean getFileSizeByNetwork(DownloadBean bean) {  
  212.         HttpURLConnection connection = null;  
  213.         long fileSize = bean.fileSize;  
  214.         try {  
  215.             if (fileSize <= 0) {// 如果沒有傳入文件大小就從網絡中獲取  
  216.                 URL url = new URL(bean.url);  
  217.                 connection = (HttpURLConnection) url.openConnection();  
  218.                 connection.setConnectTimeout(5000);  
  219.                 connection.setReadTimeout(8000);  
  220.   
  221.                 if (android.os.Build.VERSION.SDK_INT > 10) {// 規避2.x上因為加入setRM導致連接超時的bug  
  222.                     connection.setRequestMethod("HEAD");// head  
  223.                 }  
  224.   
  225.                 int resopnseCode = connection.getResponseCode();  
  226.                 if (resopnseCode != 200 && resopnseCode != 206) {  
  227.                     callBackError("http返回碼不正確:" + resopnseCode);  
  228.                     return false;  
  229.                 }  
  230.                 // 獲得文件大小  
  231.                 fileSize = connection.getContentLength();  
  232.                 mBean.fileSize = fileSize;  
  233.   
  234.                 if (fileSize <= 0) {  
  235.                     callBackError("無法從服務器上獲得文件大小" + fileSize);  
  236.                     return false;  
  237.                 }  
  238.   
  239.                 // if (connection.getHeaderField("Content-Range") == null) {  
  240.                 // Log.e(TAG, "服務器不支持斷點續傳");  
  241.                 // mThreadCount = 1;  
  242.                 // }  
  243.   
  244.                 // 如果沒有存儲空間了  
  245.                 if (!hasSpaceInSDCard()) {  
  246.                     return false;  
  247.                 }  
  248.   
  249.                 long range = fileSize / mThreadCount;// 文件分段值  
  250.                 // 更新listBean  
  251.                 for (int i = 0; i < mThreadCount - 1; i++) {  
  252.                     DownloadBean subBean = mBeans.get(i);  
  253.                     subBean.fileSize = fileSize;  
  254.                     subBean.startPosition = i * range;  
  255.                     subBean.endPosition = (i + 1) * range - 1;  
  256.                 }  
  257.   
  258.                 DownloadBean subBean = mBeans.get(mThreadCount - 1);  
  259.                 subBean.fileSize = fileSize;  
  260.                 subBean.startPosition = (mThreadCount - 1) * range;  
  261.                 subBean.endPosition = fileSize - 1;  
  262.   
  263.                 // 更新數據庫  
  264.                 if (mDBOper != null) {  
  265.                     mDBOper.updateTaskCompleteSize(mBeans, mBean.url);  
  266.                 } else {  
  267.                     callBackError("getFileSizeByNetwork錯誤,可能是EngineDBOperator is Null.");  
  268.                     throw new DownloaderErrorException(  
  269.                             "getFileSizeByNetwork錯誤,可能是EngineDBOperator is Null.");  
  270.                 }  
  271.                 return true;  
  272.             } else {// 文件有大小就直接退出  
  273.                 return true;  
  274.             }  
  275.         } catch (Exception e) {  
  276.             callBackError("從服務器獲取文件大小超時");  
  277.             e.printStackTrace();  
  278.         } finally {  
  279.             if (connection != null) {  
  280.                 connection.disconnect();  
  281.             }  
  282.         }  
  283.         return false;  
  284.     }  
  285.   
  286.     /** 
  287.      * 開始下載,可能多次調用 
  288.      */  
  289.     public void startDownloader() {  
  290.         if (mState == EngineConstants.DOWNLOAD_STATE_DOWNLOADING) {// 如果正在下載就return  
  291.             return;  
  292.         }  
  293.   
  294.         if (mBean == null) {  
  295.             callBackError("下載器沒有初始化");  
  296.             return;  
  297.         }  
  298.   
  299.         File file = new File(mBean.savePath);  
  300.         File parentDirectory = file.getParentFile();  
  301.         if (!parentDirectory.exists()) {  
  302.             parentDirectory.mkdirs();  
  303.         }  
  304.   
  305.         if (!file.exists()) {  
  306.             try {  
  307.                 file.createNewFile();  
  308.             } catch (IOException e) {  
  309.                 e.printStackTrace();  
  310.             }  
  311.         }  
  312.           
  313.         if (mBeans.size() < 1) {// 防止由於發生錯誤導致清空了mBeans列表,但是又重新開始了任務,所有要再次初始化mBeans  
  314.             try {  
  315.                 addDownloaderInfoToDB(mBean);  
  316.             } catch (DownloaderErrorException e) {  
  317.                 e.printStackTrace();  
  318.                 return;  
  319.             }  
  320.         }  
  321.   
  322.         /** 
  323.          * 隻有獲得文件大小後才會開始下載 
  324.          */  
  325.         synchronized (lock_getFileSize) {  
  326.             if (mState == EngineConstants.DOWNLOAD_STATE_INIT) {// 獲取文件大小失敗,重新獲取  
  327.                 boolean flag = getFileSizeByNetwork(mBean);  
  328.                 if (!flag) {  
  329.                     callBackError("獲取文件大小失敗");  
  330.                     return;  
  331.                 }  
  332.             }  
  333.         }  
  334.   
  335.         mState = EngineConstants.DOWNLOAD_STATE_DOWNLOADING;  
  336.         mDBOper.removePauseFileByUrl(mBean.url);// 從暫停列表中移除  
  337.         mDoneThreadCount = 0;// 初始化完成線程數  
  338.   
  339.         for (DownloadBean bean : mBeans) {  
  340.             if (bean.currentPosition < (bean.endPosition - bean.startPosition)) {// 如果該線程屬於沒有下載完成的  
  341.                 HamalThread hamalThread = new HamalThread(bean);  
  342.                 hamalThread.start();  
  343.             } else {// 已經完成的線程不需要重新創建  
  344.                 mDoneThreadCount++;  
  345.             }  
  346.         }  
  347.           
  348.         if (mDoneThreadCount == mThreadCount) {// 下載完成  
  349.             downloaderDone();  
  350.         }  
  351.     }  
  352.   
  353.     private class HamalThread extends Thread {  
  354.         private int threadId;  
  355.         private long startPos;  
  356.         private long endPos;  
  357.         private long compeleteSize;  
  358.         private String urlstr;  
  359.   
  360.         public HamalThread(DownloadBean bean) {  
  361.             this.threadId = bean.threadId;  
  362.             this.startPos = bean.startPosition;  
  363.             this.endPos = bean.endPosition;  
  364.             this.compeleteSize = bean.currentPosition;  
  365.             this.urlstr = bean.url;  
  366.         }  
  367.   
  368.         @Override  
  369.         public void run() {  
  370.             HttpURLConnection connection = null;  
  371.             RandomAccessFile randomAccessFile = null;  
  372.             InputStream is = null;  
  373.             try {  
  374.                 URL url = new URL(urlstr);  
  375.                 connection = (HttpURLConnection) url.openConnection();  
  376.                 connection.setConnectTimeout(5000);  
  377.                 connection.setReadTimeout(8000);  
  378.                 connection.setRequestMethod("GET");  
  379.                 if (mThreadCount > 1) {// 多線程下載  
  380.                     // 設置範圍,格式為Range:bytes x-y;  
  381.                     connection.setRequestProperty("Range""bytes="  
  382.                             + (startPos + compeleteSize) + "-" + endPos);  
  383.                 }  
  384.   
  385.                 randomAccessFile = new RandomAccessFile(mBean.savePath, "rwd");  
  386.                 randomAccessFile.seek(startPos + compeleteSize);  
  387.                 // 將要下載的文件寫到保存在保存路徑下的文件中  
  388.                 is = connection.getInputStream();  
  389.                 byte[] buffer = new byte[bufferSize];  
  390.                 int length = -1;  
  391.                 EngineUtil eUtil = EngineUtil.getInstance();  
  392.   
  393.                 if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果隻能是3G下載  
  394.                     if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且當前網絡不是Wifi  
  395.                         interruptDownloader();  
  396.                         return;  
  397.                     }  
  398.                 }  
  399.   
  400.                 while ((length = is.read(buffer)) != -1) {  
  401.                     // 網絡判斷  
  402.                     if (EngineVariable.SUPPORT_NETWORK_TYPE == EngineConstants.DOWNLOAD_NETWORK_ONLYWIFI) {// 如果隻能是3G下載  
  403.                         if (eUtil.getNetworkType() != EngineConstants.NETWORK_STATE_WIFI) {// 且當前網絡不是Wifi  
  404.                             interruptDownloader();  
  405.                             return;  
  406.                         }  
  407.                     }  
  408.   
  409.                     randomAccessFile.write(buffer, 0, length);  
  410.                     compeleteSize += length;  
  411.                     synchronized (lock_refresh_progress) {  
  412.                         mBean.currentPosition += length;  
  413.                     }  
  414.                     // 更新數據庫中的下載信息  
  415.                     mDBOper.updateTaskCompleteSize(threadId, compeleteSize,  
  416.                             urlstr);  
  417.                     if (mState == EngineConstants.DOWNLOAD_STATE_PAUSE  
  418.                             || mState == EngineConstants.DOWNLOAD_STATE_INTERRUPT  
  419.                             || mState == EngineConstants.DOWNLOAD_STATE_STOP  
  420.                             || mState == EngineConstants.DOWNLOAD_STATE_ERROR) {// 暫停  
  421.                         return;  
  422.                     }  
  423.                 }  
  424.   
  425.                 // 該子線程下載完成  
  426.                 mDoneThreadCount++;  
  427.             } catch (Exception e) {  
  428.                 Log.e(TAG, "下載途中斷掉了連接...");  
  429.                 interruptDownloader();  
  430.                 e.printStackTrace();  
  431.             } finally {  
  432.                 try {  
  433.                     is.close();  
  434.                     randomAccessFile.close();  
  435.                     connection.disconnect();  
  436.                 } catch (Exception e) {  
  437.                     e.printStackTrace();  
  438.                 }  
  439.             }  
  440.   
  441.             if (mDoneThreadCount == mThreadCount) {  
  442.                 downloaderDone();  
  443.             }  
  444.         }  
  445.     }  
  446.   
  447.     /** 
  448.      * 獲取下載進度 
  449.      *  
  450.      * @return 
  451.      */  
  452.     public int getProgress() {  
  453.         if (mBean.fileSize < 1) {  
  454.             return 0;  
  455.         }  
  456.         return (int) (mBean.currentPosition * 100 / mBean.fileSize);  
  457.     }  
  458.   
  459.     /** 
  460.      * 暫停下載 
  461.      */  
  462.     public void pauseDownloader() {  
  463.         mState = EngineConstants.DOWNLOAD_STATE_PAUSE;  
  464.         mDBOper.addPauseFile(mBean.url, mBean.packageName, mBean.fileId);  
  465.     }  
  466.   
  467.     /** 
  468.      * 中斷下載(非人為的暫停) 
  469.      */  
  470.     private void interruptDownloader() {  
  471.         mState = EngineConstants.DOWNLOAD_STATE_INTERRUPT;  
  472.     }  
  473.   
  474.     /** 
  475.      * 結束下載 
  476.      */  
  477.     public void stopDownloader() {  
  478.         mState = EngineConstants.DOWNLOAD_STATE_STOP;  
  479.         mBean.currentPosition = 0;  
  480.         removeDownloaderInfo(mBean.url);  
  481.     }  
  482.   
  483.     /** 
  484.      * 清除下載的信息 
  485.      *  
  486.      * @param urlstr 
  487.      */  
  488.     private void removeDownloaderInfo(String urlstr) {  
  489.         mDBOper.deleteDownloadTaskByUrl(urlstr);  
  490.         mDBOper.removePauseFileByUrl(urlstr);  
  491.         mBeans.clear();  
  492.     }  
  493.   
  494.     /** 
  495.      * 下載完成 
  496.      */  
  497.     private void downloaderDone() {  
  498.         mState = EngineConstants.DOWNLOAD_STATE_DONE;  
  499.         mBean.doneTime = System.currentTimeMillis();  
  500.         mCallback.callbackWhenDownloadTaskListener(mState, mBean,  
  501.                 mBean.fileName + "下載完成");  
  502.   
  503.         removeDownloaderInfo(mBean.url);  
  504.         mDBOper.addCompleteTask(mBean);// 將完成信息保存至數據庫  
  505.     }  
  506.   
  507.     /** 
  508.      * 出現錯誤時候回調 
  509.      *  
  510.      * @param info 
  511.      */  
  512.     private void callBackError(String info) {  
  513.         mState = EngineConstants.DOWNLOAD_STATE_ERROR;  
  514.         mCallback.callbackWhenDownloadTaskListener(mState, mBean, info);  
  515.         removeDownloaderInfo(mBean.url);  
  516.     }  
  517.   
  518.     /** 
  519.      * 判斷SD卡上是否留有足夠的空間 
  520.      */  
  521.     private boolean hasSpaceInSDCard() {  
  522.         if (mBean.fileSize > EngineUtil.getInstance().getFreeSpaceAtDirectory(  
  523.                 Environment.getExternalStorageDirectory().getAbsolutePath())) {  
  524.             callBackError("存儲卡空間不夠");  
  525.             return false;  
  526.         }  
  527.         return true;  
  528.     }  
  529. }  

 

歡迎吐槽,歡迎提供建議,歡迎提供改進方法。

整個架構詳見源碼,絕對值得你下載收藏!這一切都是免費的,對,你不要懷疑,商業級別的源碼都是免費的。
如果你覺得好,請幫忙下載源碼下載

源碼:https://download.csdn.net/detail/h3c4lenovo/5987789

最後更新:2017-04-03 12:55:18

  上一篇:go 阿裏雲Linux服務器掛載硬盤分區
  下一篇:go 2012藍橋杯【初賽試題】 巧排撲克牌