首先介紹一下FileDownloader
GH :https://github.com/lingochamp/FileDownloader/blob/master/README-zh.md
FileDownloader2
現在, FileDownloader2-OkDownload 已經正式發布, okdownload繼承了所有FileDownloader的優點,甚至做了更多的優化以及更多的特性。
特點
- 簡單易用
- 單任務多線程/多連接/分塊下載(並支持通過
ConnectionCountAdapter定制) - 高並發
- 靈活
- 可選擇性支持: 獨立/非獨立進程
- 自動斷點續傳
需要注意
- 當下載的文件大小可能大於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。
I. 效果
II. 使用
在項目中引用:
implementation 'com.liulishuo.filedownloader:library:1.7.5'
如果是eclipse引入jar包參考: 這里
如果需要引入snapshot版本,請添加sonatype的倉庫:
repositories { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } }
全局初始化
如果你需要注冊你的定制組件,你需要在Application#onCreate中調用FileDownloader.setupOnApplicationOnCreate(application):InitCustomMaker, 否則你只需要在使用FileDownloader之前的任意時候調用FileDownloader.setup(Context)即可。
這些初始化方法都十分的簡單,不會啟動下載服務,一般都是在10ms內完成。
啟動單任務下載
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) // ); } // 最后你需要主動調用start方法來啟動該Queue queueSet.start() // 串行任務動態管理也可以使用FileDownloadSerialQueue。
全局接口說明(FileDownloader)
所有的暫停,就是停止,會釋放所有資源並且停到所有相關線程,下次啟動的時候默認會斷點續傳
| 方法名 | 備注 |
|---|---|
| setup(Context) | 如果不需要注冊定制組件,就使用該方法在使用下載引擎前調用,該方法只會緩存Context |
| setupOnApplicationOnCreate(application):InitCustomMaker | 如果需要注冊定制組件,就在Application#onCreate中調用該方法來注冊定制組件以及初始化下載引擎,該方法不會啟動下載服務 |
| 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 | RemitDatabase、SqliteDatabaseImpl、NoDatabaseImpl | RemitDatabase | 傳入定制化數據庫組件,用於存儲用於斷點續傳的數據 |
| connection | FileDownloadConnection | FileDownloadUrlConnection | FileDownloadUrlConnection | 傳入定制化的網絡連接組件,用於下載時建立網絡連接 |
| outputStreamCreator | FileDownloadOutputStream | FileDownloadRandomAccessFile | FileDownloadRandomAccessFile | 傳入輸出流組件,用於下載時寫文件使用 |
| maxNetworkThreadCount | - | - | 3 | 傳入創建下載引擎時,指定可用的下載線程個數 |
| ConnectionCountAdapter | ConnectionCountAdapter | DefaultConnectionCountAdapter | DefaultConnectionCountAdapter | 根據任務指定其線程數 |
| IdGenerator | IdGenerator | DefaultIdGenerator | DefaultIdGenerator | 自定義任務Id生成器 |
- 如果你希望Okhttp作為你的網絡連接組件,可以使用這個庫。
- 如果你不希望FileDownloader用到任何的數據庫(是用於存儲任務的斷點續成信息的),只需要使用NoDatabaseImpl.java即可。
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 |
| broadcast.completed | 是否需要在任務下載完成后發送一個完成的廣播 | false |
如果你使用
broadcast.completed並且接收任務完成的廣播,你需要注冊Action為filedownloader.intent.action.completed的廣播並且使用FileDownloadBroadcastHandler來處理接收到的Intent。
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 |
當有一個正在下載的任務,它的存儲路徑與當前任務的存儲路徑完全一致,為了避免多個任務對同一個文件進行寫入,當前任務便會拋回這個異常 |
個人代碼,檢查更新app class
package net.yt.yuncare.widgets; import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.v4.content.FileProvider; import android.util.Log; import android.webkit.MimeTypeMap; import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.liulishuo.filedownloader.BaseDownloadTask; import com.liulishuo.filedownloader.FileDownloadListener; import com.liulishuo.filedownloader.FileDownloader; import java.io.File; /* content:檢查版本、更新下載功能class time:2018-7-23 11:23 */ public class CheckUpdate { private final String TAG= "CheckUpdate_檢查更新"; private Context mContext; private DownloadManager mDownloadManager; private MaterialDialog mDialog; private DownloadManager.Request mRequest; private int mDownId; private Handler mHandler; private String mDownPath; private String mDownApkName; public CheckUpdate(Context context){ this.mContext = context; } /* * 對比apk版本號的方法 * @param float 要對比的目標版本號 * @return true=版本號一致,false=版本號不一致 */ public boolean CheckVersion(float targetVersion){ String packagename = mContext.getPackageName(); float version = 0; try { //獲取當前apk的版本號, getPackageInfo = 包信息 :參數為(包名,版本號曾量) version = (float) mContext.getPackageManager().getPackageInfo(packagename,0).versionCode; Log.e(TAG, "pkgVersion:"+false); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } if (version == targetVersion){ return true; }else { return false; } } /* * 下載apk的方法 * @param String downUri : 參數是下載apk的網絡地址Uri * @param String downPath : 參數是存放下載內容的文件目錄路徑 * @param String downApkName : 參數是設置下載完成后的apk名稱。注意!名稱后面要加.apk后綴名 * @param DisplayMode displayMode : 下載模式,推薦使用 DisplayMode.MODE_DIALOG_BOX ,系統的未完善 * */ public void downNewApk(String downUri,String downPath,String downApkName,DisplayMode displayMode){ if(mContext == null){ Log.e(TAG, "Error:mContext is null"); return; } if (downUri == null){ Log.e(TAG, "Error:downUri is null"); return; } if (downPath.equals("")&&downPath==null){ Log.e(TAG, "Error:downPath is null"); return; } if (downApkName.equals("")&& downApkName == null){ Log.e(TAG, "Error:downApkName is null"); } mDownPath = downPath; mDownApkName = downApkName; if(displayMode == DisplayMode.MODE_DIALOG_BOX){ //使用第三方下載 dialog(downUri); }else if (displayMode == DisplayMode.MODE_STATUS_BAR){ //使用系統下載,注意!此系統下載沒有寫下載完成的廣播監聽,所以無法自動安裝apk mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); //初始化下載的request mRequest = new DownloadManager.Request(Uri.parse(downUri)); //設置允許網絡類型 分別是 移動網絡 和 WiFi網絡 mRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); mRequest.setAllowedOverRoaming(false); //設置文件類型 MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); String mimeString = mimeTypeMap.getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(downUri)); mRequest.setMimeType(mimeString); Log.e(TAG, "選擇模式:狀態欄"); mRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);//通知可見性 ,參數為可見度為可見 mRequest.setShowRunningNotification(true); //設置顯示運行通知 mRequest.setVisibleInDownloadsUi(true); //在下載中設置UI可見的 //設置下載保存路徑 mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, downApkName); mRequest.setTitle(downApkName); //插入下載隊列.返回下載對象Id mDownId = (int)mDownloadManager.enqueue(mRequest); Toast.makeText(mContext,"正在更新",Toast.LENGTH_SHORT).show(); }else { Log.e(TAG, "Error:downNewApk @param is null"); return; } } private void dialog (String url){ File file = new File(mDownPath); File fileApk = new File(mDownPath+mDownApkName); if (fileApk.exists()){ fileApk.delete(); } if (!file.exists()){ file.mkdirs(); } mDialog = new MaterialDialog.Builder(mContext) .title("更新中") .canceledOnTouchOutside(false) .progress(false,100,false) .positiveText("取消更新") .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { FileDownloader.getImpl().pauseAll();//暫停所有任務 FileDownloader.getImpl().clear(mDownId,mDownPath+"/"+mDownApkName);//強制清除下載的臨時文件 FileDownloader.getImpl().clearAllTaskData();//清空下載任務數據庫 FileDownloader.getImpl().unBindService(); //關閉下載進程 dialog.dismiss(); } }) .build(); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case 0x0001: int soFarBytes = msg.getData().getInt("soFarBytes",0); int totalBytes = msg.getData().getInt("totalBytes",0); float progress = ((float)soFarBytes/(float)totalBytes)*100; Log.e(TAG, "pending:接受更新值: soFarBytes="+soFarBytes+" totalBytes="+totalBytes+" progress="+progress); mDialog.setProgress((int)progress); mDialog.setContent(soFarBytes+"/"+totalBytes+" KB"); break; case 0x0002: mDialog.dismiss(); break; case 0x0003: Log.e(TAG, "mHandler:顯示對話框"); mDialog.show(); break; default: break; } } }; FileDownloader.setup(mContext); FileDownloader.getImpl() .create(url) .setPath(mDownPath+"/"+mDownApkName) .setListener(new FileDownloadListener() { @Override //等待 參數 task=下載任務 soFarBytes=當前已經下載字節 totalBytes=總字節 protected void pending(BaseDownloadTask task, int soFarBytes, int totalBytes) { mDownId = task.getId(); Log.e(TAG, "pending:顯示對話框"); Message message = Message.obtain(); message.what = 0x0003; mHandler.sendMessage(message); } @Override //增量 protected void progress(BaseDownloadTask task, int soFarBytes, int totalBytes) { Bundle bundle = new Bundle(); Log.e(TAG, "progress:發生更新值"); bundle.putInt("soFarBytes",soFarBytes); bundle.putInt("totalBytes",totalBytes); Message message = Message.obtain(); message.setData(bundle); message.what = 0x0001; mHandler.sendMessage(message); } @Override //全部下載完成后 protected void completed(BaseDownloadTask task) { Log.e(TAG, "completed:下載完成"); Message message = Message.obtain(); message.what = 0x0002; mHandler.sendMessage(message); installApk(mDownPath+"/"+mDownApkName); } @Override //暫停 protected void paused(BaseDownloadTask task, int soFarBytes, int totalBytes) { } @Override protected void error(BaseDownloadTask task, Throwable e) { Log.e(TAG, "FileDownloader error: "+e); } @Override protected void warn(BaseDownloadTask task) { } }).start(); } private void installApk(String path) { Intent intent = new Intent(Intent.ACTION_VIEW); File apkFile = new File(path); if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.N) { Uri contentUri = FileProvider.getUriForFile(mContext,mContext.getPackageName()+".provider",apkFile); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(contentUri, "application/vnd.android.package-archive"); }else{ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setDataAndType(Uri.fromFile(apkFile),"application/vnd.android.package-archive"); } mContext.startActivity(intent); } public enum DisplayMode { MODE_DIALOG_BOX,MODE_STATUS_BAR ; } }



