如何在Kotlin Multiplatform(Android/iOS)用Ktor保存或流式写入响应体到文件?
Kotlin Multiplatform + Ktor 响应体本地存储方案
1. 基础依赖配置
首先确保你的KMP项目中引入了必要的依赖:
- Common模块:
implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-io:0.3.1") - Android模块:
implementation("io.ktor:ktor-client-okhttp:$ktorVersion") - iOS模块:
implementation("io.ktor:ktor-client-darwin:$ktorVersion")
(替换$ktorVersion和$coroutinesVersion为你使用的版本号)
2. 平台通用文件路径处理
由于Android和iOS的文件系统差异,先通过expect/actual定义平台无关的文件路径获取工具:
CommonMain 定义接口
expect class FileHelper { suspend fun getLocalFile(fileName: String): File }
AndroidMain 实现
import android.content.Context import androidx.core.content.ContextCompat import java.io.File actual class FileHelper { actual suspend fun getLocalFile(fileName: String): File { val context = ContextCompat.getMainExecutor(AppContext).await() // 请替换为你的全局AppContext val privateDir = context.filesDir return File(privateDir, fileName) } }
注:AppContext可通过自定义Application类初始化全局上下文
iOSMain 实现
import platform.Foundation.NSFileManager import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSUserDomainMask import java.io.File actual class FileHelper { actual suspend fun getLocalFile(fileName: String): File { val docsDir = NSFileManager.defaultManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, url = null, create = true, error = null )?.path ?: error("Failed to get documents directory") return File("$docsDir/$fileName") } }
3. 直接保存响应体到本地文件(适合小文件)
通过Ktor直接读取响应体为ByteArray,一次性写入文件,操作简单但不适合大文件(易内存溢出):
import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get import java.io.File suspend fun downloadAndSaveFile( client: HttpClient, downloadUrl: String, targetFileName: String, fileHelper: FileHelper ) { val responseBytes = client.get(downloadUrl).body<ByteArray>() val targetFile = fileHelper.getLocalFile(targetFileName) targetFile.writeBytes(responseBytes) }
4. 流式写入响应体到文件(适合大文件)
使用Ktor的ByteReadChannel进行流式读取,配合kotlinx.io的sink()实现分块写入,避免内存占用过高,还能监听下载进度:
import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get import io.ktor.client.request.onDownload import kotlinx.io.sink import java.io.File suspend fun downloadAndStreamToFile( client: HttpClient, downloadUrl: String, targetFileName: String, fileHelper: FileHelper, onProgressUpdate: (bytesDownloaded: Long, totalBytes: Long?) -> Unit = { _, _ -> } ) { val targetFile = fileHelper.getLocalFile(targetFileName) client.get(downloadUrl) { onDownload { bytesSentTotal, contentLength -> onProgressUpdate(bytesSentTotal, contentLength) } }.apply { val byteChannel = body<ByteReadChannel>() targetFile.sink().use { fileSink -> byteChannel.copyTo(fileSink) } } }
使用示例
// 全局单例HttpClient val httpClient = HttpClient() // 初始化FileHelper val fileHelper = FileHelper() // 在协程中调用流式下载 scope.launch { downloadAndStreamToFile( client = httpClient, downloadUrl = "https://example.com/large-file.zip", targetFileName = "downloaded.zip", fileHelper = fileHelper ) { downloaded, total -> println("Downloaded: $downloaded / ${total ?: "Unknown"} bytes") } }
注意事项
- Android平台:示例使用应用私有目录,无需额外权限;若要保存到公共存储,需根据系统版本申请对应权限(如
READ_MEDIA_IMAGES)。 - iOS平台:若需访问Documents目录外的路径,需在
Info.plist中配置权限描述。 - 流式下载需在协程上下文执行,
copyTo为挂起函数,避免阻塞主线程。
内容的提问来源于stack exchange,提问作者Aleksei Tirman
相关产品推荐
相关产品推荐

