Android下載文件相關--多線程下載和斷點續傳

1.簡單一例子-Android經過HTTP協議實現多線程下載


[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. import java.io.File;  
  2. import java.io.InputStream;  
  3. import java.io.RandomAccessFile;  
  4. import java.net.HttpURLConnection;  
  5. import java.net.URL;  
  6.   
  7. public class MulThreadDownload {  
  8.   
  9.     /** 
  10.      * @param args 
  11.      */  
  12.     public static void main(String[] args) {  
  13.         String path = "http://net.itcast.cn/QQWubiSetup.exe";  
  14.         try {  
  15.             new MulThreadDownload().download(path, 3);  
  16.         } catch (Exception e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20.     /** 
  21.      * 從路徑中獲取文件名稱 
  22.      * @param path 下載路徑 
  23.      * @return 
  24.      */  
  25.     public static String getFilename(String path){  
  26.         return path.substring(path.lastIndexOf('/')+1);  
  27.     }  
  28.     /** 
  29.      * 下載文件 
  30.      * @param path 下載路徑 
  31.      * @param threadsize 線程數 
  32.      */  
  33.     public void download(String path, int threadsize) throws Exception{  
  34.         URL url = new URL(path);  
  35.         HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  36.         conn.setRequestMethod("GET");  
  37.         conn.setConnectTimeout(5 * 1000);  
  38.         int filelength = conn.getContentLength();//獲取要下載的文件的長度  
  39.         String filename = getFilename(path);//從路徑中獲取文件名稱  
  40.         File saveFile = new File(filename);  
  41.         RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");  
  42.         accessFile.setLength(filelength);//設置本地文件的長度和下載文件相同  
  43.         accessFile.close();  
  44.         //計算每條線程下載的數據長度  
  45.         int block = filelength%threadsize==0? filelength/threadsize : filelength/threadsize+1;  
  46.         for(int threadid=0 ; threadid < threadsize ; threadid++){  
  47.             new DownloadThread(url, saveFile, block, threadid).start();  
  48.         }  
  49.     }  
  50.       
  51.     private final class DownloadThread extends Thread{  
  52.         private URL url;  
  53.         private File saveFile;  
  54.         private int block;//每條線程下載的數據長度  
  55.         private int threadid;//線程id  
  56.   
  57.         public DownloadThread(URL url, File saveFile, int block, int threadid) {  
  58.             this.url = url;  
  59.             this.saveFile = saveFile;  
  60.             this.block = block;  
  61.             this.threadid = threadid;  
  62.         }  
  63.   
  64.         @Override  
  65.         public void run() {  
  66.             //計算開始位置公式:線程id*每條線程下載的數據長度= ?  
  67.             //計算結束位置公式:(線程id +1)*每條線程下載的數據長度-1 =?  
  68.             int startposition = threadid * block;  
  69.             int endposition = (threadid + 1 ) * block - 1;  
  70.             try {  
  71.                 RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");  
  72.                 accessFile.seek(startposition);//設置從什麼位置開始寫入數據  
  73.                 HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
  74.                 conn.setRequestMethod("GET");  
  75.                 conn.setConnectTimeout(5 * 1000);  
  76.                 conn.setRequestProperty("Range""bytes="+ startposition+ "-"+ endposition);  
  77.                 InputStream inStream = conn.getInputStream();  
  78.                 byte[] buffer = new byte[1024];  
  79.                 int len = 0;  
  80.                 while( (len=inStream.read(buffer)) != -1 ){  
  81.                     accessFile.write(buffer, 0, len);  
  82.                 }  
  83.                 inStream.close();  
  84.                 accessFile.close();  
  85.                 System.out.println("線程id:"+ threadid+ "下載完成");  
  86.             } catch (Exception e) {  
  87.                 e.printStackTrace();  
  88.             }  
  89.         }         
  90.     }  
  91.   
  92. }  

2.Android基於HTTP協議的多線程斷點下載器的實現


1、首先寫這篇文章以前,要了解實現該Android多線程斷點下載器的幾個知識點html

 1.多線程下載的原理,以下圖所示java


注意:因爲Android移動設備和PC機的處理器仍是不能相比,因此開闢的子線程建議不要多於5條。固然如今某些高端機子的處理器能力比較強了,就能夠多開闢幾條子線程。android

二、爲了實現斷點下載,採用數據庫方式記錄下載的進度,這樣當你將該應用退出後,下次點擊下載的時候,程序會去查看該下載連接是否存在下載記錄,若是存在下載記錄就會判斷下載的進度,如何從上次下載的進度繼續開始下載。正則表達式

三、特別注意在主線程裏不能執行一件比較耗時的工做,不然會因主線程阻塞而沒法處理用戶的輸入事件,致使「應用無響應」錯誤的出現。耗時的工做應該在子線程裏執行。sql

四、UI控件畫面的重繪(更新)是由主線程負責處理的,不能在子線程中更新UI控件的值。能夠採用Handler機制,在主線程建立Handler對象,在子線程發送消息給主線程所綁定的消息隊列,從消息中獲取UI控件的值,而後在主線程中進行UI控件的重繪(更新)工做。
五、瞭解HTTP協議各個頭字段的含義
數據庫


2、將該下載器的具體實現代碼展示出來數組

step一、首先查看整個Android項目的結構圖瀏覽器

                                           

step2:設計應用的UI界面   /layout/activity_main.xml緩存

[html]  view plain copy
  1. <span style="font-size:18px"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:orientation="vertical" android:layout_width="fill_parent"  
  3.     android:layout_height="fill_parent">  
  4.   
  5.     <TextView  
  6.         android:layout_width="fill_parent"  
  7.         android:layout_height="wrap_content"  
  8.         android:text="@string/path" />  
  9.   
  10.     <EditText  
  11.         android:id="@+id/path"  
  12.         android:layout_width="fill_parent"  
  13.         android:layout_height="wrap_content"  
  14.         android:text="http://192.168.1.100:8080/Hello/a.mp4" />  
  15.   
  16.     <LinearLayout  
  17.         android:layout_width="fill_parent"  
  18.         android:layout_height="wrap_content"  
  19.         android:orientation="horizontal" >  
  20.   
  21.         <Button  
  22.             android:id="@+id/downloadbutton"  
  23.             android:layout_width="wrap_content"  
  24.             android:layout_height="wrap_content"  
  25.             android:text="@string/startbutton" />  
  26.   
  27.         <Button  
  28.             android:id="@+id/stopbutton"  
  29.             android:layout_width="wrap_content"  
  30.             android:layout_height="wrap_content"  
  31.             android:enabled="false"  
  32.             android:text="@string/stopbutton" />  
  33.     </LinearLayout>  
  34.   
  35.     <ProgressBar  
  36.         android:id="@+id/progressBar"  
  37.         style="?android:attr/progressBarStyleHorizontal"  
  38.         android:layout_width="fill_parent"  
  39.         android:layout_height="18dp" />  
  40.   
  41.     <TextView  
  42.         android:id="@+id/resultView"  
  43.         android:layout_width="fill_parent"  
  44.         android:layout_height="wrap_content"  
  45.         android:gravity="center" />  
  46. </LinearLayout></span>  


/values/string.xml服務器

[html]  view plain copy
  1. <span style="font-size:18px"><?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <string name="action_settings">Settings</string>  
  4.     <string name="hello_world">Hello world!</string>  
  5.     <string name="app_name">多線程斷點下載器_歐陽鵬編寫</string>  
  6.     <string name="path">下載路徑</string>  
  7.     <string name="startbutton">開始下載</string>  
  8.     <string name="success">下載完成</string>  
  9.     <string name="error">下載失敗</string>  
  10.     <string name="stopbutton">中止下載</string>  
  11.     <string name="sdcarderror">SDCard不存在或者寫保護</string>  
  12. </resources></span>  


step三、程序主應用 cn.oyp.download.MainActivity.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download;  
  2.   
  3. import java.io.File;  
  4.   
  5. import android.app.Activity;  
  6. import android.os.Bundle;  
  7. import android.os.Environment;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.view.View;  
  11. import android.widget.Button;  
  12. import android.widget.EditText;  
  13. import android.widget.ProgressBar;  
  14. import android.widget.TextView;  
  15. import android.widget.Toast;  
  16. import cn.oyp.download.downloader.DownloadProgressListener;  
  17. import cn.oyp.download.downloader.FileDownloader;  
  18.   
  19. public class MainActivity extends Activity {  
  20.   
  21.     /** 下載路徑文本框 **/  
  22.     private EditText pathText;  
  23.     /** 下載按鈕 **/  
  24.     private Button downloadButton;  
  25.     /** 中止下載按鈕 **/  
  26.     private Button stopbutton;  
  27.     /** 下載進度條 **/  
  28.     private ProgressBar progressBar;  
  29.     /** 下載結果文本框,顯示下載的進度值 **/  
  30.     private TextView resultView;  
  31.   
  32.     /** Hanlder的做用是用於往建立Hander對象所在的線程所綁定的消息隊列發送消息 **/  
  33.     private Handler handler = new UIHander();  
  34.   
  35.     @Override  
  36.     protected void onCreate(Bundle savedInstanceState) {  
  37.         super.onCreate(savedInstanceState);  
  38.         setContentView(R.layout.activity_main);  
  39.   
  40.         /** 初始化各控件 **/  
  41.         pathText = (EditText) this.findViewById(R.id.path);  
  42.         downloadButton = (Button) this.findViewById(R.id.downloadbutton);  
  43.         stopbutton = (Button) this.findViewById(R.id.stopbutton);  
  44.         progressBar = (ProgressBar) this.findViewById(R.id.progressBar);  
  45.         resultView = (TextView) this.findViewById(R.id.resultView);  
  46.         /** 設置按鈕的監聽 **/  
  47.         ButtonClickListener listener = new ButtonClickListener();  
  48.         downloadButton.setOnClickListener(listener);  
  49.         stopbutton.setOnClickListener(listener);  
  50.     }  
  51.   
  52.     /** 
  53.      * Hanlder的做用是用於往建立Hander對象所在的線程所綁定的消息隊列發送消息 
  54.      */  
  55.     private final class UIHander extends Handler {  
  56.         public void handleMessage(Message msg) {  
  57.             switch (msg.what) {  
  58.             case 1:  
  59.                 int size = msg.getData().getInt("size"); // 獲取下載的進度值  
  60.                 progressBar.setProgress(size); // 實時更新,設置下載進度值  
  61.                 /** 計算下載的進度百分比 */  
  62.                 float num = (float) progressBar.getProgress()  
  63.                         / (float) progressBar.getMax();  
  64.                 int result = (int) (num * 100);  
  65.                 resultView.setText(result + "%"); // 設置下載結果文本框顯示下載的進度值  
  66.                 // 若是進度達到了進度最大值,即下載完畢  
  67.                 if (progressBar.getProgress() == progressBar.getMax()) {  
  68.                     Toast.makeText(getApplicationContext(), R.string.success, 1)  
  69.                             .show();// 下載成功  
  70.                 }  
  71.                 break;  
  72.             case -1:  
  73.                 Toast.makeText(getApplicationContext(), R.string.error, 1)  
  74.                         .show();// 下載出錯  
  75.                 break;  
  76.             }  
  77.         }  
  78.     }  
  79.       
  80.     /** 
  81.      * 按鈕監聽類 
  82.      */  
  83.     private final class ButtonClickListener implements View.OnClickListener {  
  84.         public void onClick(View v) {  
  85.             switch (v.getId()) {  
  86.             /** 若是是下載按鈕 */  
  87.             case R.id.downloadbutton:  
  88.                 String path = pathText.getText().toString();// 獲取下載路徑  
  89.                 // 判斷SD卡是否存在而且可寫  
  90.                 if (Environment.getExternalStorageState().equals(  
  91.                         Environment.MEDIA_MOUNTED)) {  
  92.                     // 獲取SD卡的路徑  
  93.                     File saveDir = Environment.getExternalStorageDirectory();  
  94.                     // 開始下載的相關操做  
  95.                     download(path, saveDir);  
  96.                 } else {  
  97.                     Toast.makeText(getApplicationContext(),  
  98.                             R.string.sdcarderror, 1).show();  
  99.                 }  
  100.                 downloadButton.setEnabled(false);  
  101.                 stopbutton.setEnabled(true);  
  102.                 break;  
  103.             /** 若是是中止下載按鈕 */  
  104.             case R.id.stopbutton:  
  105.                 exit();// 退出下載  
  106.                 downloadButton.setEnabled(true);  
  107.                 stopbutton.setEnabled(false);  
  108.                 break;  
  109.             }  
  110.         }  
  111.   
  112.         /** 
  113.          * UI控件畫面的重繪(更新)是由主線程負責處理的,若是在子線程中更新UI控件的值,更新後的值不會重繪到屏幕上 
  114.          * 必定要在主線程裏更新UI控件的值,這樣才能在屏幕上顯示出來,不能在子線程中更新UI控件的值 
  115.          * 借用Handler來傳送UI控件的值到主線程去,在主線程更新UI控件的值 
  116.          */  
  117.         private final class DownloadTask implements Runnable {  
  118.             /** 下載路徑 */  
  119.             private String path;  
  120.             /** 保存路徑 */  
  121.             private File saveDir;  
  122.             /** 文件下載器 */  
  123.             private FileDownloader loader;  
  124.   
  125.             /** 
  126.              * DownloadTask的構造函數 
  127.              *  
  128.              * @param path 
  129.              *            下載路徑 
  130.              * @param saveDir 
  131.              *            保存路徑 
  132.              */  
  133.             public DownloadTask(String path, File saveDir) {  
  134.                 this.path = path;  
  135.                 this.saveDir = saveDir;  
  136.             }  
  137.   
  138.             /** 
  139.              * 線程主方法 
  140.              */  
  141.             public void run() {  
  142.                 try {  
  143.                     /** 
  144.                      * 構建文件下載器 將下載路徑,文件保存目錄,下載線程數指定好 
  145.                      */  
  146.                     loader = new FileDownloader(getApplicationContext(), path,  
  147.                             saveDir, 5);  
  148.                     progressBar.setMax(loader.getFileSize());// 設置進度條的最大刻度(即文件的總長度)  
  149.                     /** 
  150.                      * DownloadProgressListener是一個接口,onDownloadSize()爲未實現的方法。 
  151.                      * onDownloadSize()方法會在download方法內部被動態賦值 
  152.                      * 監聽下載數量的變化,若是不須要了解實時下載的數量,能夠設置爲null 
  153.                      */  
  154.                     loader.download(new DownloadProgressListener() {  
  155.                         public void onDownloadSize(int size) {  
  156.                             // 借用Handler來傳送UI控件的值到主線程去,在主線程更新UI控件的值  
  157.                             Message msg = new Message();  
  158.                             msg.what = 1// 對應UIHander 得到的msg.what  
  159.                             msg.getData().putInt("size", size); // 將獲取的值發送給handler,用於動態更新進度  
  160.                             handler.sendMessage(msg);  
  161.                         }  
  162.                     });  
  163.                 } catch (Exception e) {  
  164.                     e.printStackTrace();  
  165.                     // 對應UIHander 得到的msg.what  
  166.                     handler.sendMessage(handler.obtainMessage(-1));   
  167.                 }  
  168.             }  
  169.   
  170.             /** 
  171.              * 退出下載 
  172.              */  
  173.             public void exit() {  
  174.                 if (loader != null)  
  175.                     loader.exit();  
  176.             }  
  177.         }  
  178.   
  179.         /** end of DownloadTask */  
  180.   
  181.         /** 
  182.          * 因爲用戶的輸入事件(點擊button, 觸摸屏幕....)是由主線程負責處理的,若是主線程處於工做狀態, 
  183.          * 此時用戶產生的輸入事件若是沒能在5秒內獲得處理,系統就會報「應用無響應」錯誤。 
  184.          * 因此在主線程裏不能執行一件比較耗時的工做,不然會因主線程阻塞而沒法處理用戶的輸入事件, 
  185.          * 致使「應用無響應」錯誤的出現。耗時的工做應該在子線程裏執行。 
  186.          */  
  187.         private DownloadTask task;  
  188.   
  189.         /** 
  190.          * 退出下載 
  191.          */  
  192.         public void exit() {  
  193.             if (task != null)  
  194.                 task.exit();  
  195.         }  
  196.   
  197.         /** 
  198.          * 下載方法,運行在主線程,負責開闢子線程完成下載操做,這操做耗時不超過1秒 
  199.          *  
  200.          * @param path 
  201.          *            下載路徑 
  202.          * @param saveDir 
  203.          *            保存路徑 
  204.          */  
  205.         private void download(String path, File saveDir) {  
  206.             task = new DownloadTask(path, saveDir);  
  207.             new Thread(task).start();// 開闢子線程完成下載操做  
  208.         }  
  209.     }  
  210.     /** end of ButtonClickListener **/  
  211. }  
  212. </span>  


文件下載器cn.oyp.download.downloader.FileDownloader.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.downloader;  
  2.   
  3. import java.io.File;  
  4. import java.io.RandomAccessFile;  
  5. import java.net.HttpURLConnection;  
  6. import java.net.URL;  
  7. import java.util.LinkedHashMap;  
  8. import java.util.Map;  
  9. import java.util.UUID;  
  10. import java.util.concurrent.ConcurrentHashMap;  
  11. import java.util.regex.Matcher;  
  12. import java.util.regex.Pattern;  
  13.   
  14. import cn.oyp.download.service.FileService;  
  15.   
  16. import android.content.Context;  
  17. import android.util.Log;  
  18.   
  19. /** 
  20.  * 文件下載器 
  21.  */  
  22. public class FileDownloader {  
  23.     private static final String TAG = "FileDownloader";  
  24.     /** 上下文 */  
  25.     private Context context;  
  26.     /** 文件下載服務類 */  
  27.     private FileService fileService;  
  28.     /** 是否中止下載 */  
  29.     private boolean exit;  
  30.     /** 已下載文件長度 */  
  31.     private int downloadSize = 0;  
  32.     /** 原始文件長度 */  
  33.     private int fileSize = 0;  
  34.     /** 用於下載的線程數組 */  
  35.     private DownloadThread[] threads;  
  36.     /** 本地保存文件 */  
  37.     private File saveFile;  
  38.     /** 緩存各線程下載的長度 */  
  39.     private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();  
  40.     /** 每條線程下載的長度 */  
  41.     private int block;  
  42.     /** 下載路徑 */  
  43.     private String downloadUrl;  
  44.   
  45.     /** 
  46.      * 獲取線程數 
  47.      */  
  48.     public int getThreadSize() {  
  49.         return threads.length;  
  50.     }  
  51.   
  52.     /** 
  53.      * 退出下載 
  54.      */  
  55.     public void exit() {  
  56.         this.exit = true;  
  57.     }  
  58.   
  59.     /** 
  60.      * 是否退出下載 
  61.      */  
  62.     public boolean getExit() {  
  63.         return this.exit;  
  64.     }  
  65.   
  66.     /** 
  67.      * 獲取文件大小 
  68.      */  
  69.     public int getFileSize() {  
  70.         return fileSize;  
  71.     }  
  72.   
  73.     /** 
  74.      * 累計已下載大小 
  75.      * 該方法在具體某個線程下載的時候會被調用 
  76.      */  
  77.     protected synchronized void append(int size) {  
  78.         downloadSize += size;  
  79.     }  
  80.   
  81.     /** 
  82.      * 更新指定線程最後下載的位置 
  83.      * 該方法在具體某個線程下載的時候會被調用 
  84.      * @param threadId 
  85.      *            線程id 
  86.      * @param pos 
  87.      *            最後下載的位置 
  88.      */  
  89.     protected synchronized void update(int threadId, int pos) {  
  90.         // 緩存各線程下載的長度  
  91.         this.data.put(threadId, pos);  
  92.         // 更新數據庫中的各線程下載的長度  
  93.         this.fileService.update(this.downloadUrl, threadId, pos);  
  94.     }  
  95.   
  96.     /** 
  97.      * 構建文件下載器 
  98.      *  
  99.      * @param downloadUrl 
  100.      *            下載路徑 
  101.      * @param fileSaveDir 
  102.      *            文件保存目錄 
  103.      * @param threadNum 
  104.      *            下載線程數 
  105.      */  
  106.     public FileDownloader(Context context, String downloadUrl,  
  107.             File fileSaveDir, int threadNum) {  
  108.         try {  
  109.             this.context = context;  
  110.             this.downloadUrl = downloadUrl;  
  111.             fileService = new FileService(this.context);  
  112.             // 根據指定的下載路徑,生成URL  
  113.             URL url = new URL(this.downloadUrl);  
  114.             if (!fileSaveDir.exists())  
  115.                 fileSaveDir.mkdirs();// 若是保存路徑不存在,則新建一個目錄  
  116.             // 根據指定的線程數來新建線程數組  
  117.             this.threads = new DownloadThread[threadNum];  
  118.             // 打開HttpURLConnection  
  119.             HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  120.             // 設置 HttpURLConnection的斷開時間  
  121.             conn.setConnectTimeout(5 * 1000);  
  122.             // 設置 HttpURLConnection的請求方式  
  123.             conn.setRequestMethod("GET");  
  124.             // 設置 HttpURLConnection的接收的文件類型  
  125.             conn.setRequestProperty(  
  126.                     "Accept",  
  127.                     "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "  
  128.                             + "application/x-shockwave-flash, application/xaml+xml, "  
  129.                             + "application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, "  
  130.                             + "application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");  
  131.             // 設置 HttpURLConnection的接收語音  
  132.             conn.setRequestProperty("Accept-Language""zh-CN");  
  133.             // 指定請求uri的源資源地址  
  134.             conn.setRequestProperty("Referer", downloadUrl);  
  135.             // 設置 HttpURLConnection的字符編碼  
  136.             conn.setRequestProperty("Charset""UTF-8");  
  137.             // 檢查瀏覽頁面的訪問者在用什麼操做系統(包括版本號)瀏覽器(包括版本號)和用戶我的偏好  
  138.             conn.setRequestProperty(  
  139.                     "User-Agent",  
  140.                     "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2;"  
  141.                             + " Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; "  
  142.                             + ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152;"  
  143.                             + " .NET CLR 3.5.30729)");  
  144.             conn.setRequestProperty("Connection""Keep-Alive");  
  145.             conn.connect();  
  146.             // 打印Http協議頭  
  147.             printResponseHeader(conn);  
  148.             // 若是返回的狀態碼爲200表示正常  
  149.             if (conn.getResponseCode() == 200) {  
  150.                 this.fileSize = conn.getContentLength();// 根據響應獲取文件大小  
  151.                 if (this.fileSize <= 0)  
  152.                     throw new RuntimeException("Unkown file size ");  
  153.   
  154.                 String filename = getFileName(conn);// 獲取文件名稱  
  155.                 this.saveFile = new File(fileSaveDir, filename);// 構建保存文件  
  156.                 Map<Integer, Integer> logdata = fileService  
  157.                         .getData(downloadUrl);// 獲取下載記錄  
  158.                 if (logdata.size() > 0) {// 若是存在下載記錄  
  159.                     for (Map.Entry<Integer, Integer> entry : logdata.entrySet())  
  160.                         data.put(entry.getKey(), entry.getValue());// 把各條線程已經下載的數據長度放入data中  
  161.                 }  
  162.                 if (this.data.size() == this.threads.length) {// 下面計算全部線程已經下載的數據總長度  
  163.                     for (int i = 0; i < this.threads.length; i++) {  
  164.                         this.downloadSize += this.data.get(i + 1);  
  165.                     }  
  166.                     print("已經下載的長度" + this.downloadSize);  
  167.                 }  
  168.                 // 計算每條線程下載的數據長度  
  169.                 this.block = (this.fileSize % this.threads.length) == 0 ? this.fileSize  
  170.                         / this.threads.length  
  171.                         : this.fileSize / this.threads.length + 1;  
  172.             } else {  
  173.                 throw new RuntimeException("server no response ");  
  174.             }  
  175.         } catch (Exception e) {  
  176.             print(e.toString());  
  177.             throw new RuntimeException("don't connection this url");  
  178.         }  
  179.     }  
  180.   
  181.     /**  
  182.      * 獲取文件名  
  183.      *   
  184.      * @param conn  
  185.      *            Http鏈接  
  186.      */  
  187.     private String getFileName(HttpURLConnection conn) {  
  188.         String filename = this.downloadUrl.substring(this.downloadUrl  
  189.                 .lastIndexOf('/') + 1);// 截取下載路徑中的文件名  
  190.         // 若是獲取不到文件名稱  
  191.         if (filename == null || "".equals(filename.trim())) {  
  192.             // 經過截取Http協議頭分析下載的文件名  
  193.             for (int i = 0;; i++) {  
  194.                 String mine = conn.getHeaderField(i);  
  195.                 if (mine == null)  
  196.                     break;  
  197.                 /** 
  198.                  * Content-disposition 是 MIME 協議的擴展,MIME 協議指示 MIME 
  199.                  * 用戶代理如何顯示附加的文件。 
  200.                  * Content-Disposition就是當用戶想把請求所得的內容存爲一個文件的時候提供一個默認的文件名 
  201.                  * 協議頭中的Content-Disposition格式以下: 
  202.                  * Content-Disposition","attachment;filename=FileName.txt"); 
  203.                  */  
  204.                 if ("content-disposition".equals(conn.getHeaderFieldKey(i)  
  205.                         .toLowerCase())) {  
  206.                     // 經過正則表達式匹配出文件名  
  207.                     Matcher m = Pattern.compile(".*filename=(.*)").matcher(  
  208.                             mine.toLowerCase());  
  209.                     // 若是匹配到了文件名  
  210.                     if (m.find())  
  211.                         return m.group(1);// 返回匹配到的文件名  
  212.                 }  
  213.             }  
  214.             // 若是仍是匹配不到文件名,則默認取一個隨機數文件名  
  215.             filename = UUID.randomUUID() + ".tmp";  
  216.         }  
  217.         return filename;  
  218.     }  
  219.   
  220.     /** 
  221.      * 開始下載文件 
  222.      *  
  223.      * @param listener 
  224.      *            監聽下載數量的變化,若是不須要了解實時下載的數量,能夠設置爲null 
  225.      * @return 已下載文件大小 
  226.      * @throws Exception 
  227.      */  
  228.     public int download(DownloadProgressListener listener) throws Exception {  
  229.         try {  
  230.             RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");  
  231.             if (this.fileSize > 0)  
  232.                 randOut.setLength(this.fileSize);  
  233.             randOut.close();  
  234.             URL url = new URL(this.downloadUrl);  
  235.             // 若是原先不曾下載或者原先的下載線程數與如今的線程數不一致  
  236.             if (this.data.size() != this.threads.length) {  
  237.                 this.data.clear();// 清除原來的線程數組  
  238.                 for (int i = 0; i < this.threads.length; i++) {  
  239.                     this.data.put(i + 10);// 初始化每條線程已經下載的數據長度爲0  
  240.                 }  
  241.                 this.downloadSize = 0;  
  242.             }  
  243.             //循環遍歷線程數組  
  244.             for (int i = 0; i < this.threads.length; i++) {  
  245.                 int downLength = this.data.get(i + 1); // 獲取當前線程下載的文件長度  
  246.                 // 判斷線程是否已經完成下載,不然繼續下載  
  247.                 if (downLength < this.block  
  248.                         && this.downloadSize < this.fileSize) {  
  249.                     //啓動線程開始下載  
  250.                     this.threads[i] = new DownloadThread(this, url,  
  251.                             this.saveFile, this.block, this.data.get(i + 1),  
  252.                             i + 1);  
  253.                     this.threads[i].setPriority(7);  
  254.                     this.threads[i].start();  
  255.                 } else {  
  256.                     this.threads[i] = null;  
  257.                 }  
  258.             }  
  259.             //若是存在下載記錄,從數據庫中刪除它們  
  260.             fileService.delete(this.downloadUrl);  
  261.             //從新保存下載的進度到數據庫  
  262.             fileService.save(this.downloadUrl, this.data);  
  263.             boolean notFinish = true;// 下載未完成  
  264.             while (notFinish) {// 循環判斷全部線程是否完成下載  
  265.                 Thread.sleep(900);  
  266.                 notFinish = false;// 假定所有線程下載完成  
  267.                 for (int i = 0; i < this.threads.length; i++) {  
  268.                     if (this.threads[i] != null && !this.threads[i].isFinish()) {// 若是發現線程未完成下載  
  269.                         notFinish = true;// 設置標誌爲下載沒有完成  
  270.                         // 若是下載失敗,再從新下載  
  271.                         if (this.threads[i].getDownLength() == -1) {  
  272.                             this.threads[i] = new DownloadThread(this, url,  
  273.                                     this.saveFile, this.block,  
  274.                                     this.data.get(i + 1), i + 1);  
  275.                             this.threads[i].setPriority(7);  
  276.                             this.threads[i].start();  
  277.                         }  
  278.                     }  
  279.                 }  
  280.                 if (listener != null)  
  281.                     listener.onDownloadSize(this.downloadSize);// 通知目前已經下載完成的數據長度  
  282.             }  
  283.             // 若是下載完成  
  284.             if (downloadSize == this.fileSize)  
  285.                 fileService.delete(this.downloadUrl);// 下載完成刪除記錄  
  286.         } catch (Exception e) {  
  287.             print(e.toString());  
  288.             throw new Exception("file download error");  
  289.         }  
  290.         return this.downloadSize;  
  291.     }  
  292.   
  293.     /** 
  294.      * 獲取Http響應頭字段 
  295.      * @param http 
  296.      * @return 
  297.      */  
  298.     public static Map<String, String> getHttpResponseHeader(  
  299.             HttpURLConnection http) {  
  300.         Map<String, String> header = new LinkedHashMap<String, String>();  
  301.         for (int i = 0;; i++) {  
  302.             String mine = http.getHeaderField(i);  
  303.             if (mine == null)  
  304.                 break;  
  305.             header.put(http.getHeaderFieldKey(i), mine);  
  306.         }  
  307.         return header;  
  308.     }  
  309.   
  310.     /** 
  311.      * 打印Http頭字段 
  312.      *  
  313.      * @param http 
  314.      */  
  315.     public static void printResponseHeader(HttpURLConnection http) {  
  316.         Map<String, String> header = getHttpResponseHeader(http);  
  317.         for (Map.Entry<String, String> entry : header.entrySet()) {  
  318.             String key = entry.getKey() != null ? entry.getKey() + ":" : "";  
  319.             print(key + entry.getValue());  
  320.         }  
  321.     }  
  322.     /** 
  323.      * 打印信息 
  324.      * @param msg  信息 
  325.      */  
  326.     private static void print(String msg) {  
  327.         Log.i(TAG, msg);  
  328.     }  
  329. }  
  330. </span>  

文件下載線程 cn.oyp.download.downloader.DownloadThread.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.downloader;  
  2.   
  3.   
  4. import java.io.File;  
  5. import java.io.InputStream;  
  6. import java.io.RandomAccessFile;  
  7. import java.net.HttpURLConnection;  
  8. import java.net.URL;  
  9.   
  10. import android.util.Log;  
  11.   
  12. public class DownloadThread extends Thread {  
  13.     private static final String TAG = "DownloadThread";  
  14.     /** 本地保存文件 */  
  15.     private File saveFile;  
  16.     /** 下載路徑 */  
  17.     private URL downUrl;  
  18.     /** 該線程要下載的長度 */  
  19.     private int block;  
  20.     /** 線程ID */  
  21.     private int threadId = -1;  
  22.     /** 該線程已經下載的長度 */  
  23.     private int downLength;  
  24.     /** 是否下載完成*/  
  25.     private boolean finish = false;  
  26.     /** 文件下載器 */  
  27.     private FileDownloader downloader;  
  28.     /*** 
  29.      *  構造方法 
  30.      */  
  31.     public DownloadThread(FileDownloader downloader, URL downUrl,  
  32.             File saveFile, int block, int downLength, int threadId) {  
  33.         this.downUrl = downUrl;  
  34.         this.saveFile = saveFile;  
  35.         this.block = block;  
  36.         this.downloader = downloader;  
  37.         this.threadId = threadId;  
  38.         this.downLength = downLength;  
  39.     }  
  40.     /** 
  41.      * 線程主方法 
  42.      */  
  43.     @Override  
  44.     public void run() {  
  45.         if (downLength < block) {// 未下載完成  
  46.             try {  
  47.                 HttpURLConnection http = (HttpURLConnection) downUrl  
  48.                         .openConnection();  
  49.                 http.setConnectTimeout(5 * 1000);  
  50.                 http.setRequestMethod("GET");  
  51.                 http.setRequestProperty(  
  52.                         "Accept",  
  53.                         "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash,"  
  54.                                 + " application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, "  
  55.                                 + "application/x-ms-application, application/vnd.ms-excel,"  
  56.                                 + " application/vnd.ms-powerpoint, application/msword, */*");  
  57.                 http.setRequestProperty("Accept-Language""zh-CN");  
  58.                 http.setRequestProperty("Referer", downUrl.toString());  
  59.                 http.setRequestProperty("Charset""UTF-8");  
  60.                 // 該線程開始下載位置  
  61.                 int startPos = block * (threadId - 1) + downLength;  
  62.                 // 該線程下載結束位置  
  63.                 int endPos = block * threadId - 1;  
  64.                 // 設置獲取實體數據的範圍  
  65.                 http.setRequestProperty("Range""bytes=" + startPos + "-"  
  66.                         + endPos);  
  67.                 http.setRequestProperty(  
  68.                         "User-Agent",  
  69.                         "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0;"  
  70.                                 + " .NET CLR 1.1.4322; .NET CLR 2.0.50727; "  
  71.                                 + ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");  
  72.                 http.setRequestProperty("Connection""Keep-Alive");  
  73.                 //獲取輸入流  
  74.                 InputStream inStream = http.getInputStream();  
  75.                 byte[] buffer = new byte[1024];  
  76.                 int offset = 0;  
  77.                 print("Thread " + this.threadId  
  78.                         + " start download from position " + startPos);  
  79.                 /**  
  80.                  * rwd: 打開以便讀取和寫入,對於 "rw",還要求對文件內容的每一個更新都同步寫入到基礎存儲設備。  
  81.                  * 對於Android移動設備必定要注意同步,不然當移動設備斷電的話會丟失數據  
  82.                  */  
  83.                 RandomAccessFile threadfile = new RandomAccessFile(  
  84.                         this.saveFile, "rwd");  
  85.                 //直接移動到文件開始位置下載的  
  86.                 threadfile.seek(startPos);  
  87.                 while (!downloader.getExit()  
  88.                         && (offset = inStream.read(buffer, 01024)) != -1) {  
  89.                     threadfile.write(buffer, 0, offset);//開始寫入數據到文件  
  90.                     downLength += offset;   //該線程以及下載的長度增長  
  91.                     downloader.update(this.threadId, downLength);//修改數據庫中該線程已經下載的數據長度  
  92.                     downloader.append(offset);//文件下載器已經下載的總長度增長  
  93.                 }  
  94.                 threadfile.close();  
  95.                 inStream.close();  
  96.                 print("Thread " + this.threadId + " download finish");  
  97.                 this.finish = true;  
  98.             } catch (Exception e) {  
  99.                 this.downLength = -1;  
  100.                 print("Thread " + this.threadId + ":" + e);  
  101.             }  
  102.         }  
  103.     }  
  104.   
  105.     private static void print(String msg) {  
  106.         Log.i(TAG, msg);  
  107.     }  
  108.   
  109.     /** 
  110.      * 下載是否完成 
  111.      *  
  112.      * @return 
  113.      */  
  114.     public boolean isFinish() {  
  115.         return finish;  
  116.     }  
  117.   
  118.     /** 
  119.      * 已經下載的內容大小 
  120.      *  
  121.      * @return 若是返回值爲-1,表明下載失敗 
  122.      */  
  123.     public long getDownLength() {  
  124.         return downLength;  
  125.     }  
  126. }  
  127. </span>  

下載進度監聽接口cn.oyp.download.downloader.DownloadProgressListener.java文件

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.downloader;  
  2.   
  3. /** 
  4.  * 下載進度監聽接口 
  5.  */  
  6. public interface DownloadProgressListener {  
  7.     /** 
  8.      *下載的進度  
  9.      */  
  10.     public void onDownloadSize(int size);  
  11. }  
  12. </span>  

數據庫操做類 cn.oyp.download.service.DBOpenHelper.java類

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.service;  
  2.   
  3. import android.content.Context;  
  4. import android.database.sqlite.SQLiteDatabase;  
  5. import android.database.sqlite.SQLiteOpenHelper;  
  6.   
  7. public class DBOpenHelper extends SQLiteOpenHelper {  
  8.     // 數據庫文件的文件名  
  9.     private static final String DBNAME = "download.db";  
  10.     // 數據庫的版本號  
  11.     private static final int VERSION = 1;  
  12.   
  13.     public DBOpenHelper(Context context) {  
  14.         super(context, DBNAME, null, VERSION);  
  15.     }  
  16.   
  17.     @Override  
  18.     public void onCreate(SQLiteDatabase db) {  
  19.         db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");  
  20.     }  
  21.   
  22.     @Override  
  23.     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  24.         db.execSQL("DROP TABLE IF EXISTS filedownlog");  
  25.         onCreate(db);  
  26.     }  
  27. }  
  28. </span>  

文件下載服務類cn.oyp.download.service.FileService

[java]  view plain copy
  1. <span style="font-size:18px">package cn.oyp.download.service;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5.   
  6. import android.content.Context;  
  7. import android.database.Cursor;  
  8. import android.database.sqlite.SQLiteDatabase;  
  9.   
  10. /** 
  11.  * 文件下載服務類 
  12.  */  
  13. public class FileService {  
  14.     private DBOpenHelper openHelper;  
  15.   
  16.     public FileService(Context context) {  
  17.         openHelper = new DBOpenHelper(context);  
  18.     }  
  19.   
  20.     /** 
  21.      * 獲取每條線程已經下載的文件長度 
  22.      *  
  23.      * @param path 
  24.      * @return 
  25.      */  
  26.     public Map<Integer, Integer> getData(String path) {  
  27.         SQLiteDatabase db = openHelper.getReadableDatabase();  
  28.         Cursor cursor = db  
  29.                 .rawQuery(  
  30.                         "select threadid, downlength from filedownlog where downpath=?",  
  31.                         new String[] { path });  
  32.         Map<Integer, Integer> data = new HashMap<Integer, Integer>();  
  33.         while (cursor.moveToNext()) {  
  34.             data.put(cursor.getInt(0), cursor.getInt(1));  
  35.         }  
  36.         cursor.close();  
  37.         db.close();  
  38.         return data;  
  39.     }  
  40.   
  41.     /** 
  42.      * 保存每條線程已經下載的文件長度 
  43.      *  
  44.      * @param path 
  45.      * @param map 
  46.      */  
  47.     public void save(String path, Map<Integer, Integer> map) {// int threadid,  
  48.                                                                 // int position  
  49.         SQLiteDatabase db = openHelper.getWritableDatabase();  
  50.         db.beginTransaction();  
  51.         try {  
  52.             for (Map.Entry<Integer, Integer> entry : map.entrySet()) {  
  53.                 db.execSQL(  
  54.                         "insert into filedownlog(downpath, threadid, downlength) values(?,?,?)",  
  55.                         new Object[] { path, entry.getKey(), entry.getValue() });  
  56.             }  
  57.             db.setTransactionSuccessful();  
  58.         } finally {  
  59.             db.endTransaction();  
  60.         }  
  61.         db.close();  
  62.     }  
  63.   
  64.     /** 
  65.      * 實時更新每條線程已經下載的文件長度 
  66.      *  
  67.      * @param path 
  68.      * @param map 
  69.      */  
  70.     public void update(String path, int threadId, int pos) {  
  71.         SQLiteDatabase db = openHelper.getWritableDatabase();  
  72.         db.execSQL(  
  73.                 "update filedownlog set downlength=? where downpath=? and threadid=?",  
  74.                 new Object[] { pos, path, threadId });  
  75.         db.close();  
  76.     }  
  77.   
  78.     /** 
  79.      * 當文件下載完成後,刪除對應的下載記錄 
  80.      *  
  81.      * @param path 
  82.      */  
  83.     public void delete(String path) {  
  84.         SQLiteDatabase db = openHelper.getWritableDatabase();  
  85.         db.execSQL("delete from filedownlog where downpath=?",  
  86.                 new Object[] { path });  
  87.         db.close();  
  88.     }  
  89. }  
  90. </span>  

step4:AndroidManifest.xml

[html]  view plain copy
  1. <span style="font-size:18px"><?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="cn.oyp.download"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="8"  
  9.         android:targetSdkVersion="17" />  
  10.     <!-- 訪問Internet權限 -->  
  11.     <uses-permission android:name="android.permission.INTERNET" />  
  12.     <!-- 在SDCard中建立與刪除文件權限 -->  
  13.     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />  
  14.     <!-- 往SDCard寫入數據權限 -->  
  15.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
  16.   
  17.     <application  
  18.         android:allowBackup="true"  
  19.         android:icon="@drawable/icon"  
  20.         android:label="@string/app_name"  
  21.         android:theme="@style/AppTheme" >  
  22.         <activity  
  23.             android:name="cn.oyp.download.MainActivity"  
  24.             android:label="@string/app_name" >  
  25.             <intent-filter>  
  26.                 <action android:name="android.intent.action.MAIN" />  
  27.   
  28.                 <category android:name="android.intent.category.LAUNCHER" />  
  29.             </intent-filter>  
  30.         </activity>  
  31.     </application>  
  32.   
  33. </manifest></span>  


step5:因爲便於本項目的展現,因此新建一個JSP項目,部署到Tomcat服務器上,以供下載。


step6:部署應用,觀看運行效果

一、打開應用


二、點擊「開始下載」


3.點擊「中止下載」


4.點擊「開始下載」   會繼續上一次的下載進度繼續下載


5.退出應用,再進應用


六、點擊「開始下載」,會繼續上一次退出應用的時候的下載進度繼續下載,完成斷點下載



7.當下載完成的時候



==================================================================================================

  做者:歐陽鵬  歡迎轉載,與人分享是進步的源泉!

  轉載請保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================




讀者下載源碼後,會發現下載速度特別慢,有如下兩種緣由:

一、因爲自己的網絡速度的緣由,不會特別快。

二、因爲使用RandomAccessFile的緣由,對IO操做太過於頻繁。所以,我修改了DownloadThread類,修改代碼以下,修改以後對速度有了點提高。在此特別感謝 pobi 讀者的意見

[java]  view plain copy
  1. package cn.oyp.download.downloader;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.IOException;  
  7. import java.io.InputStream;  
  8. import java.io.RandomAccessFile;  
  9. import java.net.HttpURLConnection;  
  10. import java.net.URL;  
  11. import java.nio.ByteBuffer;  
  12. import java.nio.channels.FileChannel;  
  13.   
  14. import android.util.Log;  
  15.   
  16. public class DownloadThread extends Thread {  
  17.     private static final String TAG = "DownloadThread";  
  18.     /** 本地保存文件 */  
  19.     private File saveFile;  
  20.     /** 下載路徑 */  
  21.     private URL downUrl;  
  22.     /** 該線程要下載的長度 */  
  23.     private int block;  
  24.     /** 線程ID */  
  25.     private int threadId = -1;  
  26.     /** 該線程已經下載的長度 */  
  27.     private int downLength;  
  28.     /** 是否下載完成 */  
  29.     private boolean finish = false;  
  30.     /** 文件下載器 */  
  31.     private FileDownloader downloader;  
  32.   
  33.     /*** 
  34.      * 構造方法 
  35.      */  
  36.     public DownloadThread(FileDownloader downloader, URL downUrl,  
  37.             File saveFile, int block, int downLength, int threadId) {  
  38.         this.downUrl = downUrl;  
  39.         this.saveFile = saveFile;  
  40.         this.block = block;  
  41.         this.downloader = downloader;  
  42.         this.threadId = threadId;  
  43.         this.downLength = downLength;  
  44.     }  
  45.   
  46.     /** 
  47.      * 線程主方法 
  48.      */  
  49.     @Override  
  50.     public void run() {  
  51.         if (downLength < block) {// 未下載完成  
  52.             try {  
  53.                 HttpURLConnection http = (HttpURLConnection) downUrl  
  54.                         .openConnection();  
  55.                 http.setConnectTimeout(5 * 1000);  
  56.                 http.setRequestMethod("GET");  
  57.                 http.setRequestProperty(  
  58.                         "Accept",  
  59.                         "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash,"  
  60.                                 + " application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, "  
  61.                                 + "application/x-ms-application, application/vnd.ms-excel,"  
  62.                                 + " application/vnd.ms-powerpoint, application/msword, */*");  
  63.                 http.setRequestProperty("Accept-Language""zh-CN");  
  64.                 http.setRequestProperty("Referer", downUrl.toString());  
  65.                 http.setRequestProperty("Charset""UTF-8");  
  66.                 // 該線程開始下載位置  
  67.                 int startPos = block * (threadId - 1) + downLength;  
  68.                 // 該線程下載結束位置  
  69.                 int endPos = block * threadId - 1;  
  70.                 // 設置獲取實體數據的範圍  
  71.                 http.setRequestProperty("Range""bytes=" + startPos + "-"  
  72.                         + endPos);  
  73.                 http.setRequestProperty(  
  74.                         "User-Agent",  
  75.                         "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0;"  
  76.                                 + " .NET CLR 1.1.4322; .NET CLR 2.0.50727; "  
  77.                                 + ".NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");  
  78.                 http.setRequestProperty("Connection""Keep-Alive");  
  79.                 /****/  
  80.                 System.out.println("DownloadThread http.getResponseCode():"  
  81.                         + http.getResponseCode());  
  82.                 if (http.getResponseCode() == 206) {  
  83.                     /*** 
  84.                      * //獲取輸入流 InputStream inStream = http.getInputStream(); 
  85.                      * byte[] buffer = new byte[1024]; int offset = 0; 
  86.                      * print("Thread " + this.threadId + 
  87.                      * " start download from position " + startPos); 
  88.                      *  
  89.                      * // rwd: 打開以便讀取和寫入,對於 "rw",還要求對文件內容的每一個更新都同步寫入到基礎存儲設備。 
  90.                      * //對於Android移動設備必定要注意同步,不然當移動設備斷電的話會丟失數據 RandomAccessFile 
  91.                      * threadfile = new RandomAccessFile( this.saveFile, "rwd"); 
  92.                      * //直接移動到文件開始位置下載的 threadfile.seek(startPos); while 
  93.                      * (!downloader.getExit() && (offset = inStream.read(buffer, 
  94.                      * 0, 1024)) != -1) { threadfile.write(buffer, 0, 
  95.                      * offset);//開始寫入數據到文件 downLength += offset; //該線程以及下載的長度增長 
  96.                      * downloader.update(this.threadId, 
  97.                      * downLength);//修改數據庫中該線程已經下載的數據長度 
  98.                      * downloader.append(offset);//文件下載器已經下載的總長度增長 } 
  99.                      * threadfile.close(); 
  100.                      *  
  101.                      * print("Thread " + this.threadId + " download finish"); 
  102.                      * this.finish = true; 
  103.                      **/  
  104.                     // 獲取輸入流  
  105.                     InputStream inStream = http.getInputStream();  
  106.                     BufferedInputStream bis = new BufferedInputStream(inStream);  
  107.                     byte[] buffer = new byte[1024 * 4];  
  108.                     int offset = 0;  
  109.                     RandomAccessFile threadfile = new RandomAccessFile(  
  110.                             this.saveFile, "rwd");  
  111.                     // 獲取RandomAccessFile的FileChannel  
  112.                     FileChannel outFileChannel = threadfile.getChannel();  
  113.                     // 直接移動到文件開始位置下載的  
  114.                     outFileChannel.position(startPos);  
  115.                     // 分配緩衝區的大小  
  116.                     while (!downloader.getExit()  
  117.                             && (offset = bis.read(buffer)) != -1) {  
  118.                         outFileChannel  
  119.                                 .write(ByteBuffer.wrap(buffer, 0, offset));// 開始寫入數據到文件  
  120.                         downLength += offset; // 該線程以及下載的長度增長  
  121.                         downloader.update(this.threadId, downLength);// 修改數據庫中該線程已經下載的數據長度  
  122.                         downloader.append(offset);// 文件下載器已經下載的總長度增長  
  123.                     }  
  124.                     outFileChannel.close();  
  125.                     threadfile.close();  
  126.                     inStream.close();  
  127.                     print("Thread " + this.threadId + " download finish");  
  128.                     this.finish = true;  
  129.                 }  
  130.             } catch (Exception e) {  
  131.                 this.downLength = -1;  
  132.                 print("Thread " + this.threadId + ":" + e);  
  133.             }  
  134.         }  
  135.     }  
  136.   
  137.     private static void print(String msg) {  
  138.         Log.i(TAG, msg);  
  139.     }  
  140.   
  141.     /** 
  142.      * 下載是否完成 
  143.      *  
  144.      * @return 
  145.      */  
  146.     public boolean isFinish() {  
  147.         return finish;  
  148.     }  
  149.   
  150.     /** 
  151.      * 已經下載的內容大小 
  152.      *  
  153.      * @return 若是返回值爲-1,表明下載失敗 
  154.      */  
  155.     public long getDownLength() {  
  156.         return downLength;  
  157.     }  
  158. }  

   ==================================下面看一個gif動畫===========================================

    


能夠查看log日誌,查看多線程下載的狀況

==================================================================================================

  做者:歐陽鵬  歡迎轉載,與人分享是進步的源泉!

  轉載請保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================


    博客寫完後,又有讀者提出了修改意見,在這兒特別感謝熱心的讀者給的建議,下面是讀者JavaLover00000 給的建議:

修改了部分代碼後,具體的優化效果以下所示,修改後下載速度確實變快了不少。

修改前的效果:

修改後的效果

具體修改的代碼能夠到如下地址進行下載:Android基於HTTP協議的多線程斷點下載器的實現源碼_第二次優化以後

主要是將一、buffer改成8k  
二、由於發現花費在更新數據庫的時間比 read和write加起來的時間都要多一點,因此將更新數據庫進度改成下載線程出現異常的時候更新單個線程進度和FileDownloader中的exit()中更新全部線程進度


代碼修改的地方具體能夠查看源代碼中的FileDownloader.java和DownloadThread.java



3.HttpURLConnection實現斷點續傳



HttpURLConnection繼承了URLConnection,所以也可用於向指定網站發送GET請求、POST請求,並且它在URLConnection基礎上提供了以下便捷方法:

實現多線程下載的步驟:


下邊的總結對我幫助蠻大的~不只用法瞭解,整個鏈接流程也要明白!

原文連接地址: 
http://www.blogjava.net/supercrsky/articles/247449.html 

針對JDK中的URLConnection鏈接Servlet的問題,網上有雖然有所涉及,可是隻是說明了某一個或幾個問題,是以FAQ的方式來解決的,並且比較零散,如今對這個類的使用就本人在項目中的使用經驗作以下總結: 
1:> URL請求的類別: 
分爲二類,GET與POST請求。兩者的區別在於: 
     a:) get請求能夠獲取靜態頁面,也能夠把參數放在URL字串後面,傳遞給servlet, 
     b:) post與get的不一樣之處在於post的參數不是放在URL字串裏面,而是放在http請求的正文內。 


2:> URLConnection的對象問題: 
URLConnection的對象,以下代碼示例: 

// 下面的index.jsp由<servlet-mapping>映射到 
// 一個Servlet(com.quantanetwork.getClientDataServlet) 
// 該Servlet的注意點下邊會提到 

複製代碼
  
  
  
  
1 URL url = new URL( " http://localhost:8080/TestHttpURLConnectionPro/index.jsp " );
2
3 URLConnection rulConnection = url.openConnection();
4 // 此處的urlConnection對象其實是根據URL的
5 // 請求協議(此處是http)生成的URLConnection類
6 // 的子類HttpURLConnection,故此處最好將其轉化
7 // 爲HttpURLConnection類型的對象,以便用到
8 // HttpURLConnection更多的API.以下:
9  
10 HttpURLConnection httpUrlConnection = (HttpURLConnection) rulConnection;
複製代碼

3:> HttpURLConnection對象參數問題 

複製代碼
  
  
  
  
1 // 設置是否向httpUrlConnection輸出,由於這個是post請求,參數要放在
2   // http正文內,所以須要設爲true, 默認狀況下是false;
3   httpUrlConnection.setDoOutput( true );
4
5   // 設置是否從httpUrlConnection讀入,默認狀況下是true;
6   httpUrlConnection.setDoInput( true );
7
8   // Post 請求不能使用緩存
9   httpUrlConnection.setUseCaches( false );
10
11   // 設定傳送的內容類型是可序列化的java對象
12   // (若是不設此項,在傳送序列化對象時,當WEB服務默認的不是這種類型時可能拋java.io.EOFException)
13   httpUrlConnection.setRequestProperty( " Content-type " , " application/x-java-serialized-object " );
14
15   // 設定請求的方法爲"POST",默認是GET
16   httpUrlConnection.setRequestMethod( " POST " );
17
18   // 鏈接,從上述第2條中url.openConnection()至此的配置必需要在connect以前完成,
19   httpUrlConnection.connect();
複製代碼

4:>  HttpURLConnection鏈接問題: 

  
  
  
  
1 // 此處getOutputStream會隱含的進行connect(即:如同調用上面的connect()方法,
2   // 因此在開發中不調用上述的connect()也能夠)。
3   OutputStream outStrm = httpUrlConnection.getOutputStream();

5:> HttpURLConnection寫數據與發送數據問題: 

複製代碼
  
  
  
  
1 // 如今經過輸出流對象構建對象輸出流對象,以實現輸出可序列化的對象。
2   ObjectOutputStream objOutputStrm = new ObjectOutputStream(outStrm);
3
4 // 向對象輸出流寫出數據,這些數據將存到內存緩衝區中
5   objOutputStrm.writeObject( new String( " 我是測試數據 " ));
6
7 // 刷新對象輸出流,將任何字節都寫入潛在的流中(些處爲ObjectOutputStream)
8   objOutputStm.flush();
9
10   // 關閉流對象。此時,不能再向對象輸出流寫入任何數據,先前寫入的數據存在於內存緩衝區中,
11   // 在調用下邊的getInputStream()函數時才把準備好的http請求正式發送到服務器
12   objOutputStm.close();
13  
14   // 調用HttpURLConnection鏈接對象的getInputStream()函數,
15   // 將內存緩衝區中封裝好的完整的HTTP請求電文發送到服務端。
16   InputStream inStrm = httpConn.getInputStream(); // <===注意,實際發送請求的代碼段就在這裏
17  
18 // 上邊的httpConn.getInputStream()方法已調用,本次HTTP請求已結束,下邊向對象輸出流的輸出已無心義,
19 // 既使對象輸出流沒有調用close()方法,下邊的操做也不會向對象輸出流寫入任何數據.
20 // 所以,要從新發送數據時須要從新建立鏈接、從新設參數、從新建立流對象、從新寫數據、
21 // 從新發送數據(至因而否不用從新這些操做須要再研究)
22 objOutputStm.writeObject( new String( "" ));
23 httpConn.getInputStream()
複製代碼

總結:a:) HttpURLConnection的connect()函數,實際上只是創建了一個與服務器的tcp鏈接,並無實際發送http請求。 

    不管是post仍是get,http請求實際上直到HttpURLConnection的getInputStream()這個函數裏面才正式發送出去。 
       b:) 在用POST方式發送URL請求時,URL請求參數的設定順序是重中之重, 
    對connection對象的一切配置(那一堆set函數) 
    都必需要在connect()函數執行以前完成。而對outputStream的寫操做,又必需要在inputStream的讀操做以前。 
    這些順序其實是由http請求的格式決定的。 
    若是inputStream讀操做在outputStream的寫操做以前,會拋出例外: 
    java.net.ProtocolException: Cannot write output after reading input....... 
       
       c:) http請求實際上由兩部分組成, 
    一個是http頭,全部關於這次http請求的配置都在http頭裏面定義, 
           一個是正文content。 
    connect()函數會根據HttpURLConnection對象的配置值生成http頭部信息,所以在調用connect函數以前, 
    就必須把全部的配置準備好。 
       d:) 在http頭後面緊跟着的是http請求的正文,正文的內容是經過outputStream流寫入的, 
    實際上outputStream不是一個網絡流,充其量是個字符串流,往裏面寫入的東西不會當即發送到網絡, 
    而是存在於內存緩衝區中,待outputStream流關閉時,根據輸入的內容生成http正文。 
    至此,http請求的東西已經所有準備就緒。在getInputStream()函數調用的時候,就會把準備好的http請求 
    正式發送到服務器了,而後返回一個輸入流,用於讀取服務器對於這次http請求的返回信息。因爲http 
    請求在getInputStream的時候已經發送出去了(包括http頭和正文),所以在getInputStream()函數 
    以後對connection對象進行設置(對http頭的信息進行修改)或者寫入outputStream(對正文進行修改) 
    都是沒有意義的了,執行這些操做會致使異常的發生。 

6:> Servlet端的開發注意點: 
a:) 對於客戶端發送的POST類型的HTTP請求,Servlet必須實現doPost方法,而不能用doGet方法。 
b:) 用HttpServletRequest的getInputStream()方法取得InputStream的對象,好比: 
     InputStream inStream = httpRequest.getInputStream(); 
     如今調用inStream.available()(該方法用於「返回此輸入流下一個方法調用能夠不受阻塞地 
     今後輸入流讀取(或跳過)的估計字節數」)時,永遠都反回0。試圖使用此方法的返回值分配緩衝區, 
     以保存此流全部數據的作法是不正確的。那麼,如今的解決辦法是 
     Servlet這一端用以下實現: 
     InputStream inStream = httpRequest.getInputStream(); 
     ObjectInputStream objInStream = new ObjectInputStream(inStream); 
     Object obj = objInStream.readObject(); 
     // 作後續的處理 
     // 。。。。。。 
     // 。。。 。。。 
     而客戶端,不管是否發送實際數據都要寫入一個對象(那怕這個對象不用),如: 
     ObjectOutputStream objOutputStrm = new ObjectOutputStream(outStrm); 
     objOutputStrm.writeObject(new String("")); // 這裏發送一個空數據 
     // 甚至能夠發一個null對象,服務端取到後再作判斷處理。 
     objOutputStrm.writeObject(null); 
     objOutputStrm.flush(); 
     objOutputStrm.close(); 

注意:上述在建立對象輸出流ObjectOutputStream時,若是將從HttpServletRequest取得的輸入流 
      (即:new ObjectOutputStream(outStrm)中的outStrm)包裝在BufferedOutputStream流裏面, 
      則必須有objOutputStrm.flush();這一句,以便將流信息刷入緩衝輸出流.以下: 
      ObjectOutputStream objOutputStrm = new ObjectOutputStream(new BufferedOutputStream(outStrm)); 
      objOutputStrm.writeObject(null); 
      objOutputStrm.flush(); // <======此處必需要有. 
      objOutputStrm.close(); 

HttpURLConnection是基於HTTP協議的,其底層經過socket通訊實現。若是不設置超時(timeout),在網絡異常的狀況下,可能會致使程序僵死而不繼續往下執行。能夠經過如下兩個語句來設置相應的超時: 
System.setProperty("sun.net.client.defaultConnectTimeout", 超時毫秒數字符串); 
System.setProperty("sun.net.client.defaultReadTimeout", 超時毫秒數字符串); 


其中: sun.net.client.defaultConnectTimeout:鏈接主機的超時時間(單位:毫秒) 
sun.net.client.defaultReadTimeout:從主機讀取數據的超時時間(單位:毫秒) 

例如: 
System.setProperty("sun.net.client.defaultConnectTimeout", "30000"); 
System.setProperty("sun.net.client.defaultReadTime 

Java中可使用HttpURLConnection來請求WEB資源。 
HttpURLConnection對象不能直接構造,須要經過URL.openConnection()來得到HttpURLConnection對象,示例代碼以下: 

  
  
  
  
1 String szUrl = " http://www.ee2ee.com/ " ;
2 URL url = new URL(szUrl);
3 HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();

HttpURLConnection是基於HTTP協議的,其底層經過socket通訊實現。若是不設置超時(timeout),在網絡異常的狀況下,可能會致使程序僵死而不繼續往下執行。能夠經過如下兩個語句來設置相應的超時: 
System.setProperty("sun.net.client.defaultConnectTimeout", 超時毫秒數字符串); 
System.setProperty("sun.net.client.defaultReadTimeout", 超時毫秒數字符串); 


其中: sun.net.client.defaultConnectTimeout:鏈接主機的超時時間(單位:毫秒) 
sun.net.client.defaultReadTimeout:從主機讀取數據的超時時間(單位:毫秒) 

例如: 
System.setProperty("sun.net.client.defaultConnectTimeout", "30000"); 
System.setProperty("sun.net.client.defaultReadTimeout", "30000"); 

JDK 1.5之前的版本,只能經過設置這兩個系統屬性來控制網絡超時。在1.5中,還可使用HttpURLConnection的父類URLConnection的如下兩個方法: 
setConnectTimeout:設置鏈接主機超時(單位:毫秒) 
setReadTimeout:設置從主機讀取數據超時(單位:毫秒) 

例如: 
  
  
  
  
1 HttpURLConnection urlCon = (HttpURLConnection)url.openConnection();
2 urlCon.setConnectTimeout( 30000 );
3 urlCon.setReadTimeout( 30000 );
  


須要注意的是,筆者在JDK1.4.2環境下,發如今設置了defaultReadTimeout的狀況下,若是發生網絡超時,HttpURLConnection會自動從新提交一次請求,出現一次請求調用,請求服務器兩次的問題(Trouble)。我認爲這是JDK1.4.2的一個bug。在JDK1.5.0中,此問題已獲得解決,不存在自動重發現象。out", "3000





下面用一個示例來示範使用HttpURLConnection實現多線程下載。此代碼來源瘋狂講義一書,該代碼主要思路:在Activity中點擊按鈕,調用DownUtil的download()方法,在download()中啓動四個線程去下載資源,每一個線程負責下載本身的那部分資源,代碼以下:

Activity:

[java]  view plain copy
  1. package com.home.activity;  
  2.   
  3. import java.util.Timer;  
  4. import java.util.TimerTask;  
  5.   
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.view.View;  
  11. import android.view.View.OnClickListener;  
  12. import android.widget.Button;  
  13. import android.widget.EditText;  
  14. import android.widget.ProgressBar;  
  15.   
  16. import com.home.multithreaddown.R;  
  17. import com.home.util.DownUtil;  
  18.   
  19. public class MultiThreadDownActivity extends Activity {  
  20.     private EditText urlText;  
  21.     private EditText targetText;  
  22.     private Button downBtn;  
  23.     private ProgressBar bar;  
  24.     private DownUtil downUtil;  
  25.     private int mDownStatus;  
  26.     private Handler handler;  
  27.   
  28.     @Override  
  29.     protected void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.main);  
  32.         // 獲取界面中控件  
  33.         targetText = (EditText) findViewById(R.id.main_et_name);  
  34.         urlText = (EditText) findViewById(R.id.main_et_url);  
  35.         downBtn = (Button) findViewById(R.id.main_btn_download);  
  36.         bar = (ProgressBar) findViewById(R.id.main_progressBar);  
  37.         // 建立一個Handler對象  
  38.         handler = new Handler() {  
  39.             public void handleMessage(Message msg) {  
  40.                 if (msg.what == 0x123) {  
  41.                     bar.setProgress(mDownStatus);  
  42.                 }  
  43.             }  
  44.         };  
  45.         downBtn.setOnClickListener(new OnClickListener() {  
  46.   
  47.             @Override  
  48.             public void onClick(View v) {  
  49.                 // 初始化DownUtil對象  
  50.                 downUtil = new DownUtil(urlText.getText().toString(),  
  51.                         targetText.getText().toString(), 4);  
  52.                 try {  
  53.                     // 開始下載  
  54.                     downUtil.download();  
  55.                 } catch (Exception e) {  
  56.                     e.printStackTrace();  
  57.                 }  
  58.                 // 定義每秒調度獲取一次系統的完成進度  
  59.                 final Timer timer = new Timer();  
  60.                 timer.schedule(new TimerTask() {  
  61.                     public void run() {  
  62.                         // 獲取下載任務的完成比率  
  63.                         double completeRate = downUtil.getCompleteRate();  
  64.                         mDownStatus = (int) (completeRate * 100);  
  65.                         // 發送消息通知界面更新進度條  
  66.                         handler.sendEmptyMessage(0x123);  
  67.                         // 下載完成後取消任務調度  
  68.                         if (mDownStatus >= 100) {  
  69.                             timer.cancel();  
  70.                         }  
  71.                     }  
  72.                 }, 0100);  
  73.   
  74.             }  
  75.         });  
  76.     }  
  77.   
  78. }  

下載的工具類(DownUtil):

[java]  view plain copy
  1. package com.home.util;  
  2.   
  3. import java.io.InputStream;  
  4. import java.io.RandomAccessFile;  
  5. import java.net.HttpURLConnection;  
  6. import java.net.URL;  
  7.   
  8. public class DownUtil {  
  9.     // 定義下載資源的路徑  
  10.     private String path;  
  11.     // 指定所下載的文件的保存位置  
  12.     private String targetFile;  
  13.     // 定義須要使用多少線程下載資源  
  14.     private int threadNum;  
  15.     // 定義下載的文件的總大小  
  16.     private int fileSize;  
  17.     // 定義下載的線程對象  
  18.     private DownloadThread[] threads;  
  19.   
  20.     public DownUtil(String path, String targetFile, int threadNum) {  
  21.         this.path = path;  
  22.         this.threadNum = threadNum;  
  23.         // 初始化threads數組  
  24.         threads = new DownloadThread[threadNum];  
  25.         this.targetFile = targetFile;  
  26.     }  
  27.   
  28.     public void download() throws Exception {  
  29.         URL url = new URL(path);  
  30.         HttpURLConnection conn = (HttpURLConnection) url.openConnection();  
  31.         conn.setConnectTimeout(5 * 1000);  
  32.         conn.setRequestMethod("GET");  
  33.         conn.setRequestProperty(  
  34.                 "Accept",  
  35.                 "image/gif,image/jpeg,image/pjpeg,application/x-shockwaveflash,application/x-ms-xbap,application/xaml+xml,application/vnd.ms-xpsdocument,application/x-ms-application,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/*");  
  36.         conn.setRequestProperty("Accept-Language""zh-CN");  
  37.         conn.setRequestProperty("Charset""UTF-8");  
  38.         conn.setRequestProperty(  
  39.                 "User-Agent",  
  40.                 "Mozilla/4.0(compatible;MSIE7.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)");  
  41.   
  42.         conn.setRequestProperty("Connection""Keep-Alive");  
  43.         // 獲得文件大小  
  44.         fileSize = conn.getContentLength();  
  45.         conn.disconnect();  
  46.         int currentPartSize = fileSize / threadNum + 1;  
  47.         RandomAccessFile file = new RandomAccessFile(targetFile, "rw");  
  48.         // 設置本地文件的大小  
  49.         file.setLength(fileSize);  
  50.         file.close();  
  51.         for (int i = 0; i < threadNum; i++) {  
  52.             // 計算每條線程的下載的開始位置  
  53.             int startPos = i * currentPartSize;  
  54.             // 每一個線程使用一個RandomAccessFile進行下載  
  55.             RandomAccessFile currentPart = new RandomAccessFile(targetFile,  
  56.                     "rw");  
  57.             // 定位該線程的下載位置  
  58.             currentPart.seek(startPos);  
  59.             // 建立下載線程  
  60.             threads[i] = new DownloadThread(startPos, currentPartSize,  
  61.                     currentPart);  
  62.             // 啓動下載線程  
  63.             threads[i].start();  
  64.         }  
  65.     }  
  66.   
  67.     /**  
  68.      * 獲取下載完成的百分比  
  69.      *   
  70.      * @return  
  71.      */  
  72.     public double getCompleteRate() {  
  73.         // 統計多條線程已經下載的總大小  
  74.         int sumSize = 0;  
  75.         for (int i = 0; i < threadNum; i++) {  
  76.             sumSize += threads[i].length;  
  77.         }  
  78.         // 返回已經完成的百分比  
  79.         return sumSize * 1.0 / fileSize;  
  80.     }  
  81.   
  82.     private class DownloadThread extends Thread {  
  83.         // 當前線程的下載位置  
  84.         private int startPos;  
  85.         // 定義當前線程負責下載的文件大小  
  86.         private int currentPartSize;  
  87.         // 當前線程須要下載的文件塊  
  88.         private RandomAccessFile currentPart;  
  89.         // 定義該線程已下載的字節數  
  90.         private int length = 0;  
  91.   
  92.         public DownloadThread(int startPos, int currentPartSize,  
  93.                 RandomAccessFile currentPart) {  
  94.             this.startPos = startPos;  
  95.             this.currentPartSize = currentPartSize;  
  96.             this.currentPart = currentPart;  
  97.         }  
  98.   
  99.         public void run() {  
  100.             try {  
  101.                 URL url = 
相關文章
相關標籤/搜索