Android通用简洁的下载器

时间:2022-12-09 16:37:18

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

主要对外接口

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

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

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

结构(十分简单)

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

Android通用简洁的下载器

内部实现

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<CommonDownloader> weakReference; private DownloadHandler(CommonDownloader patchDownloader) {
super(Looper.getMainLooper());
weakReference = new WeakReference<CommonDownloader>(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防止内存泄漏的方法:  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

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

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

Android通用简洁的下载器Android通用简洁的下载器
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

Android通用简洁的下载器Android通用简洁的下载器
public  abstract class DownLoadObserver implements Observer<DownloadInfo> {
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

Android通用简洁的下载器Android通用简洁的下载器
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;
} @Override
public 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];//缓冲数组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

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

Android通用简洁的下载器

参考:

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

Android通用简洁的下载器的更多相关文章

  1. Android开发多线程断点续传下载器

    使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...

  2. 实现android支持多线程断点续传下载器功能

    多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ...

  3. Android开源库--Universal Image Loader通用图片加载器

    如果说我比别人看得更远些,那是因为我站在了巨人的肩上.   github地址:https://github.com/nostra13/Android-Universal-Image-Loader 介绍 ...

  4. 我的Android进阶之旅------>Android基于HTTP协议的多线程断点下载器的实现

    一.首先写这篇文章之前,要了解实现该Android多线程断点下载器的几个知识点 1.多线程下载的原理,如下图所示 注意:由于Android移动设备和PC机的处理器还是不能相比,所以开辟的子线程建议不要 ...

  5. 星之小说下载器Android版

    原本是想在酷安上架的,然而审核不通过..只能通过网页方式宣传了 一款使用Jsoup开源库网络爬虫的APP,将在线阅读的小说解析,把小说全本下载为txt文件 由于使用爬虫技术,所以下载的速度不是很理想, ...

  6. 60&period;Android通用流行框架大全

    转载:https://segmentfault.com/a/1190000005073746 Android通用流行框架大全 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的 ...

  7. 15类Android通用流行框架

    15类Android通用流行框架 Android流行框架 缓存 DiskLruCache Java实现基于LRU的磁盘缓存 图片加载 Android Universal Image Loader 一个 ...

  8. 我的Android进阶之旅------>Android通用流行框架大全

    Android通用流行框架大全 缓存 图片加载 图片处理 网络请求 网络解析 数据库 依赖注入 图表 后台处理 事件总线 响应式编程 Log框架 测试框架 调试框架 性能优化 本文转载于lavor的博 ...

  9. (转载)15 个 Android 通用流行框架大全

    15 个 Android 通用流行框架大全 时间:2017-03-20 11:36来源:未知 作者:admin 点击: 2089 次 15 个 Android 通用流行框架大全   1. 缓存 Dis ...

随机推荐

  1. MongoDB数据库的操作,增删改查

    在student集合中插入一些数据 db.student.insert({ "学号":10010, "姓名":"德莱文", "年龄 ...

  2. &lbrack;goa&rsqb;golang微服务框架学习(三)-- 使用swagger-ui展示API

    既然goa框架自动生成啦swagger-json文件,那么如何用swagger-ui展示出来呢? 这里分三步: 1.下载swagger-ui的web代码 2.添加swagger.json 和 swag ...

  3. easyui-validatebox 验证两次密码是否输入一致

    验证两次密码是否输入一致 $.extend($.fn.validatebox.defaults.rules, {        /*必须和某个字段相等*/        equalTo: { vali ...

  4. C&plus;&plus; 类的动态组件化技术

    序言: N年前,我们曾在软件开发上出现了这样的困惑,用VC开发COM组件过于复杂,用VB开发COM组件发现效率低,而且不能实现面向对象的很多特性,例如,继承,多态等.更况且如何快速封装利用历史遗留的大 ...

  5. &lbrack;转载&rsqb;char &ast; 和char &lbrack;&rsqb;的区别---之第一篇

    char *  和char []的区别---之第一篇 原文地址http://blog.csdn.net/yahohi/article/details/7427724 在C/C++中,指针和数组在很多地 ...

  6. 接口和JAVA设计模式

  7. SQL数据库关键字和列名冲突处理

    在设计SQL数据库的时候可能由于考虑不全,使列名和数据库内关键字冲突,可能导致Query不能被正确识别,对列名要加[]处理.

  8. C语言练习-学生信息管理系统

    题目要求: 学生信息管理系统struct studentInfo{ int id; char name[128]; int age; char sex; int c_score; int cpp_sc ...

  9. 运行CUDA实例时候出现的问题

    问题一:>LINK : fatal error LNK1123: 转换到 COFF 期间失败:文件无效或损坏 将 项目——项目属性——配置属性——连接器——清单文件——嵌入清单 “是”改为“否” ...

  10. 【一天一道LeetCode】&num;237&period; Delete Node in a Linked List

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Write a ...