大型异步下载器(二):基于kotlin+Compose+协程+Flow+Channel+ OKhttp 实现多文件异步同时分片断点续传下载

1,372 阅读6分钟

ezgif-8eaac153aa4e9a.gif

实现一个多任务异步下载器,对于协程,线程,多任务理解,有很大帮助

系列文章2篇:
(一)大型异步下载器:基于kotlin+Compose+协程+Flow+Channel实现多文件异步同时分片断点续传下载
(二)大型异步下载器(二):基于kotlin+Compose+协程+Flow+Channel+ OKhttp 实现多文件异步同时分片断点续传下载

一、前言

前一篇文章分享了大型异步下载器:基于kotlin+Compose+协程+Flow+Channel 实现多文件异步同时分片断点续传下载 实现的思路

本篇文章在之前的基础上:

  1. 做了代码优化,
  2. 加入了,初始化进度条,文件总长度的保存,
  3. 增加相关默认配置参数
  4. 增加下载实现,可选HttpURLConnection来实现,也可选Okhttp实现,也可自行定义其他的来实现
  5. UI可以不用compose
  • 项目已经开源 WX-Download
  • 支持特性
  • 1、支持断点续传
  • 2、支持暂停
  • 3、支持多个文件同时下载
  • 4、支持控制最大下载文件并发数
  • 5、支持超过最大下载文件数后队列等待
  • 6、支持配置每个文件分片下载
  • 7、支持控制配置文件最小分片的文件大小
  • 8、支持切换原生实现下载,或者okhttp实现,或者自定义扩展其他方式实现下载
  • 9、支持离线初始化上次下载进度
  • 10、支持控制最小更新下载进度的下载大小值
  • 11、支持读取不到文件大小contentLengthLong时候,采用流的方式读取
  • 12、支持扩展UI方使用原生和Compose
  • 13、支持在viewModelScopelifecycleScope作用域下开始下载和生命周期相关联
  • 14、支持在Service下自定义协程作用域下开始下载和Service生命周期保持一致
github项目地址
gitee项目地址

二、 初始化进度条,文件总长度的保存

  1. 断点续传,如果下载中途,断掉,或者手动暂停,下次来继续点击下载,从前面文章思路里面可以知道,我们每次下载之前,需要先从服务器获取到文件长度,然后更加文件长度来对文件进行分片下载。我们可以先如果我们第一次获取到了文件长度,中途中断,我们把文件长度保存起来,下次进去,便可以直接从本地读取文件长度。这样要快了一些。

  2. 如果第一次下载,不管是由于手动还是由于网络原因导致中断了,下一次进来,我们要立马看到下载按钮的进度条上次已经下载了多少,这个操作便是初始化下载进度。如果不保存文件总大小,我们只能再次获取到文件长度,才能计算出已经下载的部分的百分比,即便这样也要再次从服务端获取,如果我们是个下载列表,那样要同时去调用网络获取很多次才行。这样的设计肯定不行,所以我们把下载文件总长度保存起来,方便后面每次进入初始化计算进度立马就能UI展示出来。

  • 这里选择直接用一个文件保存起来,用它 RandomAccessFile 去写入。
  • RandomAccessFile有相关方法:
  • writeLong:用来保存文件总大小,
  • writeBoolean:用来保存下载文件是否支持断点续传,是否支持断点续传是看文件服务器上面是否支持,所以保存下来。
  • writeInt:用来保存单个文件下载的分片数量,前面文章有提到过,如果服务器上面要下载的文件大小,小于我们最小配置的分片大小,强制就用一个线程来下载。所以这块最终由获取文件大小之后来决定的。
  • writeLong:同时用它用来保存每个分片文件已经下载的开始位置,
  • 这里需要对RandomAccessFile有相关理解
    以下是RandomAccessFile可以读写的几种基本数据类型以及它们在文件中的字节表示大小:
  1. byte (readByte()writeByte(int)):1个字节。
  2. boolean (readBoolean()writeBoolean(boolean)):1个字节,但在文件中以0(false)或1(true)表示。
  3. char (readChar()writeChar(int)):2个字节,使用Unicode编码。
  4. short (readShort()writeShort(int)):2个字节。
  5. int (readInt()writeInt(int)):4个字节。
  6. long (readLong()writeLong(long)):8个字节。
  7. float (readFloat()writeFloat(float)):4个字节。
  8. double (readDouble()writeDouble(double)):8个字节。

我们用一个临时文件来存取:
writeLong先存long型文件大小(8个字节),
再用writeBoolean存取Boolean来存取文件是否支持断点续传(1个字节),
再用writeInt来存取Int(4个字节)单个文件分片数量,
如果我们分2片来存取每片已经下载到的位置,
那么第一片存取时候需要先移动到(13+8X0)位置 (tempFile.seek(13L + 8 * i)
那边第二篇存取时候需要先移动到(13+8X1)位置 (tempFile.seek(13L + 8 * i)

三、增加相关默认配置参数

  • 默认配置最大同时下载文件数:3
  • 断点续传分片最小大小,小于它没有必要分片 :1024 * 1024 * 1L
  • 断点续传分片最小大小,最小下载了100k才更新进度:1024 * 100 * 1L

四、使用方法:

1、repositories中添加如下maven

       repositories {
            maven { url 'https://repo1.maven.org/maven2/' }
            maven { url 'https://s01.oss.sonatype.org/content/repositories/releases/' }
        }
    }

2、 dependencies中添加依赖

    implementation("io.github.wgllss:Wgllss-Download:1.0.01")

3、viewModel中使用

  • 初始化 最大并行同时下载文件个数
  • 监听文件下载状态 及下载进度
  • 初始化上次没有下载完的文件下载进度
    /** 初始化 最大并行同时下载文件个数 **/
    downloadManager.downloadInit(viewModelScope, 3)

    /** 监听文件下载状态 及下载进度 **/
    downloadManager.downloadStatusFlow().onEach {
        when (it) {
            is WXState.None -> {

            }

            is WXState.Downloading -> {
                _datas.value!![it.which].progress.value = it.progress
            }

            is WXState.Pause -> {}
            is WXState.Failed -> {}
            is WXState.Succeed -> {
                _datas.value!![it.which].progress.value = 100f
            }

            is WXState.Waiting -> {}
        }
    }.launchIn(viewModelScope)

    /** 初始化上次没有下载完的文件下载进度 **/
    downloadDatas.forEachIndexed { index, url ->
        var fileSaveName = url.substring(url.lastIndexOf("?") + 1, url.length)
           downloadManager.initTempFilePercent(viewModelScope, index, strDownloadDir, fileSaveName)
    }

4、下载调用 which:代表下载的那一个url,通常为下载列表中的位置

    fun download(which: Int) {
        val url = downloadDatas[which]
        var fileSaveName = url.substring(url.lastIndexOf("?") + 1, url.length)
        if (which == 0) fileSaveName = "blue.apk"
        val fileAsyncNumb = 2
        downloadManager.download(viewModelScope, which, url, strDownloadDir, fileSaveName, fileAsyncNumb, true)
    }

5、暂停下载

downloadManager.downloadPause(which)

五、总结

本篇文章重点介绍了:之前的基础上:

  1. 做了代码优化,
  2. 加入了,初始化进度条,文件总长度的保存,
  3. 增加相关默认配置参数
  4. 增加下载实现,可选HttpURLConnection来实现,也可选Okhttp实现,也可自行定义其他的来实现
  5. UI可以不用compose

感谢阅读:

欢迎用你发财的小手 关注,点赞、收藏

这里你会学到不一样的东西