使用ContentResolver读取2GB+大文件时InputStream提前返回-1的问题
解决Android读取2GB以上文件时InputStream提前返回-1的问题
嘿,这个问题我之前踩过坑!根源在于Android系统里,某些由ContentResolver返回的InputStream实现(比如针对媒体库文件的),内部用了int类型来记录已读取的字节数——而int的最大值刚好是2^31-1(也就是约2147MB,接近2GB)。当你读取的字节数超过这个值时,内部计数器会溢出,导致read()错误地返回-1,误以为文件已经读完了。
解决方案:用FileChannel替代普通InputStream读取
FileChannel使用long类型处理文件位置和长度,完美支持超过2GB的大文件。具体步骤如下:
- 通过
ContentResolver获取ParcelFileDescriptor,而非直接打开InputStream - 从
ParcelFileDescriptor获取FileChannel - 基于
FileChannel分块读取文件,直到读取完所有字节
代码示例
// 假设你已经拿到了文件的Uri Uri fileUri = ...; String fileName = ...; // 文件名,用于multipart请求 String contentType = ...; // 文件MIME类型 try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(fileUri, "r"); FileChannel fileChannel = new FileInputStream(pfd.getFileDescriptor()).getChannel()) { long totalBytes = fileChannel.size(); long bytesReadSoFar = 0; byte[] buffer = new byte[8192]; // 8KB缓冲区,可根据需求调整大小 ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); // 初始化你的multipart请求输出流(比如用OkHttp的RequestBody或者HttpURLConnection的输出流) OutputStream multipartOutputStream = ...; while (bytesReadSoFar < totalBytes) { int bytesRead = fileChannel.read(byteBuffer); if (bytesRead == -1) { // 正常读取结束,跳出循环 break; } // 切换缓冲区为读模式 byteBuffer.flip(); // 将读取到的内容写入multipart请求体 multipartOutputStream.write(buffer, 0, bytesRead); // 重置缓冲区为写模式,准备下一次读取 byteBuffer.clear(); bytesReadSoFar += bytesRead; } // 记得关闭multipartOutputStream multipartOutputStream.close(); } catch (IOException e) { e.printStackTrace(); // 处理读取或上传异常 }
额外优化建议
- 用try-with-resources语法:自动关闭
ParcelFileDescriptor和FileChannel,避免资源泄漏 - 后台线程执行:大文件读取和上传一定要放在子线程(比如用
Coroutine、ExecutorService),绝对不能在主线程操作,否则会触发ANR - 结合网络库简化代码:如果你用OkHttp,可以自定义一个基于
FileChannel的RequestBody,让上传逻辑更简洁:RequestBody largeFileBody = new RequestBody() { @Override public MediaType contentType() { return MediaType.parse(contentType); } @Override public long contentLength() throws IOException { try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(fileUri, "r")) { return new FileInputStream(pfd.getFileDescriptor()).getChannel().size(); } } @Override public void writeTo(BufferedSink sink) throws IOException { try (ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(fileUri, "r"); FileChannel channel = new FileInputStream(pfd.getFileDescriptor()).getChannel()) { long bytesWritten = 0; long total = contentLength(); ByteBuffer buffer = ByteBuffer.allocate(8192); while (bytesWritten < total) { int read = channel.read(buffer); if (read == -1) break; buffer.flip(); sink.write(buffer); buffer.clear(); bytesWritten += read; } } } }; // 构建multipart请求 RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", fileName, largeFileBody) .build();
核心思路就是避开int类型的限制,改用支持长整型偏移量的IO组件来处理大文件。这样就能完美解决2GB以上文件读取提前结束的问题了。
内容的提问来源于stack exchange,提问作者DirtyJoe
相关产品推荐
相关产品推荐

