美文网首页Android
接锅太急?DownloadManager助你一臂之力

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

作者: Fxymine4ever | 来源:发表于2019-02-14 22:49 被阅读122次

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

    <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"/>
    

    自动更新

    (流程图练手)


    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助你一臂之力

        本文链接:https://www.haomeiwen.com/subject/oicueqtx.html