移除MANAGE_EXTERNAL_STORAGE后Multipart文件上传失败求助
问题分析与解决方案
直接通过ACTION_GET_CONTENT获取的文件路径创建File对象访问,在Android 10(API 29)及以上的分区存储机制下会触发权限拒绝——即使申请了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE也不行。这是因为:
- 分区存储限制了APP直接通过文件路径访问外部存储的非媒体/非下载目录文件;
- 移除
MANAGE_EXTERNAL_STORAGE后,APP失去了全局访问外部存储的特殊权限,无法绕过分区存储的限制。
正确的做法是通过ContentResolver获取文件输入流,直接基于输入流构建上传用的RequestBody,而非操作原始文件路径。
完整代码实现
1. 处理文件选择回调
在resultLauncher的回调中,直接使用返回的Uri而非解析路径:
ActivityResultLauncher<Intent> resultLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { Uri fileUri = result.getData().getData(); // 基于Uri创建Multipart上传部件 MultipartBody.Part filePart = createMultipartFromUri(this, fileUri, "file"); // 执行上传逻辑... } } );
2. 基于Uri构建Multipart部件
自定义RequestBody从Uri读取文件流,避免直接操作文件路径:
private MultipartBody.Part createMultipartFromUri(Context context, Uri uri, String partName) { String fileName = getFileName(context, uri); RequestBody requestBody = RequestBody.create( new InputStreamRequestBody(context, uri), MediaType.parse("application/pdf") ); return MultipartBody.Part.createFormData(partName, fileName, requestBody); } // 自定义RequestBody:从Uri获取输入流并写入上传流 private static class InputStreamRequestBody extends RequestBody { private final Context context; private final Uri uri; InputStreamRequestBody(Context context, Uri uri) { this.context = context; this.uri = uri; } @Override public MediaType contentType() { // 从ContentResolver获取文件真实MIME类型 ContentResolver resolver = context.getContentResolver(); return MediaType.parse(resolver.getType(uri)); } @Override public void writeTo(BufferedSink sink) throws IOException { try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { if (inputStream == null) { throw new IOException("无法打开文件流: " + uri); } sink.writeAll(Okio.source(inputStream)); } } } // 辅助方法:从Uri获取文件名 private String getFileName(Context context, Uri uri) { String fileName = "unknown.pdf"; ContentResolver resolver = context.getContentResolver(); Cursor cursor = resolver.query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); if (cursor != null && cursor.moveToFirst()) { int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (nameIndex != -1) { fileName = cursor.getString(nameIndex); } cursor.close(); } return fileName; }
关键说明
- 通过
ContentResolver.openInputStream(uri)获取文件流是Android官方推荐的访问用户选择文件的方式,不受分区存储权限限制(用户主动选择文件后,APP获得临时访问权限); - 自定义
InputStreamRequestBody直接将文件流写入上传缓冲区,无需复制文件到APP内部存储,更高效; - Android 13及以上版本,访问PDF等文档类文件无需申请
READ_EXTERNAL_STORAGE权限,ACTION_GET_CONTENT的临时权限已足够。
内容的提问来源于stack exchange,提问作者Shadab Azam
相关产品推荐
相关产品推荐

