【轉載】文件下載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進程) 接收到下載進程的鏈接,會繼續下載與繼續接收回調,下載進程也會斷點續傳沒有下載完的全部任務(不管並行與串行),不會影響體驗。

相關文章
相關標籤/搜索