携手创造,共同成长!这是我参与「日新计划 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))
    }

注释的很详细,步骤如下:

  1. 咱们封装一个 Request 方针设置下载的链接Uri,设置下载到的方针文件夹,设置是否需求展现告诉等。
  2. 构建 DownloadManager 服务,把 Request 使命放入行列,假如满意条件即可生效。
  3. 一般来说咱们都期望下载结束之后能处理一些工作,咱们就需求监听结束的播送(非有必要的)。

这儿需求留意的是:

  1. 或许需求恳求SD卡权限,
  2. 假如下载是公共目录,在Android12以上只有download等少量文件夹是敞开的,其他的文件夹或许无法拜访。
  3. 假如下载的是沙盒目录,你无需恳求SD卡权限,可是假如外部运用想要拜访到此文件,需求界说FileProvider提供给对方运用(比方Apk装置)

结束的作用:

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?

咱们下载的是一个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,所以需求赞同不知道的装置包装置权限

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?

一系列的操作就装置成功了。

不可!我不能让我的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)
    }

留意点:

  1. 一定要设置 VISIBILITY_HIDDEN 才干不显示告诉栏
  2. 假如高版别设置 VISIBILITY_HIDDEN 报错,需求设置权限
 <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
  1. 咱们运用 Query 来查询下载的状况,假如要监听下载进展,咱们运用守时使命即可,比方每一秒查询一次。(这儿的守时使命能够以恣意方式来结束)

这样咱们就能够结束和运用内部OkHttp来下载相同的作用啦。

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?

告诉栏不能自界说UI?现在咱们是静默下载了,你想弹窗展现进展,布局展现进展,告诉栏展现进展,自界说告诉栏什么的,只需拿到下载的进展,那不是任你揉搓了!属实是想怎么玩就怎么玩了。

总结

DownloadManager 相同很灵活 ,其实他提供了许多 Api 。咱们能够运用它结束各种定制化的下载需求。(比方断点续传,从头下载等),如有有需求,咱们能够根据 DownloadManager 结束一个下载的结构。

我觉得 DownloadManager 比照其他的类似OkHttp这样的下载结构,最大的一个长处是体系服务,因为它是体系服务,只需咱们的App开启了一个下载使命,那么退出App,这个下载使命相同能继续下载,而运用OkHttp下载就算放在前台Service中,也是有几率挂掉的,而 DownloadManager 则不会。

当然两种方案都是能够用的,看不同的运用场景了,让我选的话,假如我做的运用是多媒体类型的,有许多的行列并发下载,并检查媒体文件之类的,我或许会运用 okdownload ,可是假如我做的就是很一般的运用,大量并发下载的场景不多,我或许就会运用DownloadManager结束了。

一起咱们能够根据体系服务进行一些联动,比方咱们之前讲到的 WorkManager 。每12小时检查一下远程的资源与版别,咱们就能够搭配 DownloadManager 在后台悄悄的下载资源与插件。并且他们都支撑指定Wifi环境下的下载。简直完美。

想测验的同学能够看看代码,运转一下,源码在此。

最后吐槽一句,DownloadManager 可比 坑爹的 LocationManager 好用多了。

好了,我如有解说不到位或讹夺的地方,期望同学们能够指出交流。

假如感觉本文对你有一点点点的启发,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

下载需求集成第三方?Android原生下载服务DownloadManager不可吗?