近期工作不是很忙,时间比较多,所以在空闲时间准备自己编写一个简单的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 }
以上是部分代码,这篇文章写的比较匆忙,代码在家里的电脑上,有需要的朋友可以在文章下面留下邮箱,我将完整代码发过去。