多任务下载在android app中很多应用场景,比如应用市场下载app,图书下载、音乐下载、电影下载等资源类型下载。
一、什么是多任务下载框架
多任务框架就是指,同一时间内,支持多个资源下载。支持下载队列、进度更新、下载取消暂停等。
包括:网络下载请求,下载任务执行、下载任务调度、UI进度更新、任务状态变化、文件的存储。
二、框架流程
三、框架代码:
下面着重分析下DownloadTask和TaskDispatcher部分代码:
四、任劳任怨的下载器--DownloadTask
DownloadTask实现了Runnable接口,定义了任务状态和网络service。具体参考代码:
/**
*
*
* HappyBaby
*
* 作者:Jacky.Ao on 2018/2/23 17:08
*
* 邮箱: [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 parameterList;
//网络下载请求对象
private Call downloadCall;
//网络上传请求对象
private Call 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 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() {
@Override
public void onResponse(Call call, Response 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 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() {
@Override
public void onResponse(Call call, Response 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 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主要实现下载任务的调度,具体参考代码:
/**
*
*
* HappyBaby
*
* 作者:Jacky.Ao on 2018/2/24 15:55
*
* 邮箱: [email protected]
*/
public class TaskDispatcher {
//最大下载任务数量
private static final int DOWNLOAD_MAX = 3;
//下载任务线程池
private ExecutorService executorService;
//正在下载的任务队列
private List queueTaskList = Collections.synchronizedList(new ArrayList<>());
//已经完成下载任务队列
private List downloadedList = Collections.synchronizedList(new ArrayList<>());
//上传任务队列
private List 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(),
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 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 it = uploadList.iterator(); it.hasNext(); ) {
DownloadTask task = it.next();
if (task.getState() == DownloadTask.STATE.FAILED) {
getExecutorService().execute(task);
}
}
}
}
/**
* 调度上传任务
*/
private synchronized void promoteSyncUploadTask() {
for (Iterator 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 it = queueTaskList.iterator(); it.hasNext();) {
DownloadTask task = it.next();
if (task.getState() == DownloadTask.STATE.PENDING) {
getExecutorService().execute(task);
return;
}
}
}
public List getQueueTaskList() {
return queueTaskList;
}
public List getDownloadedList() {
return downloadedList;
}
public List 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 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);
七、效果图
原创不易,如果您觉得好,可以分享此公众号给你更多的人。