使用
Prdownload是一个Android下载库,它可以下载任意类型的文件,支持暂停和继续,支持大文件的下载,并且可以根据下载ID检查它的下载状态。PRDownload提供进度值、开始、失败、取消等任意回调。Github
- 引入依赖文件
compile 'com.mindorks.android:prdownloader:0.2.0'
- 在应用的Application中进行初始化
PRDownloader.initialize(getApplicationContext());
- 配置参数
// Enabling database for resume support even after the application is killed:
PRDownloaderConfig config = PRDownloaderConfig.newBuilder()
.setDatabaseEnabled(true)
.build();
PRDownloader.initialize(getApplicationContext(), config);
// Setting timeout globally for the download network requests:
PRDownloaderConfig config = PRDownloaderConfig.newBuilder()
.setReadTimeout(30_000)
.setConnectTimeout(30_000)
.build();
PRDownloader.initialize(getApplicationContext(), config);
- 发起请求
int downloadId = PRDownloader.download(url, dirPath, fileName)
.build()
.setOnStartOrResumeListener(new OnStartOrResumeListener() {
@Override
public void onStartOrResume() {
}
})
.setOnPauseListener(new OnPauseListener() {
@Override
public void onPause() {
}
})
.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel() {
}
})
.setOnProgressListener(new OnProgressListener() {
@Override
public void onProgress(Progress progress) {
}
})
.start(new OnDownloadListener() {
@Override
public void onDownloadComplete() {
}
@Override
public void onError(Error error) {
}
});
- 暂停
PRDownloader.pause(downloadId);
- 继续
PRDownloader.resume(downloadId);
- 取消
// Cancel with the download id
PRDownloader.cancel(downloadId);
// The tag can be set to any request and then can be used to cancel the request
PRDownloader.cancel(TAG);
// Cancel all the requests
PRDownloader.cancelAll();
- 状态获取
Status status = PRDownloader.getStatus(downloadId);
- 清除数据库中的数据
// Method to clean up temporary resumed files which is older than the given day
PRDownloader.cleanUp(days);
断点续传原理
- 总文件大小获取
URLConnection connection = new URL(request.getUrl()).openConnection();
String length = connection.getHeaderField("Content-Length");
try {
return Long.parseLong(length);
} catch (NumberFormatException e) {
return -1;
}
- 网络断点设置
final String range = String.format(Locale.ENGLISH,"bytes=%d-", downloadedBytes);
connection.addRequestProperty(Constants.RANGE, range);
- 文件断点设置
File file = new File(filePath);
RandomAccessFile randomAccess = new RandomAccessFile(file, "rw");
FileDescriptor fileDescriptor = randomAccess.getFD();
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(fileDescriptor));
randomAccess.seek(downloadedBytes);
outputStream.write(buff, 0, byteCount);
- 持续下载
InputStream inputStream = connection.getInputStream();
do {
final int byteCount = inputStream.read(buff);
if (byteCount == -1) {
break;
}
outputStream.write(buff, 0, byteCount);
//执行进度回调,更新进度值
sendProgress();
//每次循环中都要刷新缓冲,并且同步文件描述符
try {
outputStream.flush();
fileDescriptor.sync();
} catch (IOException e) {
e.printStackTrace();
}
//持久化进度值至数据库中
ComponentHolder.getInstance().getDbHelper().updateProgress(request.getDownloadId(),request.getDownloadedBytes(),System.currentTimeMillis());
//不断在循环中判断是否执行了取消或暂停操作,所以暂停或取消操作可以达到几乎无延时
if (request.getStatus() == Status.CANCELLED) {
response.setCancelled(true);
return response;
} else if (request.getStatus() == Status.PAUSED) {
response.setPaused(true);
return response;
}
} while (true);
多任务思想
//核心类,其采用单例模式,主要是提供一个执行者提供器,用于提供各种任务的执行对象,从而使各种任务的管理内聚起来
public class Core {
private static Core instance = null;
//执行者提供器
private final ExecutorSupplier executorSupplier;
... ...
//用于清除该单例
public static void shutDown() {
if (instance != null) {
instance = null;
}
}
}
- 执行者提供器的抽象接口 ExecutorSupplier
//它提供了三种类型的执行者,分别是下载执行者(多线程的线程池)、背景执行者(单线程的线程池)、主线程执行者(UI线程)
public interface ExecutorSupplier {
DownloadExecutor forDownloadTasks();
Executor forBackgroundTasks();
Executor forMainThreadTasks();
}
- 默认的执行者提供器 DefaultExecutorSupplier
public class DefaultExecutorSupplier implements ExecutorSupplier {
/**
* Runtime.getRuntime().availableProcessors() 代表当前机器的CPU数量,即多少核。
* 如果所有的任务都是计算密集型的,则创建处理器可用的核心数那么多线程就可以了,如果任务都是IO密集型的,那么我们就需要开更多的线程来提高性能。
*
* 因为对于计算密集型来说,当有多个任务处于就绪状态时,处理器核心需要在线程间频繁进行上下文切换,而这种切换对程序性能损耗较大。
* 而对于IO密集型来说,io有发送数据(output)和返回数据(input)两个过程。比如以浏览器为主体,浏览器发送请求给服务器(output),
* 服务器再将请求结果返回给浏览器(input)。在io阻塞的情况下,会释放锁,其他线程会在当前线程等待返回值(阻塞)的情况下继续执行发送请求(output),
* 第三个线程又会在第二个线程等待返回值(阻塞)的情况下发送请求(output),即在同一时间片段,会有一个线程在等待数据,也会有一个线程在发数据,这就减少了io传输的时间。
*
* 如果任务有50%的时间处于阻塞状态,则程序所需线程数为处理器可用核心数的两倍。
* 如果任务被阻塞的时间少于50%,即这些任务是计算密集型的,则程序所需线程数将随之减少,但最少也不应低于处理器的核心数。
*/
private static final int DEFAULT_MAX_NUM_THREADS = 2 * Runtime.getRuntime().availableProcessors() + 1;
private final DownloadExecutor networkExecutor;
private final Executor backgroundExecutor;
private final Executor mainThreadExecutor;
DefaultExecutorSupplier() {
//线程工厂接口,用于生产线程池中的线程,并使得所有线程的优先级都为THREAD_PRIORITY_BACKGROUND(后台线程建议设置这个优先级,值为10)
ThreadFactory backgroundPriorityThreadFactory = new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND);
networkExecutor = new DownloadExecutor(DEFAULT_MAX_NUM_THREADS, backgroundPriorityThreadFactory);
backgroundExecutor = Executors.newSingleThreadExecutor();
mainThreadExecutor = new MainThreadExecutor();
}
@Override
public DownloadExecutor forDownloadTasks() { return networkExecutor; }
@Override
public Executor forBackgroundTasks() { return backgroundExecutor; }
@Override
public Executor forMainThreadTasks() { return mainThreadExecutor; }
}
- 对优先级处理的线程生产者,专门用于生产下载线程 PriorityThreadFactory
//使用包装者模式对制造的线程进行包装处理,设置其优先级,设计的很漂亮(此优先级并不是多任务的优先级)
public class PriorityThreadFactory implements ThreadFactory {
private final int mThreadPriority;
PriorityThreadFactory(int threadPriority) {
mThreadPriority = threadPriority;
}
@Override
public Thread newThread(final Runnable runnable) {
Runnable wrapperRunnable = new Runnable() {
@Override
public void run() {
try {
Process.setThreadPriority(mThreadPriority);
} catch (Throwable ignored) {}
runnable.run();
}
};
return new Thread(wrapperRunnable);
}
}
- 下载执行者 DownloadExecutor
//开始下载
Core.getInstance().getExecutorSupplier().forDownloadTasks().submit(new DownloadRunnable(request));
public class DownloadExecutor extends ThreadPoolExecutor {
DownloadExecutor(int maxNumThreads, ThreadFactory threadFactory) {
//PriorityBlockingQueue类需要其队列中的元素继承 Comparator 接口, 在插入资源时会按照自定义的排序规则来对资源数组进行排序。
//其中值大的排在数组后面 ,取值时从数组头开始取(先进先出)
super(maxNumThreads, maxNumThreads, 0, TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>(), threadFactory);
}
@Override
public Future<?> submit(Runnable task) {
//创建FutureTask
DownloadFutureTask futureTask = new DownloadFutureTask((DownloadRunnable) task);
execute(futureTask);
return futureTask;
}
}
public class DownloadFutureTask extends FutureTask<DownloadRunnable> implements Comparable<DownloadFutureTask> {
private final DownloadRunnable runnable;
DownloadFutureTask(DownloadRunnable downloadRunnable) {
super(downloadRunnable, null);
this.runnable = downloadRunnable;
}
//实现Comparable接口,对两个线程的优先级做比较处理
@Override
public int compareTo(DownloadFutureTask other) {
Priority p1 = runnable.priority;
Priority p2 = other.runnable.priority;
//如果优先级相等就按照加入顺序执行(先加入,值小,先执行),否则就比较其优先级(ordinal 是枚举的值,值越大优先级越高)
//所以对于加入顺序是 this - other;对于优先级是 other - this
return (p1 == p2 ? runnable.sequence - other.runnable.sequence : p2.ordinal() - p1.ordinal());
}
}
public enum Priority {
LOW, //ordinal的值为0,后续依次加1
MEDIUM,
HIGH,
IMMEDIATE
}
public class DownloadRunnable implements Runnable {
public final Priority priority;
public final int sequence; //该任务的加入时序,加入越迟,数值越大
public final DownloadRequest request;
DownloadRunnable(DownloadRequest request) {
this.request = request;
this.priority = request.getPriority();
this.sequence = request.getSequenceNumber();
}
@Override
public void run() {
//以下整体是同步执行
request.setStatus(Status.RUNNING);
DownloadTask downloadTask = DownloadTask.create(request);
Response response = downloadTask.run();
if (response.isSuccessful()) {
request.deliverSuccess();
} else if (response.isPaused()) {
request.deliverPauseEvent();
} else if (response.getError() != null) {
request.deliverError(response.getError());
} else if (!response.isCancelled()) {
request.deliverError(new Error());
}
}
}
- 主线程执行器 MainThreadExecutor
//巧妙结合Handler和Executor,值得借鉴
public class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable runnable) { handler.post(runnable);}
}
扩展
Future接口
Future接口代表异步计算的结果,通过Future接口提供的方法可以查看异步计算是否执行完成,或者等待执行结果并获取执行结果,同时还可以取消执行。Future接口的定义如下:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
- cancel():该方法用来取消异步任务的执行。如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回false。如果任务还没有被执行,则会返回true并且异步任务不会被执行。如果任务已经开始执行了但是还没有执行完成,若mayInterruptIfRunning为true,则会立即中断执行任务的线程并返回true,若mayInterruptIfRunning为false,则会返回true且不会中断任务执行线程。
- isCanceled():判断任务是否被取消,如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回true,否则返回false。
- isDone():判断任务是否已经完成,如果完成则返回true,否则返回false。需要注意的是:任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回true。
- get():获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。如果任务被取消则会抛出CancellationException异常,如果任务执行过程发生异常则会抛出ExecutionException异常,如果阻塞等待过程中被中断则会抛出InterruptedException异常。
- get(long timeout,Timeunit unit):带超时时间的get()版本,如果阻塞等待过程中超时则会抛出TimeoutException异常。
AtomicInteger
原子操作的Integer,适合高并发情况下加减(线程安全),无需添加synchronized,性能较好
private final AtomicInteger sequenceGenerator = new AtomicInteger();
//自增长后并返回其值
int curNum = sequenceGenerator.incrementAndGet();