最近用到了软件更新,感觉自己写更新比较麻烦,还要定义通知栏的进度效果,想了一下还是使用系统自带的DownloadManager好了,但这个坑还是挺多的,还要搞兼容,现在来总结一下。
文件下载和保存需要网络跟写存储卡权限
<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"/>
我们可以定义一个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>
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方法是用来检测下载管理器是否被禁用,如果禁用了主动开启。
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()
}
}