You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 07:23:15