1. 程式人生 > >手把手教你打造Android多工下載框架

手把手教你打造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);

七、效果圖


原創不易,如果您覺得好,可以分享此公眾號給你更多的人。