【转载】文件下载FileDownloader

原文地址:https://github.com/lingochamp/FileDownloader

特色

  • 简单易用
  • 高并发
  • 灵活
  • 可选择性支持: 独立/非独立进程
  • 自动断点续传

须要注意

  • 当下载的文件大小可能大于1.99GB(2^31-1=2_147_483_647 = 1.99GB)的时候, 请使用FileDownloadLargeFileListener而不是FileDownloadListener(同理使用getLargeFileSofarBytes()getLargeFileTotalBytes())
  • 暂停: paused, 恢复: 直接调用start,默认就是断点续传
  • 引擎默认会打开避免掉帧的处理(使得在有些状况下回调(FileDownloadListener)不至于太频繁致使ui线程被ddos), 若是你但愿关闭这个功能(关闭之后,全部回调会与0.1.9以前的版本同样,全部的回调会立马抛一个消息ui线程(Handler))
  • 若是没有特殊须要,直接经过配置filedownloader.propertiesprocess.non-separate置为true,能够有效减小每次回调IPC带来的I/O。

欢迎提交 Pull requests

  • 尽可能多的英文注解。
  • 每一个提交尽可能的细而精准。
  • Commit message 遵循: AngularJS's commit message convention
  • 尽量的遵循IDE的代码检查建议(如 Android Studio 的 'Inspect Code')。

I. 效果

     

II. 使用

在项目中引用:html

compile 'com.liulishuo.filedownloader:library:1.4.3'

 

若是是eclipse引入jar包参考: 这里java

全局初始化在Application.onCreate

public XXApplication extends Application{ ... @Override public void onCreate() { /** * 仅仅是缓存Application的Context,不耗时 */ FileDownloader.init(getApplicationContext); } ... }

 

启动单任务下载

FileDownloader.getImpl().create(url) .setPath(path) .setListener(new FileDownloadListener() { @Override protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) { } @Override protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void blockComplete(BaseDownloadTask task) { } @Override protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) { } @Override protected void completed(BaseDownloadTask task) { } @Override protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void error(BaseDownloadTask task, Throwable e) { } @Override protected void warn(BaseDownloadTask task) { } }).start();

 

启动多任务下载

final FileDownloadListener queueTarget = new FileDownloadListener() { @Override protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void connected(BaseDownloadTask task, String etag, boolean isContinue, int soFarBytes, int totalBytes) { } @Override protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void blockComplete(BaseDownloadTask task) { } @Override protected void retry(final BaseDownloadTask task, final Throwable ex, final int retryingTimes, final int soFarBytes) { } @Override protected void completed(BaseDownloadTask task) { } @Override protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void error(BaseDownloadTask task, Throwable e) { } @Override protected void warn(BaseDownloadTask task) { } }; // 第一种方式 : //for (String url : URLS) { // FileDownloader.getImpl().create(url) // .setCallbackProgressTimes(0) // 因为是队列任务, 这里是咱们假设了如今不须要每一个任务都回调`FileDownloadListener#progress`, 咱们只关系每一个任务是否完成, 因此这里这样设置能够颇有效的减小ipc. // .setListener(queueTarget) // .asInQueueTask() // .enqueue(); //} //if(serial){ // 串行执行该队列 // FileDownloader.getImpl().start(queueTarget, true); // } // if(parallel){ // 并行执行该队列 // FileDownloader.getImpl().start(queueTarget, false); //} // 第二种方式:

final FileDownloadQueueSet queueSet = new FileDownloadQueueSet(downloadListener); final List<BaseDownloadTask> tasks = new ArrayList<>(); for (int i = 0; i < count; i++) { tasks.add(FileDownloader.getImpl().create(Constant.URLS[i]).setTag(i + 1)); } queueSet.disableCallbackProgressTimes(); // 因为是队列任务, 这里是咱们假设了如今不须要每一个任务都回调`FileDownloadListener#progress`, 咱们只关系每一个任务是否完成, 因此这里这样设置能够颇有效的减小ipc. // 全部任务在下载失败的时候都自动重试一次
queueSet.setAutoRetryTimes(1); if (serial) { // 串行执行该任务队列
 queueSet.downloadSequentially(tasks); // 若是你的任务不是一个List,能够考虑使用下面的方式,可读性更强 // queueSet.downloadSequentially( // FileDownloader.getImpl().create(url).setPath(...), // FileDownloader.getImpl().create(url).addHeader(...,...), // FileDownloader.getImpl().create(url).setPath(...) // );
} if (parallel) { // 并行执行该任务队列
 queueSet.downloadTogether(tasks); // 若是你的任务不是一个List,能够考虑使用下面的方式,可读性更强 // queueSet.downloadTogether( // FileDownloader.getImpl().create(url).setPath(...), // FileDownloader.getImpl().create(url).setPath(...), // FileDownloader.getImpl().create(url).setSyncCallback(true) // );
} // 串行任务动态管理也可使用FileDownloadSerialQueue。

 

全局接口说明(FileDownloader)

全部的暂停,就是中止,会释放全部资源而且停到全部相关线程,下次启动的时候默认会断点续传android

方法名 备注
init(Context) 缓存Context,不会启动下载进程
init(Context, InitCustomMaker) 缓存Context,不会启动下载进程;在下载进程启动的时候,会传入定制化组件
create(url:String) 建立一个下载任务
start(listener:FileDownloadListener, isSerial:boolean) 启动是相同监听器的任务,串行/并行启动
pause(listener:FileDownloadListener) 暂停启动相同监听器的任务
pauseAll(void) 暂停全部任务
pause(downloadId) 暂停downloadId的任务
clear(downloadId, targetFilePath) 强制清理ID为downloadId的任务在filedownloader中的数据
getSoFar(downloadId) 得到下载Id为downloadId的soFarBytes
getTotal(downloadId) 得到下载Id为downloadId的totalBytes
bindService(void) 主动启动下载进程(可事先调用该方法(能够不调用),保证第一次下载的时候没有启动进程的速度消耗)
unBindService(void) 主动关停下载进程
unBindServiceIfIdle(void) 若是目前下载进程没有任务正在执行,则关停下载进程
isServiceConnected(void) 是否已经启动而且链接上下载进程(可参考任务管理demo中的使用)
getStatusIgnoreCompleted(downloadId) 获取不包含已完成状态的下载状态(若是任务已经下载完成,将收到INVALID)
getStatus(id:int, path:String) 获取下载状态
getStatus(url:String, path:String) 获取下载状态
setGlobalPost2UIInterval(intervalMillisecond:int) 为了不掉帧,这里是设置了最多每interval毫秒抛一个消息到ui线程(使用Handler),防止因为回调的过于频繁致使ui线程被ddos致使掉帧。 默认值: 10ms. 若是设置小于0,将会失效,也就是说每一个回调都直接抛一个消息到ui线程
setGlobalHandleSubPackageSize(packageSize:int) 为了不掉帧, 若是上面的方法设置的间隔是一个小于0的数,这个packageSize将不会生效。packageSize这个值是为了不在ui线程中一次处理过多回调,结合上面的间隔,就是每一个interval毫秒间隔抛一个消息到ui线程,而每一个消息在ui线程中处理packageSize个回调。默认值: 5
enableAvoidDropFrame(void) 开启 避免掉帧处理。就是将抛消息到ui线程的间隔设为默认值10ms, 很明显会影响的是回调不会立马通知到监听器(FileDownloadListener)中,默认值是: 最多10ms处理5个回调到监听器中
disableAvoidDropFrame(void) 关闭 避免掉帧处理。就是将抛消息到ui线程的间隔设置-1(无效值),这个就是让每一个回调都会抛一个消息ui线程中,可能引发掉帧
isEnabledAvoidDropFrame(void) 是否开启了 避免掉帧处理。默认是开启的
startForeground(id:int, notification:Notification) 设置FileDownloadService为前台模式,保证用户从最近应用列表移除应用之后下载服务不会被杀
stopForeground(removeNotification:boolean) 取消FileDownloadService的前台模式
setTaskCompleted(url:String, path:String, totalBytes:long) 用于告诉FileDownloader引擎,以指定Url与Path的任务已经经过其余方式(非FileDownloader)下载完成
setTaskCompleted(taskAtomList:List) 用于告诉FileDownloader引擎,指定的一系列的任务都已经经过其余方式(非FileDownloader)下载完成
setMaxNetworkThreadCount(int) 设置最大并行下载的数目(网络下载线程数), [1,12]
clearAllTaskData() 清空filedownloader数据库中的全部数据

定制化组件接口说明(InitCustomMaker)

方法名 需实现接口 已有组件 默认组件 说明
database FileDownloadDatabase DefaultDatabaseImpl DefaultDatabaseImpl 传入定制化数据库组件,用于存储用于断点续传的数据
connection FileDownloadConnection FileDownloadUrlConnection FileDownloadUrlConnection 传入定制化的网络链接组件,用于下载时创建网络链接
outputStreamCreator FileDownloadOutputStream FileDownloadRandomAccessFile、FileDownloadBufferedOutputStream、FileDownloadOkio FileDownloadRandomAccessFile 传入输出流组件,用于下载时写文件使用
maxNetworkThreadCount - - 3 传入建立下载引擎时,指定可用的下载线程个数

若是你但愿Okhttp做为你的网络链接组件,可使用这个库git

Task接口说明

方法名 备注
setPath(path:String) 下载文件的存储绝对路径
setPath(path:String, pathAsDirectory:boolean) 若是pathAsDirectorytrue,path就是存储下载文件的文件目录(而不是路径),此时默认状况下文件名filename将会默认从response#header中的contentDisposition中得到
setListener(listener:FileDownloadListener) 设置监听,能够以相同监听组成队列
setCallbackProgressTimes(times:int) 设置整个下载过程当中FileDownloadListener#progress最大回调次数
setCallbackProgressIgnored() 忽略全部的FileDownloadListener#progress的回调
setCallbackProgressMinInterval(minIntervalMillis:int) 设置每一个FileDownloadListener#progress之间回调间隔(ms)
setTag(tag:Object) 内部不会使用,在回调的时候用户本身使用
setTag(key:int, tag:Object) 用于存储任意的变量方便回调中使用,以key做为索引
setForceReDownload(isForceReDownload:boolean) 强制从新下载,将会忽略检测文件是否健在
setFinishListener(listener:FinishListener) 结束监听,仅包含结束(over(void))的监听
setAutoRetryTimes(autoRetryTimes:int) 当请求或下载或写文件过程当中存在错误时,自动重试次数,默认为0次
setSyncCallback(syncCallback:boolean) 若是设为true, 全部FileDownloadListener中的回调都会直接在下载线程中回调而不抛到ui线程, 默认为false
addHeader(name:String, value:String) 添加自定义的请求头参数,须要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上(If-MatchRange参数),请勿重复添加致使400或其余错误
addHeader(line:String) 添加自定义的请求头参数,须要注意的是内部为了断点续传,在判断断点续传有效时会自动添加上(If-MatchRange参数),请勿重复添加致使400或其余错误
setMinIntervalUpdateSpeed(minIntervalUpdateSpeedMs:int) 设置下载中刷新下载速度的最小间隔
removeAllHeaders(name:String) 删除由自定义添加上去请求参数为{name}的全部键对
setWifiRequired(isWifiRequired:boolean) 设置任务是否只容许在Wifi网络环境下进行下载。 默认值false
asInQueueTask(void):InQueueTask 申明该任务将会是队列任务中的一个任务,而且转化为InQueueTask,以后能够调用InQueueTask#enqueue将该任务入队以便于接下来启动队列任务时,能够将该任务收编到队列中
start(void) 启动孤立的下载任务
pause(void) 暂停下载任务(也能够理解为中止下载,可是在start的时候默认会断点续传)
getId(void):int 获取惟一Id(内部经过url与path生成)
getUrl(void):String 获取下载链接
getCallbackProgressTimes(void):int 得到progress最大回调次数
getCallbackProgressMinInterval(void):int 得到每一个progress之间的回调间隔(ms)
getPath(void):String 获取文件路径 或 文件目录
isPathAsDirectory 判断getPath()返回的路径是文件存储目录(directory),仍是文件存储路径(directory/filename)
getTargetFilePath 获取目标文件的存储路径
getListener(void):FileDownloadListener 获取监听器
getSoFarBytes(void):int 获取已经下载的字节数
getTotalBytes(void):int 获取下载文件总大小
getStatus(void):int 获取当前的状态
isForceReDownload(void):boolean 是否强制从新下载
getEx(void):Throwable 获取下载过程抛出的Throwable
isReusedOldFile(void):boolean 判断是不是直接使用了旧文件(检测是有效文件),没有启动下载
getTag(void):Object 获取用户setTag进来的Object
getTag(key:int):Object 根据key获取存储在task中的变量
isContinue(void):boolean 是否成功断点续传
getEtag(void):String 获取当前下载获取到的ETag
getAutoRetryTimes(void):int 自动重试次数
getRetryingTimes(void):int 当前重试次数。将要开始重试的时候,会将接下来是第几回
isSyncCallback(void):boolean 是不是设置了全部FileDownloadListener中的回调都直接在下载线程直接回调而不抛到ui线程
getSpeed():int 获取任务的下载速度, 下载过程当中为实时速度,下载结束状态为平均速度
isUsing():boolean 判断当前的Task对象是否在引擎中启动过
isWifiRequired():boolean 获取当前任务是否被设置过只容许在Wifi网络环境下下载

监听器(FileDownloadListener)说明

通常的下载回调流程:
pending -> started -> connected -> (progress <->progress) -> blockComplete -> completed
可能会遇到如下回调而直接终止整个下载过程:
paused / completed / error / warn
若是检测存在已经下载完成的文件(能够经过isReusedOldFile进行决策是不是该状况)(也能够经过setForceReDownload(true)来避免该状况):
blockComplete -> completed
方法说明
回调方法 备注 带回数据
pending 等待,已经进入下载队列 数据库中的soFarBytes与totalBytes
started 结束了pending,而且开始当前任务的Runnable -
connected 已经链接上 ETag, 是否断点续传, soFarBytes, totalBytes
progress 下载进度回调 soFarBytes
blockComplete 在完成前同步调用该方法,此时已经下载完成 -
retry 重试以前把将要重试是第几回回调回来 之因此重试遇到Throwable, 将要重试是第几回, soFarBytes
completed 完成整个下载过程 -
paused 暂停下载 soFarBytes
error 下载出现错误 抛出的Throwable
warn 在下载队列中(正在等待/正在下载)已经存在相同下载链接与相同存储路径的任务 -

因为FileDownloadListener中的方法回调过快,致使掉帧?

你有两种方法能够解决这个问题github

  1. FileDownloader#enableAvoidDropFrame, 默认 就是开启的
  2. BaseDownloadTask#setSyncCallback, 默认是false, 若是设置为true,全部的回调都会在下载线程直接同步调用而不会抛到ui线程。

FileDownloadMonitor

你能够添加一个全局监听器来进行打点或者是调试数据库

方法名 备注
setGlobalMonitor(monitor:IMonitor) 设置与替换一个全局监听器到下载引擎中
releaseGlobalMonitor(void) 释放已经设置到下载引擎中的全局监听器
getMonitor(void) 获取已经设置到下载引擎中的全局监听器
FileDownloadMonitor.IMonitor

监听器接口类缓存

接口 备注
onRequestStart(count:int, serial:boolean, lis:FileDownloadListener) 将会在启动队列任务是回调这个方法
onRequestStart(task:BaseDownloadTask) 将会在启动单一任务时回调这个方法
onTaskBegin(task:BaseDownloadTask) 将会在内部接收并开始task的时候回调这个方法(会在pending回调以前)
onTaskStarted(task:BaseDownloadTask) 将会在task结束pending开始task的runnable的时候回调该方法
onTaskOver(task:BaseDownloadTask) 将会在task走完全部生命周期是回调这个方法

FileDownloadUtils

方法名 备注
setDefaultSaveRootPath(path:String) 在整个引擎中没有设置路径时BaseDownloadTask#setPath这个路径将会做为它的Root path
getTempPath 获取用于存储还未下载完成文件的临时存储路径: filename.temp
isFilenameConverted(context:Context) 判断是否全部数据库中下载中的任务的文件名都已经从filename(在旧架构中)转为filename.temp

FileDownloadNotificationHelper

如何快速集成Notification呢? 建议参考NotificationMinSetActivityNotificationSampleActivity安全

filedownloader.properties

若是你须要定制化FileDownloader,能够在你的项目模块的assets 目录下添加 'filedownloader.properties' 文件(如/demo/src/main/assets/filedownloader.properties),而后添加如下可选相关配置。网络

格式: keyword=value架构

关键字 描述 默认值
http.lenient 若是你遇到了: 'can't know the size of the download file, and its Transfer-Encoding is not Chunked either', 可是你想要忽略相似的返回头不规范的错误,直接将该关键字参数设置为true便可,咱们将会将其做为chunck进行处理 false
process.non-separate FileDownloadService 默认是运行在独立进程':filedownloader'上的, 若是你想要FileDownloadService共享并运行在主进程上, 将该关键字参数设置为true,能够有效减小IPC产生的I/O false
download.min-progress-step 最小缓冲大小,用于断定是不是时候将缓冲区中进度同步到数据库,以及是不是时候要确保下缓存区的数据都已经写文件。值越小,更新会越频繁,下载速度会越慢,可是应对进程被没法预料的状况杀死时会更加安全 65536
download.min-progress-time 最小缓冲时间,用于断定是不是时候将缓冲区中进度同步到数据库,以及是不是时候要确保下缓存区的数据都已经写文件。值越小,更新会越频繁,下载速度会越慢,可是应对进程被没法预料的状况杀死时会更加安全 2000
download.max-network-thread-count 用于同时下载的最大网络线程数, 区间[1, 12] 3
file.non-pre-allocation 是否不须要在开始下载的时候,预申请整个文件的大小(content-length) false

III. 异常处理

全部的异常,都将在 FileDownloadListener#error(BaseDownloadTask, Throwable) 中获知。

Exception 缘由
FileDownloadHttpException 在发出请求之后,response-code不是200(HTTP_OK),也不是206(HTTP_PARTIAL)的状况下会抛出该异常; 在这个异常对象会带上 response-code、response-header、request-header。
FileDownloadGiveUpRetryException 在请求返回的 response-header 中没有带有文件大小(content-length),而且不是流媒体(transfer-encoding)的状况下会抛出该异常;出现这个异常,将会忽略全部重试的机会(BaseDownloadTask#setAutoRetryTimes). 你能够经过在filedownloader.properties中添加 http.lenient=true 来忽略这个异常,而且在该状况下,直接做为流媒体进行下载。
FileDownloadOutOfSpaceException 当将要下载的文件大小大于剩余磁盘大小时,会抛出这个异常。
其余 程序错误。
FileDownloadNetworkPolicyException 设置了BaseDownloadTask#setWifiRequired(true),在下载过程当中,一旦发现网络状况转为非Wifi环境,便会抛回这个异常
PathConflictException 当有一个正在下载的任务,它的存储路径与当前任务的存储路径彻底一致,为了不多个任务对同一个文件进行写入,当前任务便会抛回这个异常

III. 低内存状况

非下载进程(通常是UI进程):

这边的数据并很少,只是一些队列数据,用不了多少内存。

前台进程数据被回收:

若是在前台的时候这个数据都被回收了, 你的应用应该也挂了。极低几率事件。

后台进程数据被回收:

通常事件, 若是是你的下载是UI进程启动的,若是你的UI进程处于后台进程(能够理解为应用被退到后台)状态,在内存不足的状况下会被回收(回收优先级高于服务进程),此时分两种状况:

  1. 是串行队列任务,在回收掉UI进程内存之后,下载进程会继续下载完已经pending到下载进程的那个任务,而还未pending到下载进程的任务会中断下载(因为任务驱动线性执行的是在UI进程); 有损体验: 下次进入应用重启启动整个队列,会继续上次的下载。

  2. 是并行队列任务,在回收掉UI进程内存之后,下载进程会继续下载全部任务(全部已经pending到下载进程的任务,因为这里的pending速度是很快的,所以几乎是点击并行下载,全部任务在很短的时间内都已经pending到下载进程了),而UI进程因为被回收,将不会收到全部的监听; 有损体验: 下次进入应用从新启动整个队列,就会和正常的下载启动一致,收到全部状况的监听。

下载进程:

对内存有必定的占用,可是并很少,每次启动进程会根据数据的有效性进行清理冗余数据,被回收是低几率事件

因为下载不断有不一样的buffer占用内存,可是因为在下载时,是活跃的服务进程,所以被回收是低几率事件(会先回收完全部空进程后台进程(后台应用)之后,若是内存还不够,才会回收该进程)。

即便被回收,也不会有任何问题。因为咱们使用的是START_STICKY(若是不但愿被重启可主动调用FileDownloader#unBindService/FileDownloader#unBindServiceIfIdle),所以在内存足够的时候,下载进程会尝试重启(系统调度),非下载进程(通常是UI进程) 接收到下载进程的链接,会继续下载与继续接收回调,下载进程也会断点续传没有下载完的全部任务(不管并行与串行),不会影响体验。

相关文章
相关标签/搜索