实现一个多任务异步下载器,对于协程,线程,多任务理解,有很大帮助
系列文章2篇:
(一)大型异步下载器:基于kotlin+Compose+协程+Flow+Channel实现多文件异步同时分片断点续传下载
(二)大型异步下载器(二):基于kotlin+Compose+协程+Flow+Channel+ OKhttp 实现多文件异步同时分片断点续传下载
一、前言
前一篇文章分享了大型异步下载器:基于kotlin+Compose+协程+Flow+Channel 实现多文件异步同时分片断点续传下载 实现的思路
本篇文章在之前的基础上:
- 做了代码优化,
- 加入了,初始化进度条,文件总长度的保存,
- 增加相关默认配置参数
- 增加下载实现,可选
HttpURLConnection来实现,也可选Okhttp实现,也可自行定义其他的来实现 UI可以不用compose
- 项目已经开源
WX-Download - 支持特性
- 1、支持断点续传
- 2、支持暂停
- 3、支持多个文件同时下载
- 4、支持控制最大下载文件并发数
- 5、支持超过最大下载文件数后队列等待
- 6、支持配置每个文件分片下载
- 7、支持控制配置文件最小分片的文件大小
- 8、支持切换原生实现下载,或者okhttp实现,或者自定义扩展其他方式实现下载
- 9、支持离线初始化上次下载进度
- 10、支持控制最小更新下载进度的下载大小值
- 11、支持读取不到文件大小contentLengthLong时候,采用流的方式读取
- 12、支持扩展UI方使用原生和Compose
- 13、支持在
viewModelScope和lifecycleScope作用域下开始下载和生命周期相关联 - 14、支持在
Service下自定义协程作用域下开始下载和Service生命周期保持一致
github项目地址
gitee项目地址
二、 初始化进度条,文件总长度的保存
-
断点续传,如果下载中途,断掉,或者手动暂停,下次来继续点击下载,从前面文章思路里面可以知道,我们每次下载之前,需要先从服务器获取到文件长度,然后更加文件长度来对文件进行分片下载。我们可以先如果我们第一次获取到了文件长度,中途中断,我们把文件长度保存起来,下次进去,便可以直接从本地读取文件长度。这样要快了一些。
-
如果第一次下载,不管是由于手动还是由于网络原因导致中断了,下一次进来,我们要立马看到下载按钮的进度条上次已经下载了多少,这个操作便是初始化下载进度。如果不保存文件总大小,我们只能再次获取到文件长度,才能计算出已经下载的部分的百分比,即便这样也要再次从服务端获取,如果我们是个下载列表,那样要同时去调用网络获取很多次才行。这样的设计肯定不行,所以我们把下载文件总长度保存起来,方便后面每次进入初始化计算进度立马就能UI展示出来。
- 这里选择直接用一个文件保存起来,用它
RandomAccessFile去写入。 RandomAccessFile有相关方法:writeLong:用来保存文件总大小,writeBoolean:用来保存下载文件是否支持断点续传,是否支持断点续传是看文件服务器上面是否支持,所以保存下来。writeInt:用来保存单个文件下载的分片数量,前面文章有提到过,如果服务器上面要下载的文件大小,小于我们最小配置的分片大小,强制就用一个线程来下载。所以这块最终由获取文件大小之后来决定的。writeLong:同时用它用来保存每个分片文件已经下载的开始位置,
- 这里需要对
RandomAccessFile有相关理解
以下是RandomAccessFile可以读写的几种基本数据类型以及它们在文件中的字节表示大小:
- byte (
readByte(),writeByte(int)):1个字节。 - boolean (
readBoolean(),writeBoolean(boolean)):1个字节,但在文件中以0(false)或1(true)表示。 - char (
readChar(),writeChar(int)):2个字节,使用Unicode编码。 - short (
readShort(),writeShort(int)):2个字节。 - int (
readInt(),writeInt(int)):4个字节。 - long (
readLong(),writeLong(long)):8个字节。 - float (
readFloat(),writeFloat(float)):4个字节。 - 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)
五、总结
本篇文章重点介绍了:之前的基础上:
- 做了代码优化,
- 加入了,初始化进度条,文件总长度的保存,
- 增加相关默认配置参数
- 增加下载实现,可选
HttpURLConnection来实现,也可选Okhttp实现,也可自行定义其他的来实现 UI可以不用compose