下载逻辑在android开发中可谓很常见,那么封装一个通用简洁的下载器时很有必要的。如果不想给工程引入一个很重的jar包那么可以直接复用下面的代码即可。

主要对外接口

构造函数 :     public CommonDownloader(String saveDir, int timeoutMs)

开始下载接口: public void start(String saveFileName, String url)

停止下载接口: public void stop()

结构(十分简单)

下载主要由一个Handler和一个下载线程组成,Handler统一处理结果,下载线程负责将下载并将结果发送给Handler。

内部实现

public class CommonDownloader {/**patch save dir*/private String mSaveDir;/**http request timeout*/private int mTimeoutMs;/**download listener, see {@link OnDownloadListener}*/private OnDownloadListener mDownloadListener;private Thread mDownloadThread;/**download control tag*/private boolean isStop;/**UI event handler, see {@link DownloadHandler}*/private DownloadHandler mDownloadHandler;/*** download event listener*/public interface OnDownloadListener {/**start download the callback*/void onStarted();/**download success the callback*/void onSuccess(String file);/**download failed the callback*/void onFailed(String errorMsg);}public CommonDownloader(String saveDir, int timeoutMs) {if (TextUtils.isEmpty(saveDir)) {throw new IllegalArgumentException("mSaveDir is empty! please reset.");} else {File file = new File(saveDir);if (!file.exists() || !file.isDirectory()) {if (!file.mkdirs()) {throw new IllegalArgumentException("failed to create file directory. > " + file.getAbsolutePath());}}this.mSaveDir = saveDir;}this.mTimeoutMs = timeoutMs;mDownloadHandler = new DownloadHandler(this);}/*** start download* @param patchSaveFileName* @param url*/public void start(String patchSaveFileName, String url) {mDownloadHandler.sendEmptyMessage(DownloadHandler.STATUS_START);if (TextUtils.isEmpty(patchSaveFileName)) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = "patchSaveFileName is empty! please reset.";mDownloadHandler.sendMessage(message);return;}File file = new File(mSaveDir, patchSaveFileName);if (file.exists() && file.isFile()) {if (!file.delete()) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = "try deleted this file failed. >" + file.getAbsolutePath();mDownloadHandler.sendMessage(message);return;}}try {if (!file.createNewFile()) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = "failed to create the patch file. >" + file.getAbsolutePath();mDownloadHandler.sendMessage(message);return;}} catch (IOException e) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = e.getMessage();mDownloadHandler.sendMessage(message);Log.e(e);return;}stop();mDownloadThread = new Thread(new DownloadTask(url, patchSaveFileName, file));mDownloadThread.start();}/*** stop download*/public void stop() {isStop = true;if (mDownloadThread != null) {try {mDownloadThread.join(3000);} catch (InterruptedException e) {Log.w(e.getMessage());}}}/*** set the download listener* @param mDownloadListener*/public void setmDownloadListener(OnDownloadListener mDownloadListener) {this.mDownloadListener = mDownloadListener;}/*** create file output stream* @param patchSaveFileName* @return*/private OutputStream createOutputStream(String patchSaveFileName) {FileOutputStream fileOutputStream = null;try {fileOutputStream = new FileOutputStream(new File(mSaveDir, patchSaveFileName));} catch (FileNotFoundException e) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = e.getMessage();mDownloadHandler.sendMessage(message);Log.e(e);}return fileOutputStream;}/*** download task*/private class DownloadTask implements Runnable {private String urlAddress;private String patchSaveFileName;private File downloadFile;private DownloadTask(String urlAddress, String patchSaveFileName, File downloadFile) {this.urlAddress = urlAddress;this.patchSaveFileName = patchSaveFileName;this.downloadFile = downloadFile;}@Overridepublic void run() {isStop = false;HttpURLConnection connection = null;InputStream inputStream = null;OutputStream outputStream = null;try {URL url = new URL(urlAddress);connection = (HttpURLConnection)url.openConnection();connection.setConnectTimeout(mTimeoutMs);connection.setReadTimeout(mTimeoutMs);connection.setUseCaches(false);connection.setDoInput(true);connection.setRequestProperty("Accept-Encoding", "identity");connection.setRequestMethod("GET");inputStream = connection.getInputStream();byte[] buffer = new byte[100 * 1024];int length;outputStream = createOutputStream(patchSaveFileName);if(outputStream == null)    return;while (!isStop && (length = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, length);}if (!isStop) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_SUCCESS;message.obj = downloadFile.getAbsolutePath();mDownloadHandler.sendMessage(message);} else {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = "the patch download has been canceled!";mDownloadHandler.sendMessage(message);}} catch (MalformedURLException e) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = e.getMessage();mDownloadHandler.sendMessage(message);Log.e(e);} catch (IOException e) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = e.getMessage();mDownloadHandler.sendMessage(message);Log.e(e);} catch (Exception ex) {Message message = Message.obtain();message.what = DownloadHandler.STATUS_FAILED;message.obj = ex.getMessage();mDownloadHandler.sendMessage(message);Log.e(ex);} finally {if (connection != null) {connection.disconnect();}if (inputStream != null) {try {inputStream.close();} catch (IOException e) {Log.e(e);}}if (outputStream != null) {try {outputStream.close();} catch (IOException e) {Log.e(e);}}}}}/*** download event handler*/private static class DownloadHandler extends Handler {private static final int STATUS_START = 0x01;private static final int STATUS_SUCCESS = 0x02;private static final int STATUS_FAILED = 0x03;private WeakReference<CommonDownloader> weakReference;private DownloadHandler(CommonDownloader patchDownloader) {super(Looper.getMainLooper());weakReference = new WeakReference<CommonDownloader>(patchDownloader);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);int status = msg.what;CommonDownloader patchDownloader = weakReference.get();switch (status) {case STATUS_START:if(patchDownloader != null && patchDownloader.mDownloadListener != null) {patchDownloader.mDownloadListener.onStarted();}break;case STATUS_SUCCESS:if(patchDownloader != null && patchDownloader.mDownloadListener != null) {patchDownloader.mDownloadListener.onSuccess((String)msg.obj);}break;case STATUS_FAILED:if (patchDownloader != null && patchDownloader.mDownloadListener != null) {patchDownloader.mDownloadListener.onFailed((String)msg.obj);}break;default:break;}}}
}

细节分析:

1. Hanlder中弱引用的使用:

当下载器已经被回收时,Listener也不会再收到回调结果

可以参考这篇关于Activity中Handler防止内存泄漏的方法:  https://blog.csdn.net/u010134087/article/details/53610654

2. 停止下载的方法:

首先将标记为  isStop 置为true,这样下载就不再进行(DownloadThread里面写数据时进行了判断),同时调用join方法等待线程停止。 (join方法含义可以参考:https://www.cnblogs.com/NeilZhang/p/8781897.html)

断点续传

断点续传支持从文件上次中断的地方开始传送数据,而并非是从文件开头传送。

http协议支持: http请求头部可以带上请求文件的开始到结束字节。

http协议首部有四种:

  • 通用首部字段
  • 请求首部字段(首部“Range”,可以设置需要下载的字节开始和结束字节,格式如下所示)

Range: bytes=5001-10000

  • 响应首部字段
  • 实体首部字段

下面贴出支持断点续传的下载器:

public class DownloadInfo {public static final long TOTAL_ERROR = -1;//获取进度失败private String url;private long total;private long progress;private String fileName;public DownloadInfo(String url) {this.url = url;}public String getUrl() {return url;}public String getFileName() {return fileName;}public void setFileName(String fileName) {this.fileName = fileName;}public long getTotal() {return total;}public void setTotal(long total) {this.total = total;}public long getProgress() {return progress;}public void setProgress(long progress) {this.progress = progress;}
}

DownloadInfo

public  abstract class DownLoadObserver implements Observer<DownloadInfo> {protected Disposable d;//可以用于取消注册的监听者protected DownloadInfo downloadInfo;@Overridepublic void onSubscribe(Disposable d) {this.d = d;}@Overridepublic void onNext(DownloadInfo downloadInfo) {this.downloadInfo = downloadInfo;}@Overridepublic void onError(Throwable e) {e.printStackTrace();}}

DownLoadObserver

public class DownloadManager {private static final AtomicReference<DownloadManager> INSTANCE = new AtomicReference<>();private HashMap<String, Call> downCalls;//用来存放各个下载的请求private OkHttpClient mClient;//OKHttpClient;//获得一个单例类public static DownloadManager getInstance() {for (; ; ) {DownloadManager current = INSTANCE.get();if (current != null) {return current;}current = new DownloadManager();if (INSTANCE.compareAndSet(null, current)) {return current;}}}private DownloadManager() {downCalls = new HashMap<>();mClient = new OkHttpClient.Builder().build();}/*** 开始下载** @param url              下载请求的网址* @param downLoadObserver 用来回调的接口*/public void download(String url, DownLoadObserver downLoadObserver) {Observable.just(url).filter(s -> !downCalls.containsKey(s))//call的map已经有了,就证明正在下载,则这次不下载.flatMap(s -> Observable.just(createDownInfo(s))).map(this::getRealFileName)//检测本地文件夹,生成新的文件名.flatMap(downloadInfo -> Observable.create(new DownloadSubscribe(downloadInfo)))//下载
//                .observeOn(AndroidSchedulers.mainThread())//在主线程回调.subscribeOn(Schedulers.io())//在子线程执行.subscribe(downLoadObserver);//添加观察者}public void cancel(String url) {Call call = downCalls.get(url);if (call != null) {call.cancel();//取消}downCalls.remove(url);}/*** 创建DownInfo** @param url 请求网址* @return DownInfo*/private DownloadInfo createDownInfo(String url) {DownloadInfo downloadInfo = new DownloadInfo(url);long contentLength = getContentLength(url);//获得文件大小downloadInfo.setTotal(contentLength);String fileName = url.substring(url.lastIndexOf("/"));downloadInfo.setFileName(fileName);return downloadInfo;}private DownloadInfo getRealFileName(DownloadInfo downloadInfo) {String fileName = downloadInfo.getFileName();long downloadLength = 0, contentLength = downloadInfo.getTotal();File file = new File(MyApp.sContext.getFilesDir(), fileName);if (file.exists()) {//找到了文件,代表已经下载过,则获取其长度downloadLength = file.length();}//之前下载过,需要重新来一个文件int i = 1;while (downloadLength >= contentLength) {int dotIndex = fileName.lastIndexOf(".");String fileNameOther;if (dotIndex == -1) {fileNameOther = fileName + "(" + i + ")";} else {fileNameOther = fileName.substring(0, dotIndex)+ "(" + i + ")" + fileName.substring(dotIndex);}File newFile = new File(MyApp.sContext.getFilesDir(), fileNameOther);file = newFile;downloadLength = newFile.length();i++;}//设置改变过的文件名/大小downloadInfo.setProgress(downloadLength);downloadInfo.setFileName(file.getName());return downloadInfo;}private class DownloadSubscribe implements ObservableOnSubscribe<DownloadInfo> {private DownloadInfo downloadInfo;public DownloadSubscribe(DownloadInfo downloadInfo) {this.downloadInfo = downloadInfo;}@Overridepublic void subscribe(ObservableEmitter<DownloadInfo> e) throws Exception {String url = downloadInfo.getUrl();long downloadLength = downloadInfo.getProgress();//已经下载好的长度long contentLength = downloadInfo.getTotal();//文件的总长度//初始进度信息e.onNext(downloadInfo);Request request = new Request.Builder()//确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分.addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength).url(url).build();Call call = mClient.newCall(request);downCalls.put(url, call);//把这个添加到call里,方便取消Response response = call.execute();File file = new File(MyApp.sContext.getFilesDir(), downloadInfo.getFileName());InputStream is = null;FileOutputStream fileOutputStream = null;try {is = response.body().byteStream();fileOutputStream = new FileOutputStream(file, true);byte[] buffer = new byte[2048];//缓冲数组2kBint len;while ((len = is.read(buffer)) != -1) {fileOutputStream.write(buffer, 0, len);downloadLength += len;downloadInfo.setProgress(downloadLength);e.onNext(downloadInfo);}fileOutputStream.flush();downCalls.remove(url);} finally {//关闭IO流IOUtil.closeAll(is, fileOutputStream);}e.onComplete();//完成}}/*** 获取下载长度** @param downloadUrl* @return*/private long getContentLength(String downloadUrl) {Request request = new Request.Builder().url(downloadUrl).build();try {Response response = mClient.newCall(request).execute();if (response != null && response.isSuccessful()) {long contentLength = response.body().contentLength();
//                response.close();return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength;}} catch (IOException e) {e.printStackTrace();}return DownloadInfo.TOTAL_ERROR;}}

DownloadManager

主要流程如下:(具体过程查看代码)

参考:

https://www.jb51.net/article/104456.htm

转载于:https://www.cnblogs.com/NeilZhang/p/9600859.html

Android通用简洁的下载器相关推荐

  1. Android实现网页图片下载器

    Android实现网页图片下载器 网页图片下载器是配合神马笔记图像画廊功能使用的一个工具. 实现了抓取并下载网页页面图片功能. 实现过程: 使用WebView加载网页 获取网页的原始HTML内容 使用 ...

  2. python编写下载器可暂停_python多进程断点续传分片下载器

    python多进程断点续传分片下载器 标签:python 下载器 多进程 因为爬虫要用到下载器,但是直接用urllib下载很慢,所以找了很久终于找到一个让我欣喜的下载器.他能够断点续传分片下载,极大提 ...

  3. 如何为Mac找到最合适的下载器

    如果你正在为Mac寻找一个好用的下载工具,当然需要进行一番搜索,然后才能选择最便捷功能最强大的工具. BT客户端都有各自的特色,有些提供功能多,有些提供在线服务等等. 我们收集并研究了Mac上顶尖的1 ...

  4. 手机qq2008java通用版下载_【手机音乐】别被它的名字迷惑了,其实它是一款很良心的播放器兼无损下载器!...

    前言 蓝叔的公众号"蓝蓝分享汇"已经坚持了一段时间不定期为大家分享最新电影,美剧,英剧,韩剧等资源,有需要的小伙伴可以去关注一下.软件类的资源将不在公众号"凌晨两点蓝&q ...

  5. android学习笔记---31_多线程断点下载器,下载原理实现

    1.1.31_多线程断点下载器 ----------------------- 1.软件界面:   文件下载路径              text框   button 下载   点击后,下面显示下载 ...

  6. Android 系统内置下载器服务 DownloadManager 的使用

    本文链接: https://blog.csdn.net/xietansheng/article/details/52513624 在 Android 程序开发中如果需要下载文件,除了自己程序内部实现下 ...

  7. android调用系统下载器下载文件

    在项目中使用到了在android端下载服务端的文件,之前使用的是http下载方式,现在想改成调用系统的下载器进行下载,实现步骤为: 1.得到url:文件下载地址 2.使用URL下载: (1)下载前先判 ...

  8. Android DownloadManager 系统下载器实现APP升级功能

    一.下载路径的设置和获取.    (1)request.setDestinationInExternalFilesDir(context, dirType, subPath);    (2)reque ...

  9. Java多线程下载器(简洁版)

    Java多线程下载器 https://github.com/rawchen/JDownloader/archive/refs/heads/master.zip 五一无聊搞出来的,虽然已存在IDM.XD ...

最新文章

  1. pip PermissionError: [Errno 13] Permission denied
  2. 怎么配置搭建Nginx网站服务器
  3. 【PAT】1009. Product of Polynomials (25)
  4. java旋转图片后边上变黑_Java旋转图像将背景的一部分变成黑色
  5. 在线MG小游戏html5源码
  6. 象棋python代码_python象棋_python 象棋_python象棋源码 - 云+社区 - 腾讯云
  7. 读“我为什么不要应届毕业生”
  8. 控件内部显示不正确原因---没有调用layoutSubview的父类方法
  9. 现代操作系统 第一章 引论 习题
  10. 网页版在线客服实现代码
  11. 云信api_服务端API文档-音视频通话-网易云信开发文档
  12. docker部署redis的单机/主从/哨兵/集群方法
  13. zotero导出PDF
  14. ubuntu启动报错 hardware error cpu 0 machine check 0 Bank 6、ACPI BIOS Error (bug)Could not resolve symbol
  15. win7 安装SQL Server2008R2 提示文件格式错误的处理
  16. 从零开始仿写一个抖音App——音视频开篇,移动安全入门
  17. 前后端是如何交互的?
  18. Cortex:多租户、可横向扩展的Prometheus即服务
  19. R语言 2021.9 Rstudio新版本不支持中文名字的文件名 乱码 数据中文路径 【用旧版2022】
  20. Matlab神经网络语音增强,基于BP神经网络的语音增强研究

热门文章

  1. 被微软称为 “世界的电脑” ,Azure 到底有多牛?
  2. 在不同浏览器上进行网页测试,结果是...... | 每日趣闻
  3. Bug 是一门艺术 | 每日趣闻
  4. 据说只有程序员才看得懂 | 每日趣闻
  5. 雷林鹏分享:PHP 简介
  6. 使用this.$router.push('')的方法进行路由跳转,提示'$router' of undefined问题
  7. Yours的Ryan X Charles谈BCH驱动社交媒体
  8. Flask 教程 第十三章:国际化和本地化
  9. Devexpress Xtraform 资源文件 汉化
  10. .net项目发布到本地IIS