接锅太急?DownloadManager助你一臂之力

在Android开发中,许多时候都会用到大文件下载功能,例如app更新、缓存视频文件等。之前我们开发团队里布置了一次作业,写的是关于利用RandomAcceseeFile和service来实现一个下载器,需要考虑到例如异步、后台下载、暴露回调接口、自动安装等策略,对于初级Android工程师来说,项目比较赶的时候可能就会影响效率。那么,有没有一个库能够简单粗暴地完成这些任务呢?


当然,那就是Google官方的DownloadManager
仅学习参考,部分ROM已经剔除了DownloadManager

介绍

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.设置权限



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,直接安装会闪退,所以说我们加入这条权限,就能跳转到一个让用户手动允许安装未知来源的界面。


自动更新

(流程图练手)


image

后话

因为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就丢失了。

最后

基本内容就这么多了,可能有些地方写得不好,有不正确的地方欢迎大家指出

你可能感兴趣的:(接锅太急?DownloadManager助你一臂之力)