携手创造,共同成长!这是我参与「日新计划 8 月更文挑战」的第20天,点击检查活动详情
前语
App 内的下载功用也是咱们常用的场景,比方下载最新的 Apk 装置包,还有些会下载图片,或许资源,插件等场景。
下载不是很简略的功用吗?OkHttp就能下载,根据OkHttp结束的一些结构那更多,比较出名的有FileDownloader okdownload RxDownload 等等。
一起咱们 Android 体系服务 DownloadManager 相同能够运用下载服务,他们之间有什么区别?
一、DownloadManager的默许运用
DownloadManager 是android2.3今后,体系下载的办法。能够让 Android 设备恳求的 URI 被下载到一个特定的方针文件。客户端将会在后台与http交互进行下载,或许在下载失败,或许连接改动,从头启动体系后从头下载。还能够进入体系的下载办理界面检查进展。
内部首要包含 DownloadManager.Query 和 DownloadManager.Request 两个重要类。一个是封装一些下载恳求的参数,一个是用于查询下载的信息。Request 是有必要的,Query对错有必要的。
一般运用 DownloadManager 引荐咱们运用告诉栏展现真实进行下载,并且咱们能够跳转到下载器页面检查。
private fun startDownLoad() {
//下载链接 这儿下载手机B站为示例
val downloadUrl = "https://dl.hdslb.com/mobile/latest/iBiliPlayer-html5_app_bili.apk"
val fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1)
//这儿下载到指定的目录,咱们存在公共目录下的download文件夹下
val fileUri = Uri.fromFile(
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
System.currentTimeMillis().toString() + "-" + fileName
)
)
//开端构建 DownloadRequest 方针
val request = DownloadManager.Request(Uri.parse(downloadUrl))
//构建告诉栏样式
request.setTitle("测验下载标题")
request.setDescription("测验下载的内容文本")
//下载或下载结束的时分显示告诉栏
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE or DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
//指定下载的文件类型为APK
request.setMimeType("application/vnd.android.package-archive")
// request.addRequestHeader() //还能参加恳求头
// request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) //能指定下载的网络
//指定下载到本地的途径(能够指定URI)
request.setDestinationUri(fileUri)
//开端构建 DownloadManager 方针
val downloadManager = commContext().getSystemService(DOWNLOAD_SERVICE) as DownloadManager
//参加Request到体系下载行列,在条件满意时会主动开端下载。回来的为下载使命的仅有ID
val requestID = downloadManager.enqueue(request)
//注册下载使命结束的监听
commContext().registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//现已结束
if (intent.action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
//获取下载ID
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
val uri = downloadManager.getUriForDownloadedFile(id)
YYLogUtils.w("下载结束了- uri:$uri")
installApk(uri)
} else if (intent.action.equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
//假如还未结束下载,跳转到下载中心
YYLogUtils.w("跳转到下载中心")
val viewDownloadIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
viewDownloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(viewDownloadIntent)
}
}
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
注释的很详细,步骤如下:
- 咱们封装一个 Request 方针设置下载的链接Uri,设置下载到的方针文件夹,设置是否需求展现告诉等。
- 构建 DownloadManager 服务,把 Request 使命放入行列,假如满意条件即可生效。
- 一般来说咱们都期望下载结束之后能处理一些工作,咱们就需求监听结束的播送(非有必要的)。
这儿需求留意的是:
- 或许需求恳求SD卡权限,
- 假如下载是公共目录,在Android12以上只有download等少量文件夹是敞开的,其他的文件夹或许无法拜访。
- 假如下载的是沙盒目录,你无需恳求SD卡权限,可是假如外部运用想要拜访到此文件,需求界说FileProvider提供给对方运用(比方Apk装置)
结束的作用:
咱们下载的是一个Apk,因为咱们下载到了公共目录的download文件夹下面,所以咱们能够直接调用装置办法,(留意Android8.0的兼容)
兼容8.0以上 声明权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
直接调用即可
private fun installApk(uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
startActivity(intent)
}
作用:
因为测验机器为Android12,所以需求赞同不知道的装置包装置权限
一系列的操作就装置成功了。
不可!我不能让我的Apk就这么暴露在公共目录下面!我要隐私,我要下载在沙盒里面!行不可?
当然行,太行了,咱们下载到沙盒的目录中的话,咱们只能自己的运用有拜访权限,其他的运用程序拜访就需求FileProvider,这儿简略的过一下吧。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.meiyue.smartcity.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--下载apk-->
<external-path
name="download"
path=""/>
</paths>
那么咱们获取Uri的时分咱们就需求经过FileProvider来获取Uri方针了
Uri apkUri = FileProvider.getUriForFile(context, "com.meiyue.smartcity.fileprovider", file);
关于FileProvider感觉现已被开发者玩坏了,有机会会单独出一期,今日的主题是下载服务的运用,咱们回归主题。
二、DownloadManager的静默下载
哇,真的能下载了呢!好简略哦。可是你这么好Low啊,用户一看就知道我在干什么了,我想下载个资源包或插件那怎么办,总不能让用户看到我在下载吧。
万一悄悄的下载点东西干点坏事,不是搞得咱们都知道了。啊,你这个告诉栏也太丑了,只能设置Title Content,又不能定制UI,抛弃!
(下载的时分告诉栏的样式是由厂商或体系决议的)
放心,都能够结束的!DownloadManager 其实能够设置不运用告诉栏的。
那我怎么知道进展和状况?其实 DownloadManager 内部有 Query 能够查询这些状况的。那咱们结束一个悄悄的静默下载逻辑看看。
private val scheduledExecutorService: ScheduledExecutorService = Executors.newScheduledThreadPool(3)
private fun startDownLoad() {
//下载链接 这儿下载手机B站为示例
val downloadUrl = "https://dl.hdslb.com/mobile/latest/iBiliPlayer-html5_app_bili.apk"
val fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1)
//这儿下载到指定的目录,咱们存在公共目录下的download文件夹下
val fileUri = Uri.fromFile(
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
System.currentTimeMillis().toString() + "-" + fileName
)
)
//开端构建 DownloadRequest 方针
val request = DownloadManager.Request(Uri.parse(downloadUrl))
//下载时分隐藏告诉栏
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)
//指定下载的文件类型为APK
request.setMimeType("application/vnd.android.package-archive")
// request.addRequestHeader() //还能参加恳求头
// request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) //能指定下载的网络
//指定下载到本地的途径(能够指定URI)
request.setDestinationUri(fileUri)
//开端构建 DownloadManager 方针
val downloadManager = commContext().getSystemService(DOWNLOAD_SERVICE) as DownloadManager
//参加Request到体系下载行列,在条件满意时会主动开端下载。回来的为下载使命的仅有ID
val requestID = downloadManager.enqueue(request)
//注册获取进展的监听
YYLogUtils.w("开端下载:fileUri:$fileUri requestID:$requestID")
//每秒守时改写一次
val command = Runnable {
getBytesAndStatus(requestID)
}
scheduledExecutorService.scheduleAtFixedRate(command, 0, 1, TimeUnit.SECONDS)
//注册下载使命结束的监听
commContext().registerReceiver(object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
//现已结束
if (intent.action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
//解绑进展监听
scheduledExecutorService.shutdown()
//获取下载ID
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
val uri = downloadManager.getUriForDownloadedFile(id)
YYLogUtils.w("下载结束了- uri:$uri")
installApk(uri)
} else if (intent.action.equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
//假如还未结束下载,跳转到下载中心
YYLogUtils.w("跳转到下载中心")
val viewDownloadIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
viewDownloadIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(viewDownloadIntent)
}
}
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
//获取当时进展,和总进展
private fun getBytesAndStatus(downloadId: Long) {
val query = DownloadManager.Query().setFilterById(downloadId)
var cursor: Cursor? = null
val downloadManager = commContext().getSystemService(DOWNLOAD_SERVICE) as DownloadManager
try {
cursor = downloadManager.query(query)
if (cursor != null && cursor.moveToFirst()) {
// //Notification 标题
// val title = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE))
// //描述
// val description = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION))
val downloaded = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val total = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val progress = downloaded * 100 / total
YYLogUtils.w("当时下载巨细:$downloaded 总共巨细:$total")
}
} finally {
cursor?.close()
}
}
private fun installApk(uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setDataAndType(uri, "application/vnd.android.package-archive")
startActivity(intent)
}
留意点:
- 一定要设置 VISIBILITY_HIDDEN 才干不显示告诉栏
- 假如高版别设置 VISIBILITY_HIDDEN 报错,需求设置权限
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
- 咱们运用 Query 来查询下载的状况,假如要监听下载进展,咱们运用守时使命即可,比方每一秒查询一次。(这儿的守时使命能够以恣意方式来结束)
这样咱们就能够结束和运用内部OkHttp来下载相同的作用啦。
告诉栏不能自界说UI?现在咱们是静默下载了,你想弹窗展现进展,布局展现进展,告诉栏展现进展,自界说告诉栏什么的,只需拿到下载的进展,那不是任你揉搓了!属实是想怎么玩就怎么玩了。
总结
DownloadManager 相同很灵活 ,其实他提供了许多 Api 。咱们能够运用它结束各种定制化的下载需求。(比方断点续传,从头下载等),如有有需求,咱们能够根据 DownloadManager 结束一个下载的结构。
我觉得 DownloadManager 比照其他的类似OkHttp这样的下载结构,最大的一个长处是体系服务,因为它是体系服务,只需咱们的App开启了一个下载使命,那么退出App,这个下载使命相同能继续下载,而运用OkHttp下载就算放在前台Service中,也是有几率挂掉的,而 DownloadManager 则不会。
当然两种方案都是能够用的,看不同的运用场景了,让我选的话,假如我做的运用是多媒体类型的,有许多的行列并发下载,并检查媒体文件之类的,我或许会运用 okdownload ,可是假如我做的就是很一般的运用,大量并发下载的场景不多,我或许就会运用DownloadManager结束了。
一起咱们能够根据体系服务进行一些联动,比方咱们之前讲到的 WorkManager 。每12小时检查一下远程的资源与版别,咱们就能够搭配 DownloadManager 在后台悄悄的下载资源与插件。并且他们都支撑指定Wifi环境下的下载。简直完美。
想测验的同学能够看看代码,运转一下,源码在此。
最后吐槽一句,DownloadManager 可比 坑爹的 LocationManager 好用多了。
好了,我如有解说不到位或讹夺的地方,期望同学们能够指出交流。
假如感觉本文对你有一点点点的启发,还望你能点赞
支撑一下,你的支撑是我最大的动力。
Ok,这一期就此结束。