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

Android文件选择器读取大文件字节避免内存溢出方案

解决方案:流式分块读取大文件,避免内存溢出

你的问题根源在于一次性将整个大文件加载到内存中:readBytes()会把文件全部读入ByteArray,再转成String,150MB的文件会直接占用150MB以上的内存,远超Android应用的内存限制,即使开启largeHeap也无法解决。

核心解决思路是流式分块读取:只在内存中保留当前需要上传的分片,处理完就释放,不加载整个文件。结合你已有的分片上传逻辑,修改如下:

1. 修改文件读取逻辑,替换一次性读取为流式分块

直接通过ContentResolver的InputStream循环读取固定大小的字节块,每块处理后立即上传:

private val filesReceiver = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data = result.data
        data?.let { intentData ->
            val fileUri = intentData.data ?: return@let
            var name = ""
            var size: Long? = null

            // 保留原有的文件名、大小读取逻辑
            requireContext().contentResolver.query(fileUri, null, null, null, null)?.use { cursor ->
                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
                if (cursor.moveToFirst()) {
                    name = cursor.getString(nameIndex) ?: ""
                    size = cursor.getLong(sizeIndex)
                }
            }

            size?.takeIf { it > 0 }?.let { fileSize ->
                // 调用新的流式分片上传方法
                sendingFileStream(fileUri, name, fileSize)
            }
        }
    }
}

2. 新增流式分片处理方法

这个方法会逐块读取文件,每块转成你需要的ISO_8859_1编码字符串后,调用原有的sendFilePart上传:

private fun sendingFileStream(fileUri: Uri, name: String, size: Long) {
    sendingTime = System.currentTimeMillis() / 1000
    // 用协程把文件读取放到IO线程,避免阻塞UI
    lifecycleScope.launch(Dispatchers.IO) {
        requireContext().contentResolver.openInputStream(fileUri)?.use { inputStream ->
            val buffer = ByteArray(partSize) // 复用固定大小的缓冲区,减少内存分配
            var bytesRead: Int
            var currentPart = 0
            // 主线程显示对话框
            withContext(Dispatchers.Main) { dialog.show() }

            try {
                // 循环读取文件,直到读到末尾(返回-1)
                while (inputStream.read(buffer).also { bytesRead = it } != -1) {
                    // 仅将当前读取到的有效字节转成字符串
                    val partContent = String(buffer, 0, bytesRead, Charsets.ISO_8859_1)
                    sendFilePart(currentPart, partContent, name, size)
                    currentPart++
                }
            } finally {
                // 主线程隐藏对话框,清理状态
                withContext(Dispatchers.Main) { dialog.hide() }
                sendingTime = null
            }
        }
    }
}

3. 保留原有分片上传方法

你的sendFilePart方法无需修改,直接复用即可。

关键说明

  • 内存控制:内存中始终只保留一个分片大小的ByteArray(10000字节),不管文件多大,内存占用都不会飙升。
  • URI兼容性:通过ContentResolver读取content://格式的URI,避免了直接转File的格式异常问题(Android沙箱机制不允许直接访问其他应用的文件路径)。
  • UI线程优化:用协程将文件读取放到IO线程,对话框的显示/隐藏在主线程操作,避免UI卡顿。

如果你的WebSocket支持直接发送字节数组,还可以进一步优化:跳过字符串转换,直接发送ByteArray块,减少编码开销,效率更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 02:30:11