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

Android Q设备下如何利用Content Uri将文件发送至PHP MySQL服务器

解决Android Q上Content Uri无法上传文件到PHP MySQL服务器的问题

我之前在Android Q设备上也踩过这个坑!因为从Android Q(API 29)开始,Scoped Storage机制限制了直接访问外部存储的文件路径,你拿到的Content Uri没法像以前那样直接转成文件路径来上传。别慌,给你几个实用的解决方案:

方案1:将Content Uri对应的文件复制到应用私有目录后上传

应用对自己的私有目录(比如getCacheDir()或getFilesDir())拥有完全读写权限,我们可以先把Content Uri指向的文件复制到这个目录,再上传这个私有文件。

代码示例:

private File copyContentUriToPrivateDir(ContentResolver resolver, Uri contentUri) throws IOException {
    // 获取文件的MIME类型,用来命名临时文件(可选)
    String mimeType = resolver.getType(contentUri);
    String fileExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
    String fileName = "upload_temp." + (fileExtension != null ? fileExtension : "tmp");

    // 创建应用缓存目录下的临时文件
    File tempFile = new File(getCacheDir(), fileName);

    // 复制文件流
    try (InputStream inputStream = resolver.openInputStream(contentUri);
         OutputStream outputStream = new FileOutputStream(tempFile)) {
        byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区,提升复制效率
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
    }

    return tempFile;
}

使用方式:

在获取到Content Uri后(比如通过onActivityResult或AndroidX的ActivityResultContracts.GetContent),调用这个方法得到临时文件,然后用你熟悉的方式(比如HttpURLConnection、OkHttp)上传这个文件到PHP服务器即可。上传完成后记得删除临时文件,避免占用存储空间。

方案2:直接上传Content Uri的输入流(推荐)

这个方案更高效,不需要生成临时文件,直接把Content Uri对应的输入流传给HTTP请求体,完美适配Scoped Storage规则,适合大文件上传。

用OkHttp实现的代码示例:

// 1. 构建包含文件流的请求体
RequestBody multipartBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("file", "uploaded_file", // 这里的"file"对应PHP接口接收的参数名
                RequestBody.create(
                        MediaType.parse(getContentResolver().getType(contentUri)),
                        new InputStreamRequestBody(getContentResolver().openInputStream(contentUri))
                ))
        .build();

// 2. 发起上传请求
OkHttpClient client = new OkHttpClient();
Request uploadRequest = new Request.Builder()
        .url("你的PHP服务器上传接口地址")
        .post(multipartBody)
        .build();

client.newCall(uploadRequest).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // 处理上传失败逻辑,比如提示用户
        runOnUiThread(() -> Toast.makeText(getApplicationContext(), "上传失败", Toast.LENGTH_SHORT).show());
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            runOnUiThread(() -> Toast.makeText(getApplicationContext(), "上传成功", Toast.LENGTH_SHORT).show());
        } else {
            runOnUiThread(() -> Toast.makeText(getApplicationContext(), "上传失败:服务器错误", Toast.LENGTH_SHORT).show());
        }
    }
});

// 自定义RequestBody,用来包装输入流
static class InputStreamRequestBody extends RequestBody {
    private final InputStream inputStream;

    public InputStreamRequestBody(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public MediaType contentType() {
        return null; // 这里可以根据实际MIME类型设置,或者留空让OkHttp自动处理
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        // 用Okio的工具类把输入流写入到请求体
        sink.writeAll(Okio.source(inputStream));
    }
}

说明:

PHP服务器端可以像处理普通文件上传一样,通过$_FILES['file']获取上传的文件,不需要做额外修改。这个方案省去了文件复制的步骤,节省存储空间和时间。

方案3:获取媒体文件的真实路径(仅适用于媒体文件)

如果你的应用只处理图片、视频、音频这类媒体文件,可以尝试通过MediaStore获取真实路径,但注意这个方法在Android Q+下对非媒体文件可能返回null,而且即使拿到路径,你也不一定有读写权限,所以不推荐作为首选方案。

代码示例:

public String getMediaFilePath(ContentResolver resolver, Uri uri) {
    String[] projection = {MediaStore.MediaColumns.DATA};
    Cursor cursor = null;
    try {
        cursor = resolver.query(uri, projection, null, null, null);
        if (cursor != null && cursor.moveToFirst()) {
            int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
            return cursor.getString(columnIndex);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return null;
}

额外注意事项

  • Android Q及以上,通过系统文件选择器(ACTION_GET_CONTENT)获取的Content Uri,不需要申请READ_EXTERNAL_STORAGE权限,系统会自动授予临时访问权限。
  • 如果你使用Retrofit,可以参考方案2的思路,自定义RequestBody来包装输入流,实现类似的上传逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 14:57:40