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

如何使用Retrofit Multipart上传500MB以上大文件并解决OOM问题

根因分析

你遇到的所有OOM问题的核心原因是将整个大文件的内容一次性加载到了应用堆内存中:inputStream.readBytes()、ByteArray.copyOfRange这类操作都会把500MB级的文件全量/多份拷贝到Java堆中,而安卓应用的堆上限通常只有几百MB,即便开启largeHeap也无法承载500MB级的全量文件加载。Postman可以正常上传大文件的原因是它采用流式传输逻辑,不会将整个文件加载到内存,只会在传输时读取当前需要的缓冲区内容。

解决方案

无需获取文件本地路径,也无需创建临时文件,直接基于文件选择返回的Uri实现流式RequestBody即可,OkHttp原生支持流式传输,不会加载全量文件到内存。

第一步:实现流式UriRequestBody

import android.content.ContentResolver
import android.net.Uri
import android.provider.OpenableColumns
import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import okio.source
import java.io.IOException

class UriRequestBody(
    private val contentResolver: ContentResolver,
    private val uri: Uri,
    private val fileName: String
) : RequestBody() {

    override fun contentType(): MediaType? {
        // 从Uri直接获取文件MIME类型
        return contentResolver.getType(uri)?.toMediaTypeOrNull()
            ?: "application/octet-stream".toMediaTypeOrNull()
    }

    override fun contentLength(): Long {
        // 从ContentResolver查询文件大小,无需读取文件内容
        return try {
            contentResolver.query(uri, null, null, null, null)?.use { cursor ->
                val sizeIndex = cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)
                cursor.moveToFirst()
                cursor.getLong(sizeIndex)
            } ?: super.contentLength()
        } catch (e: Exception) {
            super.contentLength()
        }
    }

    @Throws(IOException::class)
    override fun writeTo(sink: BufferedSink) {
        // 流式读写,每次只读取缓冲区大小的内容,不会加载全量文件到内存
        contentResolver.openInputStream(uri)?.use { inputStream ->
            inputStream.source().use { source ->
                sink.writeAll(source)
            }
        } ?: throw IOException("无法打开指定Uri对应的文件")
    }

    fun getFileName(): String = fileName
}

第二步:改造上传逻辑

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.CoroutineScope
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.TimeUnit
import okhttp3.Protocol

private fun uploadLargeFile(uri: Uri, fileName: String) {
    // 不要用GlobalScope,建议替换为页面绑定的ViewModel的viewModelScope
    CoroutineScope(Dispatchers.IO).launch {
        val client = OkHttpClient.Builder()
            .protocols(listOf(Protocol.HTTP_1_1))
            .connectTimeout(10, TimeUnit.MINUTES)
            .readTimeout(10, TimeUnit.MINUTES)
            .writeTimeout(10, TimeUnit.MINUTES) // 大文件上传必须配置写超时
            .build()

        val multipartBody = MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart(
                "data",
                fileName,
                UriRequestBody(contentResolver, uri, fileName)
            )
            .build()

        val request = Request.Builder()
            .url("你的上传接口地址")
            .post(multipartBody)
            .build()

        runCatching {
            client.newCall(request).execute()
        }.onSuccess { response ->
            // 处理上传成功逻辑
            println("上传响应:${response.body?.string()}")
        }.onFailure { e ->
            // 处理上传失败逻辑
            e.printStackTrace()
        }
    }
}

第三步:文件选择回调中直接调用

private val filesReceiver =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val fileUri = result.data?.data ?: return@registerForActivityResult
            // 查询文件名
            val fileName = contentResolver.query(fileUri, null, null, null, null)?.use { cursor ->
                val nameIndex = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
                cursor.moveToFirst()
                cursor.getString(nameIndex)
            } ?: "unknown_file"
            // 直接调用上传,无需处理临时文件、路径转换
            uploadLargeFile(fileUri, fileName)
        }
    }

优化建议

如果后端支持分片上传,建议进一步改造为分片上传逻辑:每次只读4MB左右的文件内容上传,传完即释放内存,中途断网可续传,无需重传整个文件,稳定性更高。


内容的提问来源于stack exchange,提问作者Andrew

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 07:27:01