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
相关产品推荐
相关产品推荐

