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

移除MANAGE_EXTERNAL_STORAGE后Multipart文件上传失败求助

问题分析与解决方案

直接通过ACTION_GET_CONTENT获取的文件路径创建File对象访问,在Android 10(API 29)及以上的分区存储机制下会触发权限拒绝——即使申请了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE也不行。这是因为:

  1. 分区存储限制了APP直接通过文件路径访问外部存储的非媒体/非下载目录文件;
  2. 移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:11:13