Android笔记系列--超详细DownloadManager使用,兼容到版本8.0

厉熠彤
2023-12-01

超详细DownloadManager使用,兼容到版本8.0

最近用到了软件更新,感觉自己写更新比较麻烦,还要定义通知栏的进度效果,想了一下还是使用系统自带的DownloadManager好了,但这个坑还是挺多的,还要搞兼容,现在来总结一下。

步骤

  1. 申请权限

文件下载和保存需要网络跟写存储卡权限

	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

8.0针对未知来源应用,在应用权限设置的“特殊访问权限”中,加入了“安装其他应用”的设置,这主要是为了防止应用内引导用户安装其他无关应用,特别是针对一些流氓应用会比较有效。添加下面权限后就可以正常跳转安装界面

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
  1. 写下载监听

我们可以定义一个BroadcastReceiver

class DownloadReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        if (intent.action == DownloadManager.ACTION_DOWNLOAD_COMPLETE) {
            val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
            if (SavePicNoLogService.downloadId == id) {
                installApk(context, id)
            }
        } else if (intent.action == DownloadManager.ACTION_NOTIFICATION_CLICKED) {
            // DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
            //获取所有下载任务Ids组
            //long[] ids = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
            点击通知栏取消所有下载
            //manager.remove(ids);
            //Toast.makeText(context, "下载任务已取消", Toast.LENGTH_SHORT).show();
            //处理 如果还未完成下载,用户点击Notification ,跳转到下载中心
            val viewDownloadIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
            viewDownloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            context.startActivity(viewDownloadIntent)
        }
    }

    private fun installApk(context: Context, downloadApkId: Long) {
        val dManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        val install = Intent(Intent.ACTION_VIEW)
        val downloadFileUri = dManager.getUriForDownloadedFile(downloadApkId)
        if (downloadFileUri != null) {
            Log.d("DownloadManager", downloadFileUri.toString())
            install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive")
            if ((Build.VERSION.SDK_INT >= 24)) {//判读版本是否在7.0以上
                install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) //添加这一句表示对目标应用临时授权该Uri所代表的文件
            }
            install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            if (install.resolveActivity(context.packageManager) != null) {
                context.startActivity(install)
            } else {
                L.e("自动安装失败,请手动安装")

                val service = context as SavePicNoLogService
                service.showErrorTip("下载完成,请点击下拉列表的通知手动安装")
            }
        } else {
            Log.e("DownloadManager", "download error")
        }
    }
}

这里要注意的地方是7.0版本系统会有个文件路径的限制,我们需要添加一个FLAG_GRANT_READ_URI_PERMISSION来取得目标文件使用权限。

还要在代码中注册监听和在onDestory方法中销毁监听

private fun initDownLoadBroadcastReceiver() {
    L.i("init DownLoadBroadcastReceiver...")
    downloadBroadcastReceiver = DownloadReceiver()
    val intentFilter = IntentFilter()
    intentFilter.addAction("android.intent.action.DOWNLOAD_COMPLETE")
    intentFilter.addAction("android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED")
    registerReceiver(downloadBroadcastReceiver, intentFilter)
}

override fun onDestroy() {
    super.onDestroy()
    unregisterReceiver(downloadBroadcastReceiver)
}

或者在清单文件中注册也行

<receiver android:name=".receiver.DownloadReceiver">
   <intent-filter>
       <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
       <action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED" />
   </intent-filter>
</receiver>
  1. 封装DownloadManager下载工具类
    封装一个工具类方便以后使用
class DownloadManagerUtil(private val mContext: Context) {

    /**
     * 可能会出错Cannot update URI: content://downloads/my_downloads/-1
     * 检查下载管理器是否被禁用
     */
    fun checkDownloadManagerEnable():Boolean {
        try {
            val state = mContext.packageManager.getApplicationEnabledSetting("com.android.providers.downloads")
            if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                    || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
                    || state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                val packageName = "com.android.providers.downloads"
                try {
                    val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                    intent.data = Uri.parse("package:$packageName")
                    mContext.startActivity(intent)
                } catch (e: ActivityNotFoundException) {
                    e.printStackTrace()
                    val intent = Intent(android.provider.Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS)
                    mContext.startActivity(intent)
                }
                return false
            }
        } catch (e:Exception) {
            e.printStackTrace()
            return false
        }
        return true
    }

    fun download(url: String, title: String, desc: String): Long {
        val uri = Uri.parse(url)
        val req = DownloadManager.Request(uri)
        //设置允许使用的网络类型,这里是移动网络和wifi都可以
        req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI)
        //下载中和下载完后都显示通知栏
        req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)

        //设置文件的保存的位置[三种方式]
        // 第一种 file:///storage/emulated/0/Android/data/your-package/files/Download/update.apk
        req.setDestinationInExternalFilesDir(mContext, Environment.DIRECTORY_DOWNLOADS, "$title.apk")
        //第二种 file:///storage/emulated/0/Download/update.apk
//        req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk")
        //第三种 自定义文件路径
//        req.setDestinationUri()

        //禁止发出通知,既后台下载
//        req.setShowRunningNotification(false);
        //通知栏标题
        req.setTitle(title)
        //通知栏描述信息
        req.setDescription(desc)
        //设置类型为.apk
        req.setMimeType("application/vnd.android.package-archive")
        // 设置为可被媒体扫描器找到
        req.allowScanningByMediaScanner()
        // 设置为可见和可管理
        req.setVisibleInDownloadsUi(true)
        //获取下载任务ID
        val dm = mContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager

        return try {
            dm.enqueue(req)
        } catch (e: Exception) {
            Toast.makeText(mContext, "找不到下载文件", Toast.LENGTH_SHORT).show()
            -1
        }
    }

    /**
     * 下载前先移除前一个任务,防止重复下载
     *
     * @param downloadId
     */
    fun clearCurrentTask(downloadId: Long) {
        val dm = mContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        try {
            dm.remove(downloadId)
        } catch (ex: IllegalArgumentException) {
            ex.printStackTrace()
        }
    }
}

要注意的地方:
如果禁用了下载管理器,调用enqueue方法可能出现的错误:Cannot update URI: content://downloads/my_downloads/-1
这里面的checkDownloadManagerEnable方法是用来检测下载管理器是否被禁用,如果禁用了主动开启。

  1. 使用
downloadBroadcastReceiver = DownloadReceiver()
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.DOWNLOAD_COMPLETE")
intentFilter.addAction("android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED")
registerReceiver(downloadBroadcastReceiver, intentFilter)
btn_download.setOnClickListener {
    val dm = DownloadManagerUtil(this)
    if (dm.checkDownloadManagerEnable()) {
        if (MyApplication.downloadId != 0L) {
            dm.clearCurrentTask(MyApplication.downloadId) // 先清空之前的下载
        }
        MyApplication.downloadId = dm.download(url, title, desc)
    }else{
        Toast.makeText(this@MainActivity,"请开启下载管理器",Toast.LENGTH_SHORT).show()
    }
}

例子代码已经放到Github了

链接: https://github.com/StarsAaron/DownloadManagerDemo.

 类似资料: