使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源, 从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线、电量不足等情况下,这就需要使用到断点续传功能,下次启动 时从记录位置继续下载,可避免重复部分的下载。这里采用数据库来记录下载的进度。

断点续传

1.断点续传需要在下载过程中记录每条线程的下载进度

2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库

3.在每次向文件中写入数据之后,在数据库中更新下载进度

4.下载完成之后删除数据库中下载记录

Handler传输数据

这个主要用来记录百分比,每下载一部分数据就通知主线程来记录时间

1.主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据

2.我们使用Handler可以处理这种需求

   主线程中创建Handler,重写handleMessage()方法

   新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法


        使用多线程的好处:使用多线程下载会提升文件下载的速度。那么多线程下载文件的过程是:

        (1)首先获得下载文件的长度,然后设置本地文件的长度。

             HttpURLConnection.getContentLength();//获取下载文件的长度

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

              file.setLength(filesize);//设置本地文件的长度


          (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。

             如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。


例如10M大小,使用3个线程来下载,

               线程下载的数据长度   (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M

                下载开始位置:线程id*每条线程下载的数据长度 = ?

               下载结束位置:(线程id+1)*每条线程下载的数据长度-1=?

(3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,

                如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止

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


(4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。

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

threadfile.seek(2097152);//从文件的什么位置开始写入数据


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网络多线程断点下载</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中创建与删除文件权限 -->
17     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
18     
19     <!-- 往SDCard写入数据权限 -->
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被创建会关联到创建它的当前线程的消息队列,该类用于往消息队列发送消息
 26      * 消息队列中的消息由当前线程内部进行处理
 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) {//实时获知文件已经下载的数据长度
 95                             Message msg = new Message();
 96                             msg.what = 1;
 97                             msg.getData().putInt("size", size);
 98                             handler.sendMessage(msg);//发送消息
 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      * 获取每条线程已经下载的文件长度
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      * 保存每条线程已经下载的文件长度
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      * 实时更新每条线程已经下载的文件长度
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     /* 线程数 */
 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      * 获取线程数
 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 下载线程数
 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();//根据响应获取文件大小
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());//把各条线程已经下载的数据长度放入data中
115                 }
116                 
117                 if(this.data.size()==this.threads.length){//下面计算所有线程已经下载的数据长度
118                     for (int i = 0; i < this.threads.length; i++) {
119                         this.downloadSize += this.data.get(i+1);
120                     }
121                     
122                     print("已经下载的长度"+ this.downloadSize);
123                 }
124                 
125                 //计算每条线程下载的数据长度
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 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为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);//初始化每条线程已经下载的数据长度为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){//判断线程是否已经完成下载,否则继续下载    
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) {// 循环判断所有线程是否完成下载
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()) {//如果发现线程未完成下载
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);//通知目前已经下载完成的数据长度
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);//设置获取实体数据的范围
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      * 已经下载的内容大小
87      * @return 如果返回值为-1,代表下载失败
88      */
89     public long getDownLength() {
90         return downLength;
91     }
92 }