在Android開發中,許多時候都會用到大文件下載功能,例如app更新、緩存視頻文件等。以前咱們開發團隊里布置了一次做業,寫的是關於利用RandomAcceseeFile和service來實現一個下載器,須要考慮到例如異步、後臺下載、暴露回調接口、自動安裝等策略,對於初級Android工程師來講,項目比較趕的時候可能就會影響效率。那麼,有沒有一個庫可以簡單粗暴地完成這些任務呢?
固然,那就是Google官方的DownloadManagerandroid
咱們看看官方對於兩個主要內部類的介紹:
bash
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.設置權限app
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
複製代碼
2. 構造Request對象dom
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")
//...
複製代碼
部分配置:ide
4.獲取DownloadManager實例ui
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
複製代碼
5.加入下載隊列this
val downloadId = downloadManager.enqueue(request)
複製代碼
6.其餘操做
//remove方法能夠用來取消一個準備進行的下載,停止一個正在進行的下載,或者刪除一個已經完成的下載。
downloadManager.remove(downloadId)//移除請求隊列,取消下載
複製代碼
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主要用於查詢下載的信息,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(僅供參考)
首先列出兼容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就丟失了。
基本內容就這麼多了,可能有些地方寫得很差,有不正確的地方歡迎你們指出