安卓下载任务管理
前言:上年开发了一个壁纸,音乐,应用,视频等资源浏览和下载安卓应用,准备分解功能模块做下笔记。下载页面UI设计参照 网易云音乐
下载功能
- 多任务并行下载
- 断点续传(需服务器支持)
项目地址:https://github.com/4ndroidev/DownloadManager.git
效果图
实现原理
下载任务流程图
由上图可知,任务执行流程大致如下
- 创建任务,并做准备,设置监听器等操作
- 根据任务创建实际下载工作,添加到任务队列,等待或直接执行
- 用户操作,进行暂停,恢复,或删除
核心类分析
类 | 功能 |
---|---|
DownloadTask | 下载任务,保存部分关键信息,非实际下载工作 |
DownloadInfo | 下载信息,保存所有信息 |
DownloadJob | 实现Runnable接口,实际下载工作,负责网络请求,数据库信息更新 |
DownloadManager | 单例,创建下载任务,提供获取正在下载任务,所有下载信息,设置监听器等接口 |
DownloadEngine | 负责创建线程池,根据任务创建下载工作,调度工作及通知 |
DownloadProvider | 负责下载信息数据库增删查改 |
类关联关系
关联 | 关系 |
---|---|
DownloadTask - DownloadInfo | n - 1 |
DownloadTask - DownloadJob | n - 0…1 |
DownloadJob - DownloadInfo | 1 - 1 |
下载工作
断点续传的关键点:
- 使用Range这个Header来指定开始下载位置
- 文件读写则使用RandomAccessFile,可在指定偏移量读写文件
- 注意RandomAccessFile打开模式不要加入
s
,同步模式会拖慢下载速度123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214package com.grocery.download.library;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.HttpURLConnection;import java.net.URL;import java.util.ArrayList;import java.util.List;import static com.grocery.download.library.DownloadState.STATE_FAILED;import static com.grocery.download.library.DownloadState.STATE_FINISHED;import static com.grocery.download.library.DownloadState.STATE_PAUSED;import static com.grocery.download.library.DownloadState.STATE_RUNNING;import static com.grocery.download.library.DownloadState.STATE_WAITING;/*** Created by 4ndroidev on 16/10/6.*/// one-to-one association with DownloadInfopublic class DownloadJob implements Runnable {private boolean isPaused;private DownloadInfo info;private DownloadEngine engine;private List<DownloadListener> listeners;private Runnable changeState = new Runnable() {public void run() {synchronized (DownloadJob.class) {for (DownloadListener listener : listeners) {listener.onStateChanged(info.key, DownloadJob.this.info.state);}switch (info.state) {case STATE_RUNNING:engine.onJobStarted(info);break;case STATE_FINISHED:engine.onJobCompleted(true, info);clear();break;case STATE_FAILED:case STATE_PAUSED:engine.onJobCompleted(false, info);break;}}}};private Runnable changeProgress = new Runnable() {public void run() {synchronized (DownloadJob.class) {for (DownloadListener listener : listeners) {listener.onProgressChanged(info.key, DownloadJob.this.info.finishedLength, DownloadJob.this.info.contentLength);}}}};public DownloadJob(DownloadEngine engine, DownloadInfo info) {this.engine = engine;this.info = info;this.listeners = new ArrayList<>();}DownloadInfo getInfo() {return info;}void addListener(DownloadListener listener) {synchronized (DownloadJob.class) {if (listener == null || listeners.contains(listener)) return;listener.onStateChanged(info.key, info.state);listeners.add(listener);}}void removeListener(DownloadListener listener) {synchronized (DownloadJob.class) {if (listener == null || !listeners.contains(listener)) return;listeners.remove(listener);}}boolean isRunning() {return STATE_RUNNING == info.state;}void enqueue() {resume();}void pause() {isPaused = true;if (info.state != STATE_WAITING) return;onStateChanged(STATE_PAUSED, false);}void resume() {if (isRunning()) return;onStateChanged(STATE_WAITING, false);isPaused = false;engine.executor.submit(this);}private void clear() {listeners.clear();engine = null;info = null;}private void onStateChanged(int state, boolean updateDb) {info.state = state;if (updateDb) engine.provider.update(info);engine.handler.removeCallbacks(changeState);engine.handler.post(changeState);}private void onProgressChanged(long finishedLength, long contentLength) {info.finishedLength = finishedLength;info.contentLength = contentLength;engine.handler.removeCallbacks(changeProgress);engine.handler.post(changeProgress);}private boolean prepare() {if (isPaused) {onStateChanged(STATE_PAUSED, false);if (!engine.provider.exists(info)) {engine.provider.insert(info);} else {engine.provider.update(info);}return false;} else {onStateChanged(STATE_RUNNING, false);onProgressChanged(info.finishedLength, info.contentLength);if (engine.interceptors != null) {for (DownloadManager.Interceptor interceptor : engine.interceptors) {interceptor.updateDownloadInfo(info);}}if (!engine.provider.exists(info)) {engine.provider.insert(info);}return true;}}public void run() {if (!prepare()) return;long finishedLength = info.finishedLength;long contentLength = info.contentLength;HttpURLConnection connection = null;InputStream inputStream = null;RandomAccessFile randomAccessFile = null;try {connection = (HttpURLConnection) new URL(info.url).openConnection();connection.setAllowUserInteraction(true);connection.setConnectTimeout(5000);connection.setReadTimeout(5000);connection.setRequestMethod("GET");if (finishedLength != 0 && contentLength > 0) {connection.setRequestProperty("Range", "bytes=" + finishedLength + "-" + contentLength);} else {contentLength = connection.getContentLength();}int responseCode = connection.getResponseCode();if (contentLength > 0 && (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL)) {inputStream = connection.getInputStream();File file = new File(info.path);randomAccessFile = new RandomAccessFile(file, "rw");randomAccessFile.seek(finishedLength);byte[] buffer = new byte[20480];int len;long bytesRead = finishedLength;while (!this.isPaused && (len = inputStream.read(buffer)) != -1) {randomAccessFile.write(buffer, 0, len);bytesRead += len;finishedLength = bytesRead;onProgressChanged(finishedLength, contentLength);}connection.disconnect();if (this.isPaused) {onStateChanged(STATE_PAUSED, true);} else {info.finishTime = System.currentTimeMillis();onStateChanged(STATE_FINISHED, true);return;}} else {onStateChanged(STATE_FAILED, true);}} catch (final Exception e) {onStateChanged(STATE_FAILED, true);} finally {try {if (randomAccessFile != null)randomAccessFile.close();if (inputStream != null)inputStream.close();} catch (IOException e) {}if (connection != null)connection.disconnect();}}}
任务调度
|
|
使用说明
|
|