接鍋太急?DownloadManager助你一臂之力
在Android開發中,許多時候都會用到大檔案下載功能,例如app更新、快取視訊檔案等。之前我們開發團隊里布置了一次作業,寫的是關於利用RandomAcceseeFile和service來實現一個下載器,需要考慮到例如非同步、後臺下載、暴露回撥介面、自動安裝等策略,對於初級Android工程師來說,專案比較趕的時候可能就會影響效率。那麼,有沒有一個庫能夠簡單粗暴地完成這些任務呢?
當然,那就是Google官方的DownloadManager
介紹
我們看看官方對於兩個主要內部類的介紹:
Nested classes | description |
---|---|
DownloadManager.Query | This class may be used to filter download manager queries. |
DownloadManager.Request | This class contains all the information necessary to request a new download. |
顧名思義DownloadManager.Query主要用於查詢下載的資訊,DownloadManager.Request主要用於發起一個下載請求(其中可以新增下載的配置,例如Header等資訊)
基本使用
1.設定許可權
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 複製程式碼
2. 構造Request物件
val url = "http://hongyan.cqupt.edu.cn/app/com.mredrock.cyxbs.apk?version=44.0"//下載地址 val uri: Uri = Uri.parse(url)//轉變為Uri val request:DownloadManager.Request = DownloadManager.Request(uri)//構造request例項 複製程式碼
3. 配置Request的資訊
request.setTitle("下載任務") request.setDescription("下載掌上重郵app中...") request.allowScanningByMediaScanner() request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "cyxbs") //... 複製程式碼
部分配置:
- addRequestHeader(String header,String value) 新增請求頭
- allowScanningByMediaScanner() 表示允許MediaScanner掃描到這個檔案,預設不允許
- setAllowedNetworkTypes(int flags) 設定下載時的網路條件,預設任何網路都可以下載,可選配置:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。
- setAllowedOverRoaming(Boolean allowed) 漫遊狀態下是否可以下載
- setNotificationVisibility(int visibility) 設定下載完成或下載時是否釋出通知
- setTitle(CharSequence):設定Notification的title
- setDescription(CharSequence):設定Notification的message
- setDestinationInExternalFilesDir 設定路徑為應用程式外部檔案目錄
- setDestinationInExternalPublicDir 設定路徑為外部儲存目錄
- setDestinationUri 設定路徑
- setMimeType(String mimeType) 設定MIME內容型別
4.獲取DownloadManager例項
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager 複製程式碼
5.加入下載佇列
val downloadId = downloadManager.enqueue(request) 複製程式碼
6.其他操作
//remove方法可以用來取消一個準備進行的下載,中止一個正在進行的下載,或者刪除一個已經完成的下載。 downloadManager.remove(downloadId)//移除請求佇列,取消下載 複製程式碼
監聽DownloadManager的廣播
DownloadManager會在完成時傳送一條 ACTION_DOWNLOAD_COMPLETE 的廣播,這時我們只需要建立一個BroadCastReceiver就能接收到下載完成的資訊
建立BroadCastReceiver的子類來接收廣播
class DownLoadFinishReceiver : BroadcastReceiver(){ override fun onReceive(context: Context?, intent: Intent?) { intent?.let { val action = intent.action if (action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) { //下載完成操作 } else if (action == DownloadManager.ACTION_NOTIFICATION_CLICKED) { //點選Notification的操作 例如暫停操作 } } } } 複製程式碼
動態註冊廣播接收器
val downLoadFinishReceiver = DownLoadFinishReceiver() val intentFilter = IntentFilter() intentFilter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE) intentFilter.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED) registerReceiver(downLoadFinishReceiver, intentFilter) 複製程式碼
別忘了在onDestroy裡解除
override fun onDestroy() { super.onDestroy() unregisterReceiver(downLoadFinishReceiver) } 複製程式碼
Query的使用
query主要用於查詢下載的資訊,DownloadManager類中內建了很多的欄位可以供Query來查詢,例如狀態、檔案下載路徑等。
Public method | description |
---|---|
setFilterById(long... ids) | 僅包括給定id的下載檔案 |
setFilterByStatus(int flags) | 僅包含狀態與給定狀態匹配的下載檔案 |
結合Cursor,我們可以查詢到DownloadManager裡有的資訊,包括下載檔案的Uri,下載狀態等。具體欄位可以進入DownloadManager原始碼中檢視。
查詢下載狀態
private fun queryStatus(){ val query = DownloadManager.Query().setFilterById(downloadId) val cursor = manager.query(query) if(cursor!=null){ if(cursor.moveToFirst()){ val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) when(status){ DownloadManager.STATUS_RUNNING->{ //下載中 } DownloadManager.STATUS_FAILED->{ //下載失敗 } DownloadManager.STATUS_PAUSED->{ //下載暫停 } DownloadManager.STATUS_PENDING->{ //下載延遲 } DownloadManager.STATUS_SUCCESSFUL->{ //下載成功 } } } } cursor.close() } 複製程式碼
查詢下載的檔案的部分資訊
這裡DownloadManager封裝好了兩種查詢的方法,其內部都是使用Query+Cursor的組合實現的。
manager.getUriForDownloadedFile(downloadId) //下載完成的檔案的Uri,你可以拿到這個uri去操作他,例如apk安裝 manager.getMimeTypeForDownloadedFile(downloadId) //下載完成的檔案的media type 複製程式碼
你也可以查詢其他資訊
DownloadManager.COLUMN_ID//下載id DownloadManager.COLUMN_TITLE//下載檔案的題目 DownloadManager.COLUMN_LOCAL_URI//下載檔案的uri DownloadManager.COLUMN_STATUS//下載檔案的狀態 DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR//目前檔案的大小 DownloadManager.COLUMN_TOTAL_SIZE_BYTES//檔案的總大小 複製程式碼
多說一句,若想回調進度,你可以根據上面的 檔案目前大小 / 檔案總大小 來得到,重新整理進度可以使用Hanlder+Timer(僅供參考)
自動安裝Apk
首先列出相容7.0的方法,因為7.0時引入了"StrictMode Api"政策,禁止向你的應用外公開uri,如果Intent跳轉到應用外,則會出現FileUriExposedException,所以說我們要新增一個flag去獲得臨時授權
private fun installApk(downloadId: Long) { val uri = manager.getUriForDownloadedFile(downloadId) val intent = Intent(Intent.ACTION_VIEW) if (uri != null) { intent.setDataAndType(uri,"application/vnd.android.package-archive") if ((Build.VERSION.SDK_INT >= 24)) {//版本是否在7.0以上 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) //對目標apk的uri臨時授權 使得有許可權開啟該Uri指向的Apk } intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) if (intent.resolveActivity(packageManager) != null) { startActivity(intent) } else { Log.e("DownloadManager","自動安裝失敗") } }else{ Log.e("DownloadManager","下載失敗") } } 複製程式碼
其次,8.0時禁止安裝未知來源的apk,直接安裝會閃退,所以說我們加入這條許可權,就能跳轉到一個讓使用者手動允許安裝未知來源的介面。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> 複製程式碼
自動更新
(流程圖練手)

後話
因為Request要設定的引數比較多,適合Builder模式,所以說大家可以寫一個RequsetBuilder來配置Request物件,比較美觀,簡單修改如下。
class RequestBuilder { private lateinit var request:DownloadManager.Request private lateinit var uri: Uri private lateinit var context:Context fun with(context: Context):RequestBuilder{ this.context = context return this } fun downloadUrl(url:String):RequestBuilder{ this.uri = Uri.parse(url) this.request = DownloadManager.Request(uri) return this } fun setTitle(title: String):RequestBuilder{ request.setTitle(title) return this } fun setDescription(description: String):RequestBuilder{ request.setDescription(description) return this } fun allowScanningByMediaScanner():RequestBuilder{ request.allowScanningByMediaScanner() return this } fun setNetworkType(networkType:Int):RequestBuilder{ request.setAllowedNetworkTypes(networkType) return this } fun setNotificationVisibility(visibility:Int):RequestBuilder{ request.setNotificationVisibility(visibility) return this } fun setDefaultDestination(subPath:String):RequestBuilder{ request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, subPath) return this } fun build():DownloadManager.Request{ return request } } 複製程式碼
此外,記得快取downloadId,否則退出介面之後id就丟失了。