如何通过文件选择器返回的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(); } } }
关键注意事项
- 权限适配:
- Android 9及以下:读取外部文件需要声明
READ_EXTERNAL_STORAGE权限,并动态申请。 - Android 10+:由于Scoped Storage的限制,不需要
READ_EXTERNAL_STORAGE权限即可通过Content Uri读取文件,写入应用内部存储也不需要额外权限。
- Android 9及以下:读取外部文件需要声明
- 无法直接移动的原因:Content Uri对应的文件可能不在你的应用权限范围内,所以无法直接调用
File的移动方法,只能通过“拷贝+删除”的方式模拟移动。 - 避免硬编码文件名:用
OpenableColumns.DISPLAY_NAME获取原文件名,适配不同文件选择器的返回结果。
内容的提问来源于stack exchange,提问作者Johan Fick
相关产品推荐
相关产品推荐

