如何使用 Ktor 和 Kotlin 下载带有进度指示器的大文件?
Posted
技术标签:
【中文标题】如何使用 Ktor 和 Kotlin 下载带有进度指示器的大文件?【英文标题】:How can I download a large file with Ktor and Kotlin with a progress indicator? 【发布时间】:2021-03-12 21:59:11 【问题描述】:我花了太多时间试图解决这个问题。因此,我在下面发布的代码在下载文件方面确实有效,但问题是,流程具有非常意外的行为。 response.content.readAvailable()
方法调用似乎会阻塞,直到它完全完成下载整个文件,此时会发生发射进度,所以你最终要等待很长时间才能下载文件,然后在一瞬间你得到所有进度更新。所以我想知道是否有办法做到这一点,我一次读取一定数量的字节,然后发出一个进度,然后重复直到文件完成下载?或者可能是一种挂钩 readAvailable() 方法并以这种方式更新进度的方法?对此的任何帮助将不胜感激。
这是我找到并修改的代码,但仍然无法正常工作:
suspend fun HttpClient.downloadFile(
output: File,
downloadUrl: String,
md5Hash: String,
) = flow
try
val response = get<HttpResponse> url(downloadUrl)
val data = ByteArray(response.contentLength()?.toInt() ?: 0)
val contentLn = response.contentLength()?.toInt() ?: 0
var offset = 0
var bytesRemaining = contentLn
do
val chunkSize = min(maxChunkSize, bytesRemaining)
logger?.d "Read Available:"
val result = response.content.readAvailable(data, offset, length = chunkSize)
val progress = ((offset / contentLn.toDouble()) * 100).toInt()
emit(DownloadResult.Progress(progress))
logger?.d "logged progress: $progress"
// delay(6000L) this was to test my assumption that the readAvalible was blocking.
offset += chunkSize
bytesRemaining -= chunkSize
while (result != -1)
if (response.status.isSuccess())
if (data.md5().hex == md5Hash)
output.write(data)
emit(DownloadResult.Success)
else
emit(DownloadResult.ErrorCorruptFile)
else
emit(DownloadResult.ErrorBadResponseCode(response.status.value))
catch (e: TimeoutCancellationException)
emit(DownloadResult.ErrorRequestTimeout("Connection timed out", e))
【问题讨论】:
【参考方案1】:最后经过一段愚蠢的时间后,我解决了这个问题。您需要使用的是this。这使您可以在下载时访问字节通道。
一个非常粗略的实现(我还没有完成)是这样的:
get<HttpStatement>(url = downloadUrl).execute
var offset = 0
val byteBufferSize = 1024 * 100
val channel = it.receive<ByteReadChannel>()
val contentLen = it.contentLength()?.toInt() ?: 0
val data = ByteArray(contentLen)
do
val currentRead = channel.readAvailable(data, offset, byteBufferSize)
val progress = if(contentLen == 0) 0 else ( offset / contentLen.toDouble() ) * 100
logger?.d "progress: $progress"
offset += currentRead
while (currentRead >= 0)
此解决方案不能做两件事。 1.) 我在 HttpClient 的上下文中,所以这就是我访问 get() 的方式。 2.) 我正在创建一个大小为1024 * 100
的字节缓冲区,以便不让readAvailable
方法阻塞太久,尽管这可能不是必需的......关于它的一个好处是它决定了如何您会经常发布进度更新。
【讨论】:
以上是关于如何使用 Ktor 和 Kotlin 下载带有进度指示器的大文件?的主要内容,如果未能解决你的问题,请参考以下文章
在带有 Postgresql 数据库的 docker 容器中运行简单的 Kotlin Ktor 应用程序
在 Ktor 中使用 kotlin Reified 进行通用 api 调用
Kotlin Ktor 客户端 mltiplatform gradle 配置