在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,直接安装会闪退,所以说我们加入这条权限,就能跳转到一个让用户手动允许安装未知来源的界面。
自动更新
(流程图练手)
后话
因为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就丢失了。
最后
基本内容就这么多了,可能有些地方写得不好,有不正确的地方欢迎大家指出