首页 > 代码库 > 一个简单Android下载管理器的实现(支持断点续传)

一个简单Android下载管理器的实现(支持断点续传)

近期工作不是很忙,时间比较多,所以在空闲时间准备自己编写一个简单的Android下载管理器。该管理器实现如下功能:

1、能够支持正常的下载,暂停,继续,安装操作。

2、支持断点续传,实现暂停继续功能,在推出应用后,再次进入应用依然能正常将文件下载完成。

3、实现实时状态回调,下载进度,速度,一目了然。

 

以上是UML设计图,这个简单下载器的实现,有几个技术难点,攻克它们问题就迎刃而解。

1、如何实现断点续传:这个问题其实不难,网上也有很多相关资料,基本原理都相同,就是记录下载任务上一次中断的位置,并保存已经下载的部分到本地文件,下次继续下载时,从中断的位置开始下载(也可以在中断位置前面一点,或许可以减小丢包率)

  继续将下载的内容写入已经保存的本地部分文件中即可。

2、有了断点续传,那么暂停\继续功能就解决了。

3、实时状态回调,获取下载进度和速度。很容易想到,下载操作会在一个工作线程中进行,那么需要实现状态监听,只需要把下载过程中遇到的问题实时回调到主线程即可。关于下载进度,这个只需要在读取输入流写入本地文件的时候,实时获取即可;下载速度

  获取的原理相似,比如每隔两秒统计一下下载文件的大小除以2就得到了下载速度。

 

下面贴上几个主要类的代码

 1 /**
 2  * 
 3  */
 4 package com.example.downloadmanagerdemo.download;
 5 
 6 /**
 7  * 下载过程中各种状态回调
 8  * @author careyjwang
 9  */
10 public interface DownloadTaskImp {
11     void onPrepared();
12     
13     void onStart();
14     
15     void onProgressChanged(int progress);
16     
17     void onPause();
18     
19     void onNetworkError();
20     
21     void onReadError();
22     
23     void onSDCardMounted();
24     
25     void onSpaceNotEnough();
26     
27     void onUnKnowError();
28     
29     void onCancel();
30     
31     void onFinished();
32 }
  1 /**
  2  * 
  3  */
  4 package com.example.downloadmanagerdemo.download;
  5 
  6 import java.io.ByteArrayOutputStream;
  7 import java.io.File;
  8 import java.io.IOException;
  9 import java.io.InputStream;
 10 import java.io.RandomAccessFile;
 11 import java.net.HttpURLConnection;
 12 import java.net.MalformedURLException;
 13 import java.net.URL;
 14 
 15 import android.os.Handler;
 16 import android.os.Looper;
 17 import android.util.Log;
 18 
 19 /**
 20  * 下载线程
 21  */
 22 public class DownloadTask extends Thread implements DownloadTaskImp {
 23     private File mSaveFile;//保存的文件
 24     private DownloadInfo mDownloadInfo;
 25     private long mDownloadedLength;
 26     private DownloadStatusCallback mCallback;//下载状态监听
 27     private DownloadMMCache mCache;//内存级别缓存
 28     private boolean mUseMMCache = false;// 表示是否使用缓存
 29     private boolean mKeepLoading = true;//用于实现暂停的flag
 30 
 31     public DownloadTask(String savePath, DownloadInfo info) {
 32         mSaveFile = new File(savePath);
 33         mDownloadInfo = info;
 34         mDownloadedLength = info.getDownloadedSize();
 35         mDownloadInfo.setSavePath(savePath);
 36     }
 37 
 38     /**
 39      * 设置下载监听
 40      * @param callback
 41      */
 42     public void setDownloadStatusCallback(DownloadStatusCallback callback) {
 43         mCallback = callback;
 44     }
 45 
 46     /**
 47      * 是否使用内存缓存
 48      * @param use
 49      */
 50     public void useMMCache(boolean use) {
 51         mUseMMCache = use;
 52     }
 53 
 54     /**
 55      * 暂停下载
 56      */
 57     public void pause() {
 58         mKeepLoading = false;
 59         onPause();
 60     }
 61     
 62     /**
 63      * 取消下载
 64      */
 65     public void cancel(){
 66         mKeepLoading = false;
 67         DownloadUtils.deleteFile(mSaveFile);
 68     }
 69 
 70     @Override
 71     public void run() {
 72         DownloadInfo info = mDownloadInfo;
 73         String downloadUrl = info.getDownloadUrl();
 74         long start = mDownloadedLength;
 75         HttpURLConnection connection = null;
 76         RandomAccessFile accessFile = null;
 77         InputStream is = null;
 78         try {
 79             connection = createDefaultHttpConnection(downloadUrl);
 80             long length = getContentLength(downloadUrl);
 81             if (length <= 0) {
 82                 onUnKnowError();
 83             }
 84             info.setSize(length);
 85             if (start > 0) {
 86                 connection.setRequestProperty("Range", "bytes=" + start + "-" + (length - 1));
 87             }
 88             Log.i(DownloadConfig.LOG_TAG, "mSaveFile = " + mSaveFile + " length = " + length);
 89             connection.connect();
 90             // RandomAccessFile的打开操作应该放到connect成功之后。
 91             accessFile = new RandomAccessFile(mSaveFile, "rw");
 92             accessFile.seek(0);
 93             if (mUseMMCache) {
 94                 mCache = new DownloadMMCache(new ByteArrayOutputStream(), accessFile,
 95                                 DownloadConfig.DEFAULT_DOWNLOAD_MM_CACHED_SIZE);
 96             }
 97             is = connection.getInputStream();
 98             byte[] temp = new byte[2 * 1024];
 99             int readed = 0;
100             onDownloadStart();
101             onStart();
102             while ((readed = is.read(temp, 0, temp.length)) != -1 && mKeepLoading) {
103                 if (!DownloadUtils.isSDCardEnable()) {
104                     onSDCardMounted();
105                     return;
106                 }
107 
108                 if (!DownloadUtils.isSDCardSpaceEnough()) {
109                     onSpaceNotEnough();
110                     return;
111                 }
112                 Log.i(DownloadConfig.LOG_TAG, "write data");
113                 if (mUseMMCache) {// 采用缓存策略,减少文件写入频率
114                     mCache.write(temp, 0, readed);
115                 } else {
116                     accessFile.write(temp, 0, readed);
117                 }
118                 mDownloadedLength += readed;
119                 int progress = (int) (mDownloadedLength * 100 / length);
120                 if (progress - info.getProgress() >= 1) {
121                     onProgressChanged(progress);
122                     info.setProgress(progress);
123                     info.setDownloadedSize(mDownloadedLength);
124                 }
125             }
126             if (mUseMMCache) {
127                 mCache.flush();
128             }
129             accessFile.close();
130             is.close();
131             connection.disconnect();
132             onDownloadFinished();
133             onFinished();
134         } catch (MalformedURLException e) {
135             e.printStackTrace();
136             onNetworkError();
137         } catch (IOException e) {
138             e.printStackTrace();
139             onReadError();
140         } finally {
141             if (connection != null) {
142                 connection.disconnect();
143             }
144             if (accessFile != null) {
145                 try {
146                     accessFile.close();
147                 } catch (IOException e) {
148                     e.printStackTrace();
149                 }
150             }
151             if (is != null) {
152                 try {
153                     is.close();
154                 } catch (IOException e) {
155                     e.printStackTrace();
156                 }
157             }
158         }
159     }
160 
161     /**
162      * 创建一个默认的HttpUrlConnection
163      * @param downloadUrl
164      * @return
165      * @throws IOException
166      * @throws MalformedURLException
167      */
168     private HttpURLConnection createDefaultHttpConnection(String downloadUrl) throws IOException, MalformedURLException {
169         HttpURLConnection connection;
170         connection = (HttpURLConnection) new URL(downloadUrl).openConnection();
171         connection.setRequestProperty("Connection", "Keep-Alive");
172         connection.setRequestProperty("Charset", "UTF-8");
173         connection.setRequestProperty("Accept-Language", "zh-CN");
174         connection.setRequestProperty("User-Agent", "Android");
175         return connection;
176     }
177 
178     public long getContentLength(String url) throws IOException {
179         URL u = new URL(url);
180         HttpURLConnection conn = (HttpURLConnection) u.openConnection();
181         long l = conn.getContentLength();
182         conn.disconnect();
183         return l;
184     }
185 
186     private void onDownloadFinished() {
187         runOnMainThread(new Runnable() {
188             @Override
189             public void run() {
190                 if (mCallback != null) {
191                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_SUCCESS, mDownloadInfo.getPackageName());
192                 }
193             }
194         });
195     }
196 
197     private void onDownloadStart() {
198         runOnMainThread(new Runnable() {
199             @Override
200             public void run() {
201                 if (mCallback != null) {
202                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_DOWNLOADING, mDownloadInfo.getPackageName());
203                 }
204             }
205         });
206 
207     }
208 
209     public void onDownloadWait() {
210         runOnMainThread(new Runnable() {
211             @Override
212             public void run() {
213                 if (mCallback != null) {
214                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_WAIT, mDownloadInfo.getPackageName());
215                 }
216             }
217         });
218 
219     }
220 
221     /**
222      * 在主线程执行一个操作 
223      * @param run
224      */
225     private void runOnMainThread(Runnable run) {
226         new Handler(Looper.getMainLooper()).post(run);
227     }
228 
229     @Override
230     public void onStart() {
231         runOnMainThread(new Runnable() {
232             @Override
233             public void run() {
234                 if (mCallback != null) {
235                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_DOWNLOADING, mDownloadInfo.getPackageName());
236                 }
237             }
238         });
239     }
240 
241     @Override
242     public void onProgressChanged(final int progress) {
243         runOnMainThread(new Runnable() {
244             @Override
245             public void run() {
246                 if (mCallback != null) {
247                     mCallback.onDownloadProgressChanged(progress, mDownloadInfo.getPackageName());
248                 }
249             }
250         });
251     }
252 
253     @Override
254     public void onNetworkError() {
255         runOnMainThread(new Runnable() {
256             @Override
257             public void run() {
258                 if (mCallback != null) {
259                     DownloadError error = new DownloadError();
260                     error.errorCode = DownloadError.ERROR_NETWORK;
261                     mCallback.onDownloadError(error);
262                 }
263             }
264         });
265     }
266 
267     @Override
268     public void onSDCardMounted() {
269         runOnMainThread(new Runnable() {
270             @Override
271             public void run() {
272                 if (mCallback != null) {
273                     DownloadError error = new DownloadError();
274                     error.errorCode = DownloadError.ERROR_SDCARD_UNMOUNTED;
275                     mCallback.onDownloadError(error);
276                 }
277             }
278         });
279     }
280 
281     @Override
282     public void onSpaceNotEnough() {
283         runOnMainThread(new Runnable() {
284             @Override
285             public void run() {
286                 if (mCallback != null) {
287                     DownloadError error = new DownloadError();
288                     error.errorCode = DownloadError.ERROR_SDCARD_SPACE_NOT_ENOUGH;
289                     mCallback.onDownloadError(error);
290                 }
291             }
292         });
293     }
294 
295     @Override
296     public void onUnKnowError() {
297         runOnMainThread(new Runnable() {
298             @Override
299             public void run() {
300                 if (mCallback != null) {
301                     DownloadError error = new DownloadError();
302                     error.errorCode = DownloadError.ERROR_UNKNOW;
303                     mCallback.onDownloadError(error);
304                 }
305             }
306         });
307     }
308 
309     @Override
310     public void onFinished() {
311         runOnMainThread(new Runnable() {
312             @Override
313             public void run() {
314                 if (mCallback != null) {
315                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_SUCCESS, mDownloadInfo.getPackageName());
316                 }
317             }
318         });
319     }
320 
321     @Override
322     public void onReadError() {
323         runOnMainThread(new Runnable() {
324             @Override
325             public void run() {
326                 if (mCallback != null) {
327                     DownloadError error = new DownloadError();
328                     error.errorCode = DownloadError.ERROR_READ;
329                     mCallback.onDownloadError(error);
330                 }
331             }
332         });
333     }
334 
335     @Override
336     public void onPrepared() {
337         runOnMainThread(new Runnable() {
338             @Override
339             public void run() {
340                 if (mCallback != null) {
341                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_WAIT, mDownloadInfo.getPackageName());
342                 }
343             }
344         });
345     }
346 
347     @Override
348     public void onPause() {
349         runOnMainThread(new Runnable() {
350             @Override
351             public void run() {
352                 if (mCallback != null) {
353                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_PAUSE, mDownloadInfo.getPackageName());
354                 }
355             }
356         });
357     }
358 
359     @Override
360     public void onCancel() {
361         runOnMainThread(new Runnable(){
362             @Override
363             public void run() {
364                 if (mCallback != null) {
365                     mCallback.onDownloadStatusChanged(DownloadStatus.STATUS_CANCEL, mDownloadInfo.getPackageName());
366                 }
367             }
368         });
369     }
370 }
 1 /**
 2  * 
 3  */
 4 package com.example.downloadmanagerdemo.download;
 5 
 6 /**
 7  * 下载过程中可能出现的各种状态,暂时只有这些,你可以补齐
 8  * @author careyjwang
 9  */
10 public class DownloadStatus {
11     public static final int STATUS_INIT = 1;
12     public static final int STATUS_WAIT = 2;
13     public static final int STATUS_DOWNLOADING = 3;
14     public static final int STATUS_PAUSE = 4;
15     public static final int STATUS_CANCEL = 5;
16     public static final int STATUS_SUCCESS = 6;
17     public static final int STATUS_FAILED = 7;
18 }
 1 /**
 2  * 
 3  */
 4 package com.example.downloadmanagerdemo.download;
 5 
 6 import java.io.File;
 7 
 8 import android.os.Environment;
 9 
10 /**
11  * 需要用到的一些配置属性
12  * @author careyjwang
13  */
14 public class DownloadConfig {
15     public static final String LOG_TAG = "DownloadManagerDemo";
16     public static final String DOWNLOAD_FILE_ROOT = Environment.getExternalStorageDirectory().getAbsolutePath()
17                     + File.separator + "downloadManager";
18 
19     public static final long DEFAULT_DOWNLOAD_MM_CACHED_SIZE = 128 * 1024;//默认下载内存缓存区域大小
20 
21     public static final long MIN_SDCARD_SPACE = 5 * 1024 * 1024;//SD卡最小空间5MB
22     
23     public static final long DEFAULT_SPEED_NOTIFY_LENGTH = 512 * 1024;//默认计算下载速度的缓冲区大小
24     
25     public static final long DEFAULT_SPEED_NOTIFY_PERIOD_LENGTH = 2 * 1000;//默认计算下载速度的时间间隔
26 }

 

 

以上是部分代码,这篇文章写的比较匆忙,代码在家里的电脑上,有需要的朋友可以在文章下面留下邮箱,我将完整代码发过去。