使用多線程斷點續(xù)傳下載器在下載的時候多個線程并發(fā)可以占用服務器端更多資源, 從而加快下載速度,在下載過程中記錄每個線程已拷貝數(shù)據(jù)的數(shù)量,如果下載中斷,比如無信號斷線、電量不足等情況下,這就需要使用到斷點續(xù)傳功能,下次啟動 時從記錄位置繼續(xù)下載,可避免重復部分的下載。這里采用數(shù)據(jù)庫來記錄下載的進度。

斷點續(xù)傳

1.斷點續(xù)傳需要在下載過程中記錄每條線程的下載進度

2.每次下載開始之前先讀取數(shù)據(jù)庫,查詢是否有未完成的記錄,有就繼續(xù)下載,沒有則創(chuàng)建新記錄插入數(shù)據(jù)庫

3.在每次向文件中寫入數(shù)據(jù)之后,在數(shù)據(jù)庫中更新下載進度

4.下載完成之后刪除數(shù)據(jù)庫中下載記錄

Handler傳輸數(shù)據(jù)

這個主要用來記錄百分比,每下載一部分數(shù)據(jù)就通知主線程來記錄時間

1.主線程中創(chuàng)建的View只能在主線程中修改,其他線程只能通過和主線程通信,在主線程中改變View數(shù)據(jù)

2.我們使用Handler可以處理這種需求

   主線程中創(chuàng)建Handler,重寫handleMessage()方法

   新線程中使用Handler發(fā)送消息,主線程即可收到消息,并且執(zhí)行handleMessage()方法


        使用多線程的好處:使用多線程下載會提升文件下載的速度。那么多線程下載文件的過程是:

        (1)首先獲得下載文件的長度,然后設置本地文件的長度。

             HttpURLConnection.getContentLength();//獲取下載文件的長度

            RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");

              file.setLength(filesize);//設置本地文件的長度


          (2)根據(jù)文件長度和線程數(shù)計算每條線程下載的數(shù)據(jù)長度和下載位置。

             如:文件的長度為6M,線程數(shù)為3,那么,每條線程下載的數(shù)據(jù)長度為2M,每條線程開始下載的位置如下圖所示。


例如10M大小,使用3個線程來下載,

               線程下載的數(shù)據(jù)長度   (10%3 == 0 ? 10/3:10/3+1) ,第1,2個線程下載長度是4M,第三個線程下載長度為2M

                下載開始位置:線程id*每條線程下載的數(shù)據(jù)長度 = ?

               下載結束位置:(線程id+1)*每條線程下載的數(shù)據(jù)長度-1=?

(3)使用Http的Range頭字段指定每條線程從文件的什么位置開始下載,下載到什么位置為止,

                如:指定從文件的2M位置開始下載,下載到位置(4M-1byte)為止

           代碼如下:HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");


(4)保存文件,使用RandomAccessFile類指定每條線程從本地文件的什么位置開始寫入數(shù)據(jù)。

RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");

threadfile.seek(2097152);//從文件的什么位置開始寫入數(shù)據(jù)


string.xml文件中代碼:

 

 1 <?xml version="1.0" encoding="utf-8"?> 
 2 <resources>
 3     <string name="hello">Hello World, MainActivity!</string>
 4     <string name="app_name">Android網(wǎng)絡多線程斷點下載</string>
 5     <string name="path">下載路徑</string>
 6     <string name="downloadbutton">下載</string>
 7     <string name="sdcarderror">SDCard不存在或者寫保護</string>
 8     <string name="success">下載完成</string>
 9     <string name="error">下載失敗</string>
10 </resources>
main.xml文件中代碼:

 1  <?xml version="1.0" encoding="utf-8"?>    
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3 android:orientation="vertical"   
 4 android:layout_width="fill_parent"    
 5 android:layout_height="fill_parent">
 6     <!-- 下載路徑 -->
 7     <TextView
 8         android:layout_width="fill_parent"
 9         android:layout_height="wrap_content"
10         android:text="@string/path"/>
11     <EditText 
12         android:id="@+id/path" 
13         android:text="http://www.winrar.com.cn/download/wrar380sc.exe" 
14         android:layout_width="fill_parent" 
15         android:layout_height="wrap_content">
16     </EditText>
17     <!-- 下載按鈕 -->
18     <Button
19         android:layout_width="wrap_content"
20         android:layout_height="wrap_content"
21         android:text="@string/downloadbutton"
22         android:id="@+id/button"/>
23     <!-- 進度條 -->
24     <ProgressBar
25         android:layout_width="fill_parent"
26         android:layout_height="20dip"
27         style="?android:attr/progressBarStyleHorizontal"
28         android:id="@+id/downloadbar" />
29     <TextView
30         android:layout_width="fill_parent"
31         android:layout_height="wrap_content"
32         android:gravity="center"
33         android:id="@+id/resultView" />
34     </LinearLayout>
  AndroidManifest.xml文件中代碼:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"      package="com.android.downloader"      android:versionCode="1"      android:versionName="1.0">
 3     <uses-sdk android:minSdkVersion="8" />
 4 
 5     <application android:icon="@drawable/icon" android:label="@string/app_name">
 6         <activity android:name=".MainActivity"
 7                   android:label="@string/app_name">
 8             <intent-filter>
 9                 <action android:name="android.intent.action.MAIN" />
10                 <category android:name="android.intent.category.LAUNCHER" />
11             </intent-filter>
12         </activity>
13 
14     </application>
15     
16     <!-- 在SDCard中創(chuàng)建與刪除文件權限 -->
17     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
18     
19     <!-- 往SDCard寫入數(shù)據(jù)權限 -->
20     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
21     
22     <!-- 訪問internet權限 -->
23     <uses-permission android:name="android.permission.INTERNET"/>
24 </manifest> 

MainActivity中代碼:

  1 package com.android.downloader;
  2 import java.io.File;
  3 
  4 import com.android.network.DownloadProgressListener;
  5 import com.android.network.FileDownloader;
  6 
  7 import android.app.Activity;
  8 import android.os.Bundle;
  9 import android.os.Environment;
 10 import android.os.Handler;
 11 import android.os.Message;
 12 import android.view.View;
 13 import android.widget.Button;
 14 import android.widget.EditText;
 15 import android.widget.ProgressBar;
 16 import android.widget.TextView;
 17 import android.widget.Toast;
 18 
 19 public class MainActivity extends Activity {
 20     private EditText downloadpathText;
 21     private TextView resultView;
 22     private ProgressBar progressBar;
 23     
 24     /**
 25      * 當Handler被創(chuàng)建會關聯(lián)到創(chuàng)建它的當前線程的消息隊列,該類用于往消息隊列發(fā)送消息
 26      * 消息隊列中的消息由當前線程內(nèi)部進行處理
 27      */
 28     private Handler handler = new Handler(){
 29 
 30         @Override
 31         public void handleMessage(Message msg) {            
 32             switch (msg.what) {
 33             case 1:                
 34                 progressBar.setProgress(msg.getData().getInt("size"));
 35                 float num = (float)progressBar.getProgress()/(float)progressBar.getMax();
 36                 int result = (int)(num*100);
 37                 resultView.setText(result+ "%");
 38                 
 39                 if(progressBar.getProgress()==progressBar.getMax()){
 40                     Toast.makeText(MainActivity.this, R.string.success, 1).show();
 41                 }
 42                 break;
 43             case -1:
 44                 Toast.makeText(MainActivity.this, R.string.error, 1).show();
 45                 break;
 46             }
 47         }
 48     };
 49     
 50     /** Called when the activity is first created. */
 51     @Override
 52     public void onCreate(Bundle savedInstanceState) {
 53         super.onCreate(savedInstanceState);
 54         setContentView(R.layout.main);
 55         
 56         downloadpathText = (EditText) this.findViewById(R.id.path);
 57         progressBar = (ProgressBar) this.findViewById(R.id.downloadbar);
 58         resultView = (TextView) this.findViewById(R.id.resultView);
 59         Button button = (Button) this.findViewById(R.id.button);
 60         
 61         button.setOnClickListener(new View.OnClickListener() {
 62             
 63             @Override
 64             public void onClick(View v) {
 65                 // TODO Auto-generated method stub
 66                 String path = downloadpathText.getText().toString();
 67                 System.out.println(Environment.getExternalStorageState()+"------"+Environment.MEDIA_MOUNTED);
 68                 
 69                 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
 70                     download(path, Environment.getExternalStorageDirectory());
 71                 }else{
 72                     Toast.makeText(MainActivity.this, R.string.sdcarderror, 1).show();
 73                 }
 74             }
 75         });
 76     }
 77     
 78       /**
 79        * 主線程(UI線程)
 80        * 對于顯示控件的界面更新只是由UI線程負責,如果是在非UI線程更新控件的屬性值,更新后的顯示界面不會反映到屏幕上
 81        * @param path
 82        * @param savedir
 83        */
 84     private void download(final String path, final File savedir) {
 85         new Thread(new Runnable() {            
 86             @Override
 87             public void run() {
 88                 FileDownloader loader = new FileDownloader(MainActivity.this, path, savedir, 3);
 89                 progressBar.setMax(loader.getFileSize());//設置進度條的最大刻度為文件的長度
 90                 
 91                 try {
 92                     loader.download(new DownloadProgressListener() {
 93                         @Override
 94                         public void onDownloadSize(int size) {//實時獲知文件已經(jīng)下載的數(shù)據(jù)長度
 95                             Message msg = new Message();
 96                             msg.what = 1;
 97                             msg.getData().putInt("size", size);
 98                             handler.sendMessage(msg);//發(fā)送消息
 99                         }
100                     });
101                 } catch (Exception e) {
102                     handler.obtainMessage(-1).sendToTarget();
103                 }
104             }
105         }).start();
106     }
107 
DBOpenHelper中代碼:
 1 package com.android.service;
 2 import android.content.Context;
 3 import android.database.sqlite.SQLiteDatabase;
 4 import android.database.sqlite.SQLiteOpenHelper;
 5 
 6 public class DBOpenHelper extends SQLiteOpenHelper {
 7     private static final String DBNAME = "down.db";
 8     private static final int VERSION = 1;
 9     
10     public DBOpenHelper(Context context) {
11         super(context, DBNAME, null, VERSION);
12     }
13     
14     @Override
15     public void onCreate(SQLiteDatabase db) {
16         db.execSQL("CREATE TABLE IF NOT EXISTS filedownlog (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, downlength INTEGER)");
17     }
18 
19     @Override
20     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
21         db.execSQL("DROP TABLE IF EXISTS filedownlog");
22         onCreate(db);
23     }
24 }
FileService中代碼:

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

1 package com.android.network;
2 public interface DownloadProgressListener {
3     public void onDownloadSize(int size);
4 

FileDownloader中代碼:

  1 package com.android.network;
  2 import java.io.File;
  3 import java.io.RandomAccessFile;
  4 import java.net.HttpURLConnection;
  5 import java.net.URL;
  6 import java.util.LinkedHashMap;
  7 import java.util.Map;
  8 import java.util.UUID;
  9 import java.util.concurrent.ConcurrentHashMap;
 10 import java.util.regex.Matcher;
 11 import java.util.regex.Pattern;
 12 
 13 import com.android.service.FileService;
 14 
 15 import android.content.Context;
 16 import android.util.Log;
 17 
 18 public class FileDownloader {
 19     private static final String TAG = "FileDownloader";
 20     private Context context;
 21     private FileService fileService;    
 22     
 23     /* 已下載文件長度 */
 24     private int downloadSize = 0;
 25     
 26     /* 原始文件長度 */
 27     private int fileSize = 0;
 28     
 29     /* 線程數(shù) */
 30     private DownloadThread[] threads;
 31     
 32     /* 本地保存文件 */
 33     private File saveFile;
 34     
 35     /* 緩存各線程下載的長度*/
 36     private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();
 37     
 38     /* 每條線程下載的長度 */
 39     private int block;
 40     
 41     /* 下載路徑  */
 42     private String downloadUrl;
 43     
 44     /**
 45      * 獲取線程數(shù)
 46      */
 47     public int getThreadSize() {
 48         return threads.length;
 49     }
 50     
 51     /**
 52      * 獲取文件大小
 53      * @return
 54      */
 55     public int getFileSize() {
 56         return fileSize;
 57     }
 58     
 59     /**
 60      * 累計已下載大小
 61      * @param size
 62      */
 63     protected synchronized void append(int size) {
 64         downloadSize += size;
 65     }
 66     
 67     /**
 68      * 更新指定線程最后下載的位置
 69      * @param threadId 線程id
 70      * @param pos 最后下載的位置
 71      */
 72     protected synchronized void update(int threadId, int pos) {
 73         this.data.put(threadId, pos);
 74         this.fileService.update(this.downloadUrl, this.data);
 75     }
 76     
 77     /**
 78      * 構建文件下載器
 79      * @param downloadUrl 下載路徑
 80      * @param fileSaveDir 文件保存目錄
 81      * @param threadNum 下載線程數(shù)
 82      */
 83     public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {
 84         try {
 85             this.context = context;
 86             this.downloadUrl = downloadUrl;
 87             fileService = new FileService(this.context);
 88             URL url = new URL(this.downloadUrl);
 89             if(!fileSaveDir.exists()) fileSaveDir.mkdirs();
 90             this.threads = new DownloadThread[threadNum];                    
 91             
 92             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 93             conn.setConnectTimeout(5*1000);
 94             conn.setRequestMethod("GET");
 95             conn.setRequestProperty("Accept""image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
 96             conn.setRequestProperty("Accept-Language""zh-CN");
 97             conn.setRequestProperty("Referer", downloadUrl); 
 98             conn.setRequestProperty("Charset""UTF-8");
 99             conn.setRequestProperty("User-Agent""Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
100             conn.setRequestProperty("Connection""Keep-Alive");
101             conn.connect();
102             printResponseHeader(conn);
103             
104             if (conn.getResponseCode()==200) {
105                 this.fileSize = conn.getContentLength();//根據(jù)響應獲取文件大小
106                 if (this.fileSize <= 0throw new RuntimeException("Unkown file size ");
107                         
108                 String filename = getFileName(conn);//獲取文件名稱
109                 this.saveFile = new File(fileSaveDir, filename);//構建保存文件
110                 Map<Integer, Integer> logdata = fileService.getData(downloadUrl);//獲取下載記錄
111                 
112                 if(logdata.size()>0){//如果存在下載記錄
113                     for(Map.Entry<Integer, Integer> entry : logdata.entrySet())
114                         data.put(entry.getKey(), entry.getValue());//把各條線程已經(jīng)下載的數(shù)據(jù)長度放入data中
115                 }
116                 
117                 if(this.data.size()==this.threads.length){//下面計算所有線程已經(jīng)下載的數(shù)據(jù)長度
118                     for (int i = 0; i < this.threads.length; i++) {
119                         this.downloadSize += this.data.get(i+1);
120                     }
121                     
122                     print("已經(jīng)下載的長度"+ this.downloadSize);
123                 }
124                 
125                 //計算每條線程下載的數(shù)據(jù)長度
126                 this.block = (this.fileSize % this.threads.length)==0? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1;
127             }else{
128                 throw new RuntimeException("server no response ");
129             }
130         } catch (Exception e) {
131             print(e.toString());
132             throw new RuntimeException("don't connection this url");
133         }
134     }
135     
136     /**
137      * 獲取文件名
138      * @param conn
139      * @return
140      */
141     private String getFileName(HttpURLConnection conn) {
142         String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/'+ 1);
143         
144         if(filename==null || "".equals(filename.trim())){//如果獲取不到文件名稱
145             for (int i = 0;; i++) {
146                 String mine = conn.getHeaderField(i);
147                 
148                 if (mine == nullbreak;
149                 
150                 if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){
151                     Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());
152                     if(m.find()) return m.group(1);
153                 }
154             }
155             
156             filename = UUID.randomUUID()+ ".tmp";//默認取一個文件名
157         }
158         
159         return filename;
160     }
161     
162     /**
163      *  開始下載文件
164      * @param listener 監(jiān)聽下載數(shù)量的變化,如果不需要了解實時下載的數(shù)量,可以設置為null
165      * @return 已下載文件大小
166      * @throws Exception
167      */
168     public int download(DownloadProgressListener listener) throws Exception{
169         try {
170             RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");
171             if(this.fileSize>0) randOut.setLength(this.fileSize);
172             randOut.close();
173             URL url = new URL(this.downloadUrl);
174             
175             if(this.data.size() != this.threads.length){
176                 this.data.clear();
177                 
178                 for (int i = 0; i < this.threads.length; i++) {
179                     this.data.put(i+10);//初始化每條線程已經(jīng)下載的數(shù)據(jù)長度為0
180                 }
181             }
182             
183             for (int i = 0; i < this.threads.length; i++) {//開啟線程進行下載
184                 int downLength = this.data.get(i+1);
185                 
186                 if(downLength < this.block && this.downloadSize<this.fileSize){//判斷線程是否已經(jīng)完成下載,否則繼續(xù)下載    
187                     this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);
188                     this.threads[i].setPriority(7);
189                     this.threads[i].start();
190                 }else{
191                     this.threads[i] = null;
192                 }
193             }
194             
195             this.fileService.save(this.downloadUrl, this.data);
196             boolean notFinish = true;//下載未完成
197             
198             while (notFinish) {// 循環(huán)判斷所有線程是否完成下載
199                 Thread.sleep(900);
200                 notFinish = false;//假定全部線程下載完成
201                 
202                 for (int i = 0; i < this.threads.length; i++){
203                     if (this.threads[i] != null && !this.threads[i].isFinish()) {//如果發(fā)現(xiàn)線程未完成下載
204                         notFinish = true;//設置標志為下載沒有完成
205                         
206                         if(this.threads[i].getDownLength() == -1){//如果下載失敗,再重新下載
207                             this.threads[i] = new DownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);
208                             this.threads[i].setPriority(7);
209                             this.threads[i].start();
210                         }
211                     }
212                 }    
213                 
214                 if(listener!=null) listener.onDownloadSize(this.downloadSize);//通知目前已經(jīng)下載完成的數(shù)據(jù)長度
215             }
216             
217             fileService.delete(this.downloadUrl);
218         } catch (Exception e) {
219             print(e.toString());
220             throw new Exception("file download fail");
221         }
222         return this.downloadSize;
223     }
224     
225     /**
226      * 獲取Http響應頭字段
227      * @param http
228      * @return
229      */
230     public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {
231         Map<String, String> header = new LinkedHashMap<String, String>();
232         
233         for (int i = 0;; i++) {
234             String mine = http.getHeaderField(i);
235             if (mine == nullbreak;
236             header.put(http.getHeaderFieldKey(i), mine);
237         }
238         
239         return header;
240     }
241     
242     /**
243      * 打印Http頭字段
244      * @param http
245      */
246     public static void printResponseHeader(HttpURLConnection http){
247         Map<String, String> header = getHttpResponseHeader(http);
248         
249         for(Map.Entry<String, String> entry : header.entrySet()){
250             String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";
251             print(key+ entry.getValue());
252         }
253     }
254 
255     private static void print(String msg){
256         Log.i(TAG, msg);
257     }
258 }
DownloadThread 中代碼:

 1 package com.android.network;
 2 import java.io.File;
 3 import java.io.InputStream;
 4 import java.io.RandomAccessFile;
 5 import java.net.HttpURLConnection;
 6 import java.net.URL;
 7 
 8 import android.util.Log;
 9 
10 public class DownloadThread extends Thread {
11     private static final String TAG = "DownloadThread";
12     private File saveFile;
13     private URL downUrl;
14     private int block;
15     
16     /* 下載開始位置  */
17     private int threadId = -1;    
18     private int downLength;
19     private boolean finish = false;
20     private FileDownloader downloader;
21     
22     public DownloadThread(FileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) {
23         this.downUrl = downUrl;
24         this.saveFile = saveFile;
25         this.block = block;
26         this.downloader = downloader;
27         this.threadId = threadId;
28         this.downLength = downLength;
29     }
30     
31     @Override
32     public void run() {
33         if(downLength < block){//未下載完成
34             try {
35                 HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();
36                 http.setConnectTimeout(5 * 1000);
37                 http.setRequestMethod("GET");
38                 http.setRequestProperty("Accept""image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
39                 http.setRequestProperty("Accept-Language""zh-CN");
40                 http.setRequestProperty("Referer", downUrl.toString()); 
41                 http.setRequestProperty("Charset""UTF-8");
42                 int startPos = block * (threadId - 1+ downLength;//開始位置
43                 int endPos = block * threadId -1;//結束位置
44                 http.setRequestProperty("Range""bytes=" + startPos + "-"+ endPos);//設置獲取實體數(shù)據(jù)的范圍
45                 http.setRequestProperty("User-Agent""Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
46                 http.setRequestProperty("Connection""Keep-Alive");
47                 
48                 InputStream inStream = http.getInputStream();
49                 byte[] buffer = new byte[1024];
50                 int offset = 0;
51                 print("Thread " + this.threadId + " start download from position "+ startPos);
52                 RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");
53                 threadfile.seek(startPos);
54                 
55                 while ((offset = inStream.read(buffer, 01024)) != -1) {
56                     threadfile.write(buffer, 0, offset);
57                     downLength += offset;
58                     downloader.update(this.threadId, downLength);
59                     downloader.append(offset);
60                 }
61                 
62                 threadfile.close();
63                 inStream.close();
64                 print("Thread " + this.threadId + " download finish");
65                 this.finish = true;
66             } catch (Exception e) {
67                 this.downLength = -1;
68                 print("Thread "+ this.threadId+ ":"+ e);
69             }
70         }
71     }
72     
73     private static void print(String msg){
74         Log.i(TAG, msg);
75     }
76     
77     /**
78      * 下載是否完成
79      * @return
80      */
81     public boolean isFinish() {
82         return finish;
83     }
84     
85     /**
86      * 已經(jīng)下載的內(nèi)容大小
87      * @return 如果返回值為-1,代表下載失敗
88      */
89     public long getDownLength() {
90         return downLength;
91     }
92 }