Android開源:FileDownloader-文件下載引擎
FileDownloader
Android 文件下載引擎,穩定、高效、靈活、簡單易用
特點
- 簡單易用
- 高并發
- 靈活
- 可選擇性支持: 獨立/非獨立進程
- 自動斷點續傳
需要注意
- 當下載的文件大小可能大于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.properties 將 process.non-separate 置為 true ,可以有效減少每次回調IPC帶來的I/O。
歡迎提交 Pull requests
- 盡量多的英文注解。
- 每個提交盡量的細而精準。
- Commit message 遵循: AngularJS's commit message convention 。
- 盡可能的遵循IDE的代碼檢查建議(如 Android Studio 的 'Inspect Code')。
I. 效果
II. 使用
在項目中引用:
compile 'com.liulishuo.filedownloader:library:1.4.2'
全局初始化在 Application.onCreate 中
public XXApplication extends Application{
...
@Override
public void onCreate() {
/**
* 僅僅是緩存Application的Context,不耗時
*/
FileDownloader.init(getApplicationContext);
}
...
}</code></pre>
啟動單任務下載
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();</code></pre>
啟動多任務下載
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 )
所有的暫停,就是停止,會釋放所有資源并且停到所有相關線程,下次啟動的時候默認會斷點續傳
方法名
備注
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
傳入創建下載引擎時,指定可用的下載線程個數
Task接口說明
方法名
備注
setPath(path:String)
下載文件的存儲絕對路徑
setPath(path:String, pathAsDirectory:boolean)
如果 pathAsDirectory 是 true , 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-Match 與 Range 參數),請勿重復添加導致400或其他錯誤
addHeader(line:String)
添加自定義的請求頭參數,需要注意的是內部為了斷點續傳,在判斷斷點續傳有效時會自動添加上( If-Match 與 Range 參數),請勿重復添加導致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 中的方法回調過快,導致掉幀?
你有兩種方法可以解決這個問題
- FileDownloader#enableAvoidDropFrame , 默認 就是開啟的
- 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呢? 建議參考 NotificationMinSetActivity 、 NotificationSampleActivity 。
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進程處于 后臺進程 (可以理解為應用被退到后臺)狀態,在內存不足的情況下會被回收(回收優先級高于 服務進程 ),此時分兩種情況:
-
是串行隊列任務,在回收掉UI進程內存以后,下載進程會繼續下載完已經pending到下載進程的那個任務,而還未pending到下載進程的任務會中斷下載(由于任務驅動線性執行的是在UI進程); 有損體驗: 下次進入應用重啟啟動整個隊列,會繼續上次的下載。
-
是并行隊列任務,在回收掉UI進程內存以后,下載進程會繼續下載所有任務(所有已經pending到下載進程的任務,由于這里的pending速度是很快的,因此幾乎是點擊并行下載,所有任務在很短的時間內都已經pending到下載進程了),而UI進程由于被回收,將不會收到所有的監聽; 有損體驗: 下次進入應用重新啟動整個隊列,就會和正常的下載啟動一致,收到所有情況的監聽。
下載進程:
對內存有一定的占用,但是并不多,每次啟動進程會根據數據的有效性進行清理冗余數據,被回收是低概率事件
由于下載不斷有不同的buffer占用內存,但是由于在下載時,是活躍的 服務進程 ,因此被回收是低概率事件(會先回收完所有 空進程 、 后臺進程 (后臺應用)以后,如果內存還不夠,才會回收該進程)。
即使被回收,也不會有任何問題。由于我們使用的是 START_STICKY (如果不希望被重啟可主動調用 FileDownloader#unBindService / FileDownloader#unBindServiceIfIdle ),因此在內存足夠的時候,下載進程會嘗試重啟(系統調度),非下載進程(一般是UI進程) 接收到下載進程的連接,會繼續下載與繼續接收回調,下載進程也會斷點續傳沒有下載完的所有任務(無論并行與串行),不會影響體驗。
IV. LICENSE
Copyright (c) 2015 LingoChamp Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
項目主頁:
http://www.baiduhome.net/lib/view/home/1491393141779