如何让Ktor实现并行流式下载?当前先存内存再写入文件
解决Ktor下载文件先存内存再写入的问题并实现并行下载
一、先实现流式下载(边下载边写入文件)
你当前的代码会先把整个文件加载到内存再写入,是因为httpClient.request()默认会等待完整响应返回。改用HttpStatement可以实现流式处理,边接收数据边写入文件,避免内存占用过高,同时解决前期无响应的问题:
val httpClient = HttpClient(OkHttp) // 或Android引擎 val targetFile = File(cacheDir, "test.foo") httpClient.prepareRequest { url("http://example.com/large_file.dat") }.execute { response -> response.bodyAsChannel().copyAndClose(targetFile.writeChannel()) }
prepareRequest返回HttpStatement,execute会在响应开始接收时就回调,而非等整个响应完成,这样就能实时把数据写入文件。
二、实现并行分块下载
并行下载需要借助HTTP的Range请求头,将文件拆分为多个块同时下载,最后合并成完整文件,步骤如下:
1. 获取文件总大小
先发送HEAD请求获取文件的总大小,确认服务器支持分块下载(响应头需包含Accept-Ranges: bytes):
val fileUrl = "http://example.com/large_file.dat" val fileSize = httpClient.head(fileUrl) { onSuccess { response -> response.headers["Content-Length"]?.toLong() ?: 0L } }
2. 分块并启动多协程并行下载
定义单块大小(比如1MB),计算块的区间,为每个块启动独立协程下载:
val chunkSize = 1024 * 1024 // 1MB每块 val chunks = (0..fileSize step chunkSize).map { start -> val end = minOf(start + chunkSize - 1, fileSize - 1) start to end } // 创建临时文件存储每个块 val tempChunkFiles = chunks.mapIndexed { index, _ -> File(cacheDir, "temp_chunk_$index") } // 并行下载所有块 coroutineScope { chunks.forEachIndexed { index, (start, end) -> launch { httpClient.prepareRequest { url(fileUrl) header("Range", "bytes=$start-$end") }.execute { response -> response.bodyAsChannel().copyAndClose(tempChunkFiles[index].writeChannel()) } } } }
3. 合并块文件为完整文件
所有块下载完成后,按顺序合并到目标文件,最后清理临时文件:
val targetFile = File(cacheDir, "test.foo") targetFile.outputStream().use { output -> tempChunkFiles.forEach { chunkFile -> chunkFile.inputStream().use { input -> input.copyTo(output) } chunkFile.delete() } }
额外注意事项
- 若服务器不支持
Range请求,分块下载会失效,需降级为普通流式下载。 - 需处理下载失败的场景:比如单个块下载失败时,要重新发起该块的下载请求。
- 可根据设备性能调整块大小和并发协程数量,避免资源过度占用。
内容的提问来源于stack exchange,提问作者AndreKR
相关产品推荐
相关产品推荐

