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

如何通过文件选择器返回的Uri打开并移动文件?

解决Content Uri转File对象失败并移动文件的问题

你遇到的核心问题是:通过ACTION_GET_CONTENT获取的Uri是Content Uri(格式如/document/856),它指向的是内容提供者(比如MediaStore或DocumentsProvider)中的资源,并非直接对应文件系统的绝对路径。直接用uploadfileuri.getPath()创建File对象自然找不到文件——因为这个路径只是内容提供者内部的标识,不是真实的文件路径。

下面给你两种可行的解决方案,核心思路是通过ContentResolver读取文件内容,再写入到应用内部存储的目标位置:

方案一:通过IO流拷贝文件(通用所有Android版本)

这是最稳妥的方式,不需要依赖真实文件路径,直接通过内容提供者的输入流读取文件,再写入到你的目标目录:

步骤1:修改onActivityResult的处理逻辑

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CHOOSE_FILE && resultCode == Activity.RESULT_OK && data != null) {
        Uri uploadFileUri = data.getData();
        if (uploadFileUri == null) {
            Log.e(TAG, "获取的Uri为空");
            return;
        }

        // 1. 准备目标目录和文件
        File targetDir = new File(getFilesDir(), "bluetoothchat_files");
        if (!targetDir.exists()) {
            boolean isDirCreated = targetDir.mkdirs();
            if (!isDirCreated) {
                Log.e(TAG, "目标目录创建失败");
                return;
            }
        }
        // 获取原文件的文件名(避免硬编码README.txt)
        String fileName = getFileName(uploadFileUri);
        File targetFile = new File(targetDir, fileName);

        // 2. 拷贝文件内容
        try (InputStream inputStream = getContentResolver().openInputStream(uploadFileUri);
             OutputStream outputStream = new FileOutputStream(targetFile)) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            Log.i(TAG, "文件已成功复制到应用内部存储");

            // 3. (可选)尝试删除原文件(注意权限限制)
            // 不是所有Content Uri都支持删除操作,比如外部存储的文件可能需要权限
            int deleteResult = getContentResolver().delete(uploadFileUri, null, null);
            if (deleteResult > 0) {
                Log.i(TAG, "原文件已删除");
            } else {
                Log.w(TAG, "无法删除原文件,可能需要额外权限或Uri不支持删除");
            }
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "文件拷贝失败:" + e.getMessage());
        }
    }
}

步骤2:添加获取文件名的辅助方法

为了避免硬编码目标文件名,我们可以通过ContentResolver查询原文件的显示名称:

private String getFileName(Uri uri) {
    String result = null;
    if ("content".equals(uri.getScheme())) {
        Cursor cursor = getContentResolver().query(uri, 
                new String[]{OpenableColumns.DISPLAY_NAME}, 
                null, null, null);
        try {
            if (cursor != null && cursor.moveToFirst()) {
                result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }
    // 如果查询失败,从Uri路径中截取文件名
    if (result == null) {
        result = uri.getPath();
        int lastSlashIndex = result.lastIndexOf('/');
        if (lastSlashIndex != -1) {
            result = result.substring(lastSlashIndex + 1);
        }
    }
    return result;
}

方案二:使用DocumentFile(Android 4.4+)

如果你需要对原文件进行更多操作(比如删除、重命名),可以用DocumentFile来包装Content Uri:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CHOOSE_FILE && resultCode == Activity.RESULT_OK && data != null) {
        Uri uploadFileUri = data.getData();
        DocumentFile sourceDoc = DocumentFile.fromSingleUri(this, uploadFileUri);
        if (sourceDoc == null || !sourceDoc.exists()) {
            Log.e(TAG, "无法获取源文件");
            return;
        }

        File targetDir = new File(getFilesDir(), "bluetoothchat_files");
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }
        File targetFile = new File(targetDir, sourceDoc.getName());

        // 同样通过IO流拷贝内容(DocumentFile也没有直接移动的方法)
        try (InputStream inputStream = getContentResolver().openInputStream(uploadFileUri);
             OutputStream outputStream = new FileOutputStream(targetFile)) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            Log.i(TAG, "文件复制成功");
            // 删除原文件
            if (sourceDoc.delete()) {
                Log.i(TAG, "原文件已删除");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

关键注意事项

  1. 权限适配:
    • Android 9及以下:读取外部文件需要声明READ_EXTERNAL_STORAGE权限,并动态申请。
    • Android 10+:由于Scoped Storage的限制,不需要READ_EXTERNAL_STORAGE权限即可通过Content Uri读取文件,写入应用内部存储也不需要额外权限。
  2. 无法直接移动的原因:Content Uri对应的文件可能不在你的应用权限范围内,所以无法直接调用File的移动方法,只能通过“拷贝+删除”的方式模拟移动。
  3. 避免硬编码文件名:用OpenableColumns.DISPLAY_NAME获取原文件名,适配不同文件选择器的返回结果。

内容的提问来源于stack exchange,提问作者Johan Fick

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:53:04