博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android通用简洁的下载器
阅读量:6324 次
发布时间:2019-06-22

本文共 17098 字,大约阅读时间需要 56 分钟。

下载逻辑在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;        }        @Override        public 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
weakReference; private DownloadHandler(CommonDownloader patchDownloader) { super(Looper.getMainLooper()); weakReference = new WeakReference
(patchDownloader); } @Override public 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防止内存泄漏的方法: 

2. 停止下载的方法:

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

断点续传

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

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
{ protected Disposable d;//可以用于取消注册的监听者 protected DownloadInfo downloadInfo; @Override public void onSubscribe(Disposable d) { this.d = d; } @Override public void onNext(DownloadInfo downloadInfo) { this.downloadInfo = downloadInfo; } @Override public void onError(Throwable e) { e.printStackTrace(); }}
DownLoadObserver

public class DownloadManager {    private static final AtomicReference
INSTANCE = new AtomicReference<>(); private HashMap
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
{ private DownloadInfo downloadInfo; public DownloadSubscribe(DownloadInfo downloadInfo) { this.downloadInfo = downloadInfo; } @Override public void subscribe(ObservableEmitter
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];//缓冲数组2kB int 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.cnblogs.com/NeilZhang/p/9600859.html

你可能感兴趣的文章
android判断网络连接状态的三种方法
查看>>
ZOJ Monthly, March 2013 解题报告
查看>>
LaTex表格 Itemize&&enumerate
查看>>
Spring Boot中@OneToMany与@ManyToOne几个需要注意的问题
查看>>
文件传输协议之FTP
查看>>
Openstack 安装部署指南翻译系列 之 Glance服务安装(Image)
查看>>
Java 使用POI实现execl的导入导出数据实践
查看>>
Unity3D游戏开发之伤害数值显示
查看>>
如何在Linux上搭建一个基于Web的轻型监控系统
查看>>
linux基础命令使用
查看>>
zabbix简单检测
查看>>
other模块的网络请求业务封装工具类
查看>>
Windows进程崩溃问题定位方法
查看>>
程序员如何让自己 Be Cloud Native - 配置篇
查看>>
SQL Server各个版本之间的差异
查看>>
如何拆笔记本键盘(组图)
查看>>
lua install
查看>>
海量数据处理 算法总结
查看>>
DNS服务器之主从服务搭建
查看>>
vim编辑器常用操作整理
查看>>