安卓多任务库下载使用了HttpURLConnection进行网络传输,使用了多线程进行多任务管理。可以实时更新下载进度,速度,下载状态,剩余时间预计。
1.先定义下载数据以及状态的model(DownloadInfoModel)
public class DownloadInfoModel {
public static final int DOWNLOADING_STATUS = 0;//下载状态
public static final int WAIT_STATUS = 1;//等待状态
public static final int DOWNLOAD_FAILD_STATUS = 2;//下载失败状态
public static final int DOWNLOAD_SUCCESS_STATUS = 3;//下载成功状态
public static final int DOWNLOAD_REMOVE_STATUS = 4;//删除该条下载
private String downloadId = "";//下载任务的id
private String remoteUrl = "";//远程下载地址
private String savePath = "";//保存的路径文件夹
private String fileName = "";//保存的文件名(不包括扩展名)
private String fileExtname = "";//保存的文件扩展名
private DownloadCallback downloadCallback;
private int percentage = 0;//下载进度0-100
private long speed = 0;//下载速度byte/s
private long lestTime = 0;//剩余时间
private long len = 0;//
private long fileLength = 0;//文件大小
private int status = WAIT_STATUS;//下载状态
public int getStatus() {
return status;
}
public DownloadInfoModel setStatus(int status) {
this.status = status;
return this;
}
public String getDownloadId() {
return downloadId;
}
public DownloadInfoModel setDownloadId(String downloadId) {
this.downloadId = downloadId;
return this;
}
public String getRemoteUrl() {
return remoteUrl;
}
public DownloadInfoModel setRemoteUrl(String remoteUrl) {
this.remoteUrl = remoteUrl;
return this;
}
public String getSavePath() {
return savePath;
}
public DownloadInfoModel setSavePath(String savePath) {
this.savePath = savePath;
return this;
}
public String getFileName() {
return fileName;
}
public DownloadInfoModel setFileName(String fileName) {
this.fileName = fileName;
return this;
}
public String getFileExtname() {
return fileExtname;
}
public DownloadInfoModel setFileExtname(String fileExtname) {
this.fileExtname = fileExtname;
return this;
}
public DownloadCallback getDownloadCallback() {
return downloadCallback;
}
public DownloadInfoModel setDownloadCallback(DownloadCallback downloadCallback) {
this.downloadCallback = downloadCallback;
return this;
}
public int getPercentage() {
return percentage;
}
public DownloadInfoModel setPercentage(int percentage) {
this.percentage = percentage;
return this;
}
public long getSpeed() {
return speed;
}
public DownloadInfoModel setSpeed(long speed) {
this.speed = speed;
return this;
}
public long getLestTime() {
return lestTime;
}
public DownloadInfoModel setLestTime(long lestTime) {
this.lestTime = lestTime;
return this;
}
public long getLen() {
return len;
}
public DownloadInfoModel setLen(long len) {
this.len = len;
return this;
}
public long getFileLength() {
return fileLength;
}
public DownloadInfoModel setFileLength(long fileLength) {
this.fileLength = fileLength;
return this;
}
@Override
public String toString() {
return "DownloadInfoModel{" +
"downloadId='" + downloadId + '\'' +
", remoteUrl='" + remoteUrl + '\'' +
", savePath='" + savePath + '\'' +
", fileName='" + fileName + '\'' +
", fileExtname='" + fileExtname + '\'' +
", downloadCallback=" + downloadCallback +
", percentage=" + percentage +
", speed=" + speed +
", lestTime=" + lestTime +
", len=" + len +
", fileLength=" + fileLength +
", status=" + status +
'}';
}
}
2.定义下载进度回调的接口(DownloadCallback)
public interface DownloadCallback {
/**
* 正在下载中
*
* // * @param len 当前文件大小
* // * @param fileLength 文件总大小
* // * @param remoteUrl 下载路径
* // * @param fileName 文件名
* // * @param percentage 下载的百分比(0-100)
* // * @param speed 下载速度
* // * @param lestTime 剩余时间
*/
void onDownloading(DownloadInfoModel downloadInfoModel);
/**
* 开始下载
*
* @param downloadInfoModel
*/
void onStartDownload(DownloadInfoModel downloadInfoModel);
/**
* 下载结束调用接口
*
* @param downloadInfoModel
*/
void onFinishDownload(DownloadInfoModel downloadInfoModel);
/**
* 下载出现错误
*
* @param downloadInfoModel
* @param e
*/
void onErrorDownload(DownloadInfoModel downloadInfoModel, Exception e);
/**
* 停止下载
*
* @param downloadInfoModel
* @param isDelete
*/
void onStopDownload(DownloadInfoModel downloadInfoModel, boolean isDelete);
/**
* 如果已经有文件
*
* @param downloadInfoModel
*/
void hasExists(DownloadInfoModel downloadInfoModel);
/**
* 删除单条信息
*
* @param model
*/
void onRemoveDownload(DownloadInfoModel model);
}
3.添加一个时间工具类(DownloadTimeUtils),处理剩余时间的显示
public class DownloadTimeUtils {
public static long SECOND_TIME = 1 * 1000;
public static long MIN_TIME = 60 * 1000;
public static long HOUR_TIME = 60 * 60 * 1000;
public static long DAY_TIME = 24 * 60 * 60 * 1000;
public static String getStringByLongTime(long time) {
String timeString = "";
DecimalFormat df = new DecimalFormat("#00");
if (time != 0) {
if (time > 0 && time < SECOND_TIME) {
timeString = "00:00:01";
} else if (time >= SECOND_TIME && time < MIN_TIME) {
//一分钟内,大于一秒钟
timeString = "00:00:" + df.format(time / SECOND_TIME);
} else if (time >= MIN_TIME && time < HOUR_TIME) {
//一小时内,大于一分钟
int min = 0;
int second = 0;
min = (int) (time / MIN_TIME);
second = (int) (time % MIN_TIME) / 1000;
timeString = "00:" + df.format(min) + ":" + df.format(second);
} else if (time >= HOUR_TIME && time < DAY_TIME) {
//一天内,大于一小时
int min = 0;
int hour = 0;
int second = 0;
hour = (int) (time / HOUR_TIME);
min = (int) ((time % HOUR_TIME) / MIN_TIME);
second = (int) (time % MIN_TIME) / 1000;
timeString = df.format(hour) + ":" + df.format(min) + ":" + df.format(second);
} else {
timeString = "大于一天";
}
}
return timeString;
}
}
4.编写一个文件下载的工具类(DownSingleRunnable),实现Runnable接口,用于加入线程池。
public class DownSingleRunnable implements Runnable {
private final static String TAG = "DownSingleRunnable";
public static final int START_MSG = 0;
public static final int DOWNLOADING_MSG = 1;
public static final int FINISH_DOWNLOAD_MSG = 2;
public static final int ERROR_DOWNLOAD_MSG = 3;
public static final int EXISTS_DOWNLOAD_MSG = 4;
public static final int STOP_DOWNLOAD_MSG = 5;
public static final int REMOVE_DOWNLOAD_MSG = 6;
private DownloadInfoModel model;
private long countTime = 1000;//定时计算速度
private DownloadCallback downloadCallback;
private boolean isDownloading = false;//是否在下载中
private boolean isStop = false;//是否是暂停结束的
private boolean isDelete = false;
public DownSingleRunnable(DownloadInfoModel model) {
this.model = model;
this.downloadCallback = model.getDownloadCallback();
}
public DownloadInfoModel getModel() {
return model;
}
public void setModel(DownloadInfoModel model) {
this.model = model;
}
/**
* 停止下载
*
* @param isDelete
*/
public void stopDownload(boolean isDelete) {
Log.e(TAG, "stopDownload " + model.getDownloadId());
isDownloading = false;
isStop = true;
this.isDelete = isDelete;
handler.sendEmptyMessage(STOP_DOWNLOAD_MSG);
}
/**
* 从头文件得到下载内容的大小
*
* @param urlConnection
* @return
*/
private long getContentLengthFromHeader(URLConnection urlConnection) {
List values = urlConnection.getHeaderFields().get("content-Length");
if (values != null && !values.isEmpty()) {
String sLength = (String) values.get(0);
if (sLength != null) {
return Long.parseLong(sLength, 10);
}
}
return -1;
}
@Override
public void run() {
if (model.getStatus() == DOWNLOAD_REMOVE_STATUS) {
Log.e(TAG, "已经移除了任务 " + model.getDownloadId());
return;
}
Log.e(TAG, "" + model.getDownloadId());
File file = null;
BufferedInputStream bin = null;
OutputStream out = null;
try {
//建立连接
HttpURLConnection httpURLConnection = null;
URL url = new URL(model.getRemoteUrl());
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestProperty("Charset", "UTF-8");// 设置字符编码
httpURLConnection.setRequestProperty("Accept-Encoding", "identity");
httpURLConnection.setReadTimeout(1000);//断网操作
httpURLConnection.connect();
long fileLength = httpURLConnection.getContentLength();
if (fileLength == -1) {
//大文件下载机制
fileLength = getContentLengthFromHeader(httpURLConnection);
}
String filePathUrl = httpURLConnection.getURL().getFile();
String fileFullName = model.getFileName() + "." + model.getFileExtname();
bin = new BufferedInputStream(httpURLConnection.getInputStream());
String path = model.getSavePath() + File.separatorChar + fileFullName;
file = new File(path);
//没文件夹就创建文件夹
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
//如果文件已经存在
if (file.exists()) {
handler.sendEmptyMessage(EXISTS_DOWNLOAD_MSG);
model.setFileName(model.getFileName() + "(1)");
fileFullName = model.getFileName() + "." + model.getFileExtname();
path = model.getSavePath() + File.separatorChar + fileFullName;
file = new File(path);
}
Log.d(TAG, "filePathUrl = " + filePathUrl + " fileFullName = " + fileFullName
+ " path = " + path + " fileLength = " + fileLength);
out = new FileOutputStream(file);
long size = 0;
long len = 0;
byte[] buf = new byte[1024];
long lastTime = System.currentTimeMillis();//上一次计算速度的时间
long lastLen = 0;//上一次的长度,用来计算速度
long speed = 0;//速度
long lestTime = 0;//剩余时间ms
handler.sendEmptyMessage(START_MSG);
isDownloading = true;
while ((size = bin.read(buf)) != -1) {
if (!isDownloading) {
break;
}
len += size;
long nowTime = System.currentTimeMillis();//获取当前时间
if (lastLen == 0) {
lastLen = len;
}
//每隔countTime时间以上才进行速度计算
if ((nowTime - lastTime) >= countTime) {
speed = (len - lastLen) * 1000 / (nowTime - lastTime);
lestTime = (fileLength - len) * 1000 / speed;
lastTime = nowTime;
lastLen = len;
model.setLen(len).setFileLength(fileLength).setPercentage((int) (len * 100 / fileLength))
.setSpeed(speed).setLestTime(lestTime);
handler.sendEmptyMessage(DOWNLOADING_MSG);
}
out.write(buf, 0, (int) size);
}
Log.e(TAG, "finish " + model.getDownloadId());
//下载完成
if (!isStop) {
handler.sendEmptyMessage(FINISH_DOWNLOAD_MSG);
}
} catch (Exception e) {
//断网会进入当前错误
Message message = new Message();
message.what = ERROR_DOWNLOAD_MSG;
message.obj = e;
handler.sendMessage(message);
} finally {
Log.e(TAG, "finally ");
try {
if (bin != null) {
bin.close();
}
if (out != null) {
out.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case START_MSG:
if (null != downloadCallback) {
model.setStatus(DownloadInfoModel.DOWNLOADING_STATUS);
downloadCallback.onStartDownload(model);
}
break;
case DOWNLOADING_MSG:
if (null != downloadCallback) {
downloadCallback.onDownloading(model);
}
break;
case FINISH_DOWNLOAD_MSG:
if (null != downloadCallback) {
model.setStatus(DownloadInfoModel.DOWNLOAD_SUCCESS_STATUS);
downloadCallback.onFinishDownload(model);
}
break;
case ERROR_DOWNLOAD_MSG:
if (null != downloadCallback) {
model.setStatus(DownloadInfoModel.DOWNLOAD_FAILD_STATUS);
downloadCallback.onErrorDownload(model, (Exception) msg.obj);
}
break;
case EXISTS_DOWNLOAD_MSG:
if (null != downloadCallback) {
downloadCallback.hasExists(model);
}
break;
case STOP_DOWNLOAD_MSG:
if (null != downloadCallback) {
downloadCallback.onStopDownload(model, isDelete);
}
break;
case REMOVE_DOWNLOAD_MSG:
if (null != downloadCallback) {
model.setStatus(DOWNLOAD_REMOVE_STATUS);
downloadCallback.onRemoveDownload(model);
}
break;
default:
break;
}
}
};
/**
* 清除单个列表
*/
public void removeList() {
handler.sendEmptyMessage(REMOVE_DOWNLOAD_MSG);
}
}
5.编写一个操作工具类(DownloadThreadHelper),用来处理多任务下载,内部使用的是线程池(newFixedThreadPool),可在外部设置同时下载数量,通过改变线程池大小。
public class DownloadThreadHelper {
private int maxDownloadNum = 1;//最大同时下载数
private ArrayList downloadInfoModels;
private ArrayList downSingleRunnables;
ExecutorService executorService;
public int getMaxDownloadNum() {
return maxDownloadNum;
}
public DownloadThreadHelper setMaxDownloadNum(int maxDownloadNum) {
this.maxDownloadNum = maxDownloadNum;
return this;
}
public ArrayList getDownloadInfoModels() {
return downloadInfoModels;
}
public DownloadThreadHelper setDownloadInfoModels(ArrayList downloadInfoModels) {
this.downloadInfoModels = downloadInfoModels;
return this;
}
/**
* 开启线程池下载文件
*/
public void startDownloadFiles() {
executorService = Executors.newFixedThreadPool(maxDownloadNum);
downSingleRunnables = new ArrayList<>();
for (DownloadInfoModel model : downloadInfoModels) {
DownSingleRunnable downSingleRunnable = new DownSingleRunnable(model);
downSingleRunnables.add(downSingleRunnable);
}
for (DownSingleRunnable runnable : downSingleRunnables) {
executorService.execute(runnable);
}
executorService.shutdown();//不让其他线程再加进来
}
/**
* 停止单个下载
*
* @param downloadId
*/
public void stopDownloadSingleFile(String downloadId) {
if (isEmpty()) {
return;
}
for (DownSingleRunnable runnable : downSingleRunnables) {
if (runnable.getModel().getDownloadId().equals(downloadId)) {
runnable.stopDownload(true);
break;
}
}
}
/**
* 停止全部下载
*/
public void stopAllFile() {
if (isEmpty()) {
return;
}
for (DownSingleRunnable runnable : downSingleRunnables) {
runnable.stopDownload(false);
}
}
/**
* 删除在列表,改状态
*
* @param downloadId
*/
public void removeList(String downloadId) {
if (isEmpty()) {
return;
}
for (DownSingleRunnable runnable : downSingleRunnables) {
if (runnable.getModel().getDownloadId().equals(downloadId)) {
runnable.removeList();
break;
}
}
}
/**
* 判断线程池是否为空
*
* @return
*/
private boolean isEmpty() {
if (null == downSingleRunnables) {
return true;
}
if (downSingleRunnables.size() <= 1) {
return true;
}
return false;
}
}
上面就是全部的代码库封装,使用的方式如下:
DownloadThreadHelper downloadThreadHelper = new DownloadThreadHelper();
ArrayList downloadInfoModels = new ArrayList<>();
DownloadInfoModel model = new DownloadInfoModel();
//设置下载的id
model.setDownloadId(downloadId);
//设置服务器文件的路径
model.setRemoteUrl(remoteUrl);
//设置存储路径
model.setSavePath(savePath);
//设置文件名,不带扩展名
model.setFileName(fileName);
//设置文件扩展名
model.setFileExtname(fileExtname);
//设置监听器
model.setDownloadCallback(new DownloadCallback() {
@Override
public void onDownloading(DownloadInfoModel downloadInfoModel) {
}
@Override
public void onStartDownload(DownloadInfoModel downloadInfoModel) {
}
@Override
public void onFinishDownload(DownloadInfoModel downloadInfoModel) {
}
@Override
public void onErrorDownload(DownloadInfoModel downloadInfoModel, Exception e) {
}
@Override
public void onStopDownload(DownloadInfoModel downloadInfoModel, boolean isDelete) {
}
@Override
public void hasExists(DownloadInfoModel downloadInfoModel) {
}
@Override
public void onRemoveDownload(DownloadInfoModel model) {
}
});
//添加一个下载任务,如果有多个就add多个任务进去
downloadInfoModels.add(model);
//将全部任务添加进去
downloadThreadHelper.setDownloadInfoModels(downloadInfoModels);
//开始下载
downloadThreadHelper.startDownloadFiles();
//停止所有下载任务
downloadThreadHelper.stopAllFile();
//停止指定任务
downloadThreadHelper.stopDownloadSingleFile(downloadId);
该库可以实时更新下载进度,速度,下载状态,剩余时间预计。暂时没去弄断点续传,后续有时间的话再加上去。