手把手教你打造Android多工下載框架
多工下載在android app中很多應用場景,比如應用市場下載app,圖書下載、音樂下載、電影下載等資源型別下載。
一、什麼是多工下載框架
多工框架就是指,同一時間內,支援多個資源下載。支援下載佇列、進度更新、下載取消暫停等。
包括:網路下載請求,下載任務執行、下載任務排程、UI進度更新、任務狀態變化、檔案的儲存。
二、框架流程
三、框架程式碼:
下面著重分析下DownloadTask和TaskDispatcher部分程式碼:
四、任勞任怨的下載器--DownloadTask
DownloadTask實現了Runnable介面,定義了任務狀態和網路service。具體參考程式碼:
/** * <The trouble with the world is that the stupid are sure and the intelligent are full of doubt.> * <p> * HappyBaby * <p> * 作者:Jacky.Ao on 2018/2/23 17:08 * <p> * 郵箱:
[email protected] */ public class DownloadTask implements Runnable, ProgressListener { //更新任務進度訊息 private static final int UPDATE_PROGRESS_ID = 0x100; //下載網路服務 private APIService.DownloadApiService downloadApiService; //上傳網路服務 private APIService.UploadApiService uploadApiService; //下載任務狀態 private STATE state; //下載實體類,使用object基類,方便統一獲取 private Object downloadObject; //網路服務請求引數列表 private List<RequestParameter> parameterList; //網路下載請求物件 private Call<File> downloadCall; //網路上傳請求物件 private Call<BaseEntity> uploadCall; //下載儲存檔案物件 private File downloadFile = null; //下載任務進度監聽器 private OnProgressListener onProgressListener; private DownloadTask mySelf; //是否是下載,區分當前任務是下載還是上傳 private boolean isDownload; @Override public void run() { start(); } @Override public void onProgress(long addedBytes, long contentLenght, boolean done) { sendUpdateProgressMessage(addedBytes, contentLenght, false); } public enum STATE { IDLE, PENDING, LOADING, FAILED, FINISHED, UNKNOWN, } private void sendUpdateProgressMessage(long addedBytes, long contentLenght, boolean done) { Message message = handler.obtainMessage(UPDATE_PROGRESS_ID); message.obj = done; message.arg1 = (int) addedBytes; message.arg2 = (int) contentLenght; handler.sendMessage(message); } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == UPDATE_PROGRESS_ID) { if (onProgressListener != null) { onProgressListener.onProgress(msg.arg1, msg.arg2, (Boolean) msg.obj); } } } }; public DownloadTask(final Object object, List<RequestParameter> list, final boolean download) { downloadObject = object; parameterList = list; isDownload = download; if (isDownload) { downloadApiService = HttpApiService.getDownloadApiService(this); } else { uploadApiService = HttpApiService.getUploadApiService(this); } state = STATE.IDLE; mySelf = this; } public void start() { if (state == STATE.LOADING) { return; } state = STATE.LOADING; if (isDownload) { download(); } else { upload(); } } private void download() { if (parameterList != null && parameterList.size() > 1 && downloadApiService != null) { //change state pending or idle to loading, notify ui to update. sendUpdateProgressMessage(0, 0, false); String downloadFilename = parameterList.get(0).getValue(); String saveFilename = parameterList.get(1).getValue(); downloadCall = downloadApiService.httpDownloadFile(downloadFilename, saveFilename); downloadCall.enqueue(new Callback<File>() { @Override public void onResponse(Call<File> call, Response<File> response) { Log.i(response.toString()); if (response.code() == 200) { mySelf.downloadFile = response.body(); if (mySelf.downloadFile != null && !mySelf.downloadFile.getPath().endsWith(".tmp")) { sendUpdateProgressMessage(100, 100, true); mySelf.state = STATE.FINISHED; TaskDispatcher.getInstance().finished(mySelf); } else { mySelf.state = STATE.FAILED; sendUpdateProgressMessage(0, 0, false); } } } @Override public void onFailure(Call<File> call, Throwable t) { mySelf.state = STATE.FAILED; sendUpdateProgressMessage(0, 0, false); } }); } } private void upload() { if (parameterList != null && parameterList.size() > 1 && uploadApiService != null) { File file = new File(parameterList.get(0).getValue()); String uid = parameterList.get(1).getValue(); RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file); MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile); uploadCall = uploadApiService.upLoad(uid, body); // "34" uploadCall.enqueue(new Callback<BaseEntity>() { @Override public void onResponse(Call<BaseEntity> call, Response<BaseEntity> response) { Log.i(response.body().toString()); if (response.code() == 200 && response.body().status.equals("2000")) { sendUpdateProgressMessage(100, 100, true); mySelf.state = STATE.FINISHED; TaskDispatcher.getInstance().finished(mySelf); } else { mySelf.state = STATE.FAILED; sendUpdateProgressMessage(0, 0, false); } } @Override public void onFailure(Call<BaseEntity> call, Throwable t) { Log.e(t.getMessage()); } }); } } public void cancel() { if (downloadCall != null) { downloadCall.cancel(); } handler.removeMessages(UPDATE_PROGRESS_ID); } public void setState(final STATE state) { this.state = state; } public STATE getState() { return state; } public Object getDownloadObject() { return downloadObject; } public void setDownloadObject(Object downloadObject) { this.downloadObject = downloadObject; } public File getDownloadFile() { return downloadFile; } public boolean isDownload() { return isDownload; } public void setOnProgressListener(final OnProgressListener listener) { onProgressListener = listener; } public interface OnProgressListener { void onProgress(long addedBytes, long contentLenght, boolean done); } }
上面省略了一些無關程式碼,程式碼寫的還是比較簡潔,很容易讀懂。建構函式中例項了下載服務,並註冊了進度更新的監聽器,監聽器同handler訊息更新UI,方便在當前執行緒更新UI。具體下載參考downloadApiService.httpDownloadFile介面,返回一個Call,下載完成通過OnResponse把儲存的檔案物件回傳回來,儲存在downloadFile物件中並修改任務狀態為Finished。五、忠於職守的巡查官--任務排程器
TaskDispatcher主要實現下載任務的排程,具體參考程式碼:
/**
* <The trouble with the world is that the stupid are sure and the intelligent are full of doubt.>
* <p>
* HappyBaby
* <p>
* 作者:Jacky.Ao on 2018/2/24 15:55
* <p>
* 郵箱: [email protected]
*/
public class TaskDispatcher {
//最大下載任務數量
private static final int DOWNLOAD_MAX = 3;
//下載任務執行緒池
private ExecutorService executorService;
//正在下載的任務佇列
private List<DownloadTask> queueTaskList = Collections.synchronizedList(new ArrayList<>());
//已經完成下載任務佇列
private List<DownloadTask> downloadedList = Collections.synchronizedList(new ArrayList<>());
//上傳任務佇列
private List<DownloadTask> uploadList = Collections.synchronizedList(new ArrayList<>());
//單例物件
private static TaskDispatcher instance;
//任務是否中斷
private boolean taskAbort;
private TaskDispatcher() {
}
/**
*執行緒安全單例模式
*/
public static TaskDispatcher getInstance() {
if (instance == null) {
synchronized (TaskDispatcher.class) {
if (instance == null) {
instance = new TaskDispatcher();
}
}
}
return instance;
}
/**
* 初始化執行緒池
*/
private ExecutorService getExecutorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
threadFactory("happybaby download dispatcher", false));
}
return executorService;
}
/**
* 任務入列下載
*/
public synchronized boolean enqueue(DownloadTask task) {
if (queueTaskList.contains(task) || uploadList.contains(task)) {
return false;
}
if (task != null && task.isDownload()) {
if (queueTaskList.size() < DOWNLOAD_MAX) {
queueTaskList.add(task);
getExecutorService().execute(task);
} else {
task.setState(DownloadTask.STATE.PENDING);
queueTaskList.add(task);
}
return true;
} else {
if (uploadList.size() < DOWNLOAD_MAX) {
uploadList.add(task);
getExecutorService().execute(task);
} else {
task.setState(DownloadTask.STATE.PENDING);
uploadList.add(task);
}
return true;
}
}
/**
* 任務下載完成
*/
public synchronized void finished(DownloadTask task) {
if (task != null && task.getState() == DownloadTask.STATE.FINISHED) {
if (task.isDownload()) {
if (queueTaskList.remove(task)) {
downloadedList.add(task);
promoteSyncTask();
}
} else {
uploadList.remove(task);
promoteSyncUploadTask();
}
}
}
/**
* 刪除下載任務,是否刪除檔案
*/
public synchronized void deleteTask(DownloadTask task, boolean isDeleteFile) {
if (task != null) {
if (task.getState() != DownloadTask.STATE.FINISHED) {
if (task.isDownload()) {
queueTaskList.remove(task);
if (task.getState() == DownloadTask.STATE.LOADING) {
task.cancel();
}
promoteSyncTask();
} else {
uploadList.remove(task);
if (task.getState() == DownloadTask.STATE.LOADING) {
task.cancel();
}
promoteSyncUploadTask();
}
return;
}
downloadedList.remove(task);
if (isDeleteFile) {
task.getDownloadFile().delete();
}
}
}
/**
*失敗任務重新下載
*/
public synchronized void promoteSyncFailedTask() {
if (taskAbort && queueTaskList.size() > 0) {
for (Iterator<DownloadTask> it = queueTaskList.iterator(); it.hasNext(); ) {
DownloadTask task = it.next();
if (task.getState() == DownloadTask.STATE.FAILED) {
getExecutorService().execute(task);
}
}
}
if (taskAbort && uploadList.size() > 0) {
for (Iterator<DownloadTask> it = uploadList.iterator(); it.hasNext(); ) {
DownloadTask task = it.next();
if (task.getState() == DownloadTask.STATE.FAILED) {
getExecutorService().execute(task);
}
}
}
}
/**
* 排程上傳任務
*/
private synchronized void promoteSyncUploadTask() {
for (Iterator<DownloadTask> it = uploadList.iterator(); it.hasNext();) {
DownloadTask task = it.next();
if (task.getState() == DownloadTask.STATE.PENDING) {
getExecutorService().execute(task);
return;
}
}
}
/**
* 排程pending狀態的任務,開始下載
*/
private synchronized void promoteSyncTask() {
for (Iterator<DownloadTask> it = queueTaskList.iterator(); it.hasNext();) {
DownloadTask task = it.next();
if (task.getState() == DownloadTask.STATE.PENDING) {
getExecutorService().execute(task);
return;
}
}
}
public List<DownloadTask> getQueueTaskList() {
return queueTaskList;
}
public List<DownloadTask> getDownloadedList() {
return downloadedList;
}
public List<DownloadTask> getUploadList() {
return uploadList;
}
/**
* 取消所有任務
*/
public synchronized void cancelAll() {
for (DownloadTask task : queueTaskList) {
if (task.getState() == DownloadTask.STATE.LOADING) {
task.cancel();
}
}
for (DownloadTask task : uploadList) {
if (task.getState() == DownloadTask.STATE.LOADING) {
task.cancel();
}
}
}
public void setTaskAbort(boolean taskAbort) {
this.taskAbort = taskAbort;
}
private ThreadFactory threadFactory(final String name, final boolean daemon) {
return new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r, name);
thread.setDaemon(daemon);
return thread;
}
};
}
}
上面是TaskDispatcher部分程式碼,重點看下enqueue函式,該函式是個入列操作,根據傳入的task, 加入下載佇列,如果當前下載佇列超出最大任務數量,將任務狀態修改為pending狀態,等待任務 完成即finished介面,將當前下載完成的task新增到下載完成佇列,並呼叫promoteSyncTask介面, 檢查是否有pending的任務,如果有則開始任務。
六、測試示例:
RequestParameter parameter = new RequestParameter("name", fileName);
RequestParameter parameter1 = new RequestParameter("savename", filePath + fileName);
List<RequestParameter> parameterList = new ArrayList<>();
parameterList.add(parameter);
parameterList.add(parameter1);
DownloadTask downloadTask = new DownloadTask(musicEntity, parameterList,true);
downloadTask.setDownloadObject(musicEntity);
downloadTask.setOnProgressListener(new DownloadTask.OnProgressListener() {
@Override
public void onProgress(long addedBytes, long contentLenght, boolean done) {
if (contentLenght > 0) {
holder.progress.setProgress((int) (1.0f * addedBytes / contentLenght * 100));
}
if (done) {
Log.i(entity.getName() + " finished...");
notifyItemRemoved(position);
}
}
});
TaskDispatcher.getInstance().enqueue(downloadTask);
七、效果圖
原創不易,如果您覺得好,可以分享此公眾號給你更多的人。