安卓多任務(wù)庫下載使用了HttpURLConnection進行網(wǎng)絡(luò)傳輸根资,使用了多線程進行多任務(wù)管理⊥剑可以實時更新下載進度玄帕,速度,下載狀態(tài)想邦,剩余時間預(yù)計裤纹。
1.先定義下載數(shù)據(jù)以及狀態(tài)的model(DownloadInfoModel)
public class DownloadInfoModel {
public static final int DOWNLOADING_STATUS = 0;//下載狀態(tài)
public static final int WAIT_STATUS = 1;//等待狀態(tài)
public static final int DOWNLOAD_FAILD_STATUS = 2;//下載失敗狀態(tài)
public static final int DOWNLOAD_SUCCESS_STATUS = 3;//下載成功狀態(tài)
public static final int DOWNLOAD_REMOVE_STATUS = 4;//刪除該條下載
private String downloadId = "";//下載任務(wù)的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;//下載狀態(tài)
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.定義下載進度回調(diào)的接口(DownloadCallback)
public interface DownloadCallback {
/**
* 正在下載中
* <p>
* // * @param len 當(dāng)前文件大小
* // * @param fileLength 文件總大小
* // * @param remoteUrl 下載路徑
* // * @param fileName 文件名
* // * @param percentage 下載的百分比(0-100)
* // * @param speed 下載速度
* // * @param lestTime 剩余時間
*/
void onDownloading(DownloadInfoModel downloadInfoModel);
/**
* 開始下載
*
* @param downloadInfoModel
*/
void onStartDownload(DownloadInfoModel downloadInfoModel);
/**
* 下載結(jié)束調(diào)用接口
*
* @param downloadInfoModel
*/
void onFinishDownload(DownloadInfoModel downloadInfoModel);
/**
* 下載出現(xiàn)錯誤
*
* @param downloadInfoModel
* @param e
*/
void onErrorDownload(DownloadInfoModel downloadInfoModel, Exception e);
/**
* 停止下載
*
* @param downloadInfoModel
* @param isDelete
*/
void onStopDownload(DownloadInfoModel downloadInfoModel, boolean isDelete);
/**
* 如果已經(jīng)有文件
*
* @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) {
//一分鐘內(nèi)丧没,大于一秒鐘
timeString = "00:00:" + df.format(time / SECOND_TIME);
} else if (time >= MIN_TIME && time < HOUR_TIME) {
//一小時內(nèi)鹰椒,大于一分鐘
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) {
//一天內(nèi),大于一小時
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)呕童,實現(xiàn)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;//是否是暫停結(jié)束的
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);
}
/**
* 從頭文件得到下載內(nèi)容的大小
*
* @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, "已經(jīng)移除了任務(wù) " + 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");// 設(shè)置字符編碼
httpURLConnection.setRequestProperty("Accept-Encoding", "identity");
httpURLConnection.setReadTimeout(1000);//斷網(wǎng)操作
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);
//沒文件夾就創(chuàng)建文件夾
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
//如果文件已經(jīng)存在
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();//獲取當(dāng)前時間
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) {
//斷網(wǎng)會進入當(dāng)前錯誤
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)奸汇,用來處理多任務(wù)下載施符,內(nèi)部使用的是線程池(newFixedThreadPool),可在外部設(shè)置同時下載數(shù)量擂找,通過改變線程池大小戳吝。
public class DownloadThreadHelper {
private int maxDownloadNum = 1;//最大同時下載數(shù)
private ArrayList<DownloadInfoModel> downloadInfoModels;
private ArrayList<DownSingleRunnable> downSingleRunnables;
ExecutorService executorService;
public int getMaxDownloadNum() {
return maxDownloadNum;
}
public DownloadThreadHelper setMaxDownloadNum(int maxDownloadNum) {
this.maxDownloadNum = maxDownloadNum;
return this;
}
public ArrayList<DownloadInfoModel> getDownloadInfoModels() {
return downloadInfoModels;
}
public DownloadThreadHelper setDownloadInfoModels(ArrayList<DownloadInfoModel> 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);
}
}
/**
* 刪除在列表,改狀態(tài)
*
* @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<DownloadInfoModel> downloadInfoModels = new ArrayList<>();
DownloadInfoModel model = new DownloadInfoModel();
//設(shè)置下載的id
model.setDownloadId(downloadId);
//設(shè)置服務(wù)器文件的路徑
model.setRemoteUrl(remoteUrl);
//設(shè)置存儲路徑
model.setSavePath(savePath);
//設(shè)置文件名听哭,不帶擴展名
model.setFileName(fileName);
//設(shè)置文件擴展名
model.setFileExtname(fileExtname);
//設(shè)置監(jiān)聽器
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) {
}
});
//添加一個下載任務(wù),如果有多個就add多個任務(wù)進去
downloadInfoModels.add(model);
//將全部任務(wù)添加進去
downloadThreadHelper.setDownloadInfoModels(downloadInfoModels);
//開始下載
downloadThreadHelper.startDownloadFiles();
//停止所有下載任務(wù)
downloadThreadHelper.stopAllFile();
//停止指定任務(wù)
downloadThreadHelper.stopDownloadSingleFile(downloadId);
該庫可以實時更新下載進度塘雳,速度欢唾,下載狀態(tài),剩余時間預(yù)計粉捻。暫時沒去弄斷點續(xù)傳礁遣,后續(xù)有時間的話再加上去。