Android 14中将DCIM/Camera图片移动至其他目录的实现问题
解决Android 14下照片分类/重命名失败的问题
为什么你的代码会失败
从Android 10(API 29)开始,Scoped Storage限制了直接通过File类操作外部存储的媒体目录(比如DCIM/Camera),File.renameTo()在这类受保护目录下会直接失效,因为APP没有直接修改文件路径的权限。
方案1:用MediaStore API处理媒体文件(推荐)
针对照片/视频这类媒体文件,官方推荐使用MediaStore API来修改文件名或移动位置,无需额外申请单个文件权限,只要有READ_MEDIA_IMAGES/VIDEO权限即可。
同一目录重命名示例代码
private void renameMediaFile(Context context, long lastModifiedTime) { String[] projection = { MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_MODIFIED, MediaStore.Images.Media.RELATIVE_PATH }; // 筛选DCIM/Camera下、修改时间晚于lastModifiedTime的文件 String selection = MediaStore.Images.Media.RELATIVE_PATH + " = ? AND " + MediaStore.Images.Media.DATE_MODIFIED + " > ?"; String[] selectionArgs = { "DCIM/Camera/", String.valueOf(lastModifiedTime / 1000) // MediaStore存的是秒级时间戳 }; try (Cursor cursor = context.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, null)) { if (cursor != null && cursor.moveToFirst()) { do { long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); String oldName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)); // 根据实际文件后缀调整,比如适配.png/.mp4等 String newName = oldName.substring(0, oldName.lastIndexOf(".")) + "_2" + oldName.substring(oldName.lastIndexOf(".")); // 构建要更新的内容 ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, newName); // 更新文件 int rowsUpdated = context.getContentResolver().update( ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id), values, null, null); if (rowsUpdated > 0) { Log.d("Files", "重命名成功:" + oldName + " -> " + newName); } else { Log.d("Files", "重命名失败:" + oldName); } } while (cursor.moveToNext()); } } catch (Exception e) { e.printStackTrace(); } }
移动文件到指定目录示例
如果要把照片移动到其他目录(比如DCIM/MyPhotos),只需修改RELATIVE_PATH字段:
// 移动时的ContentValues ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/MyPhotos/"); values.put(MediaStore.Images.Media.DISPLAY_NAME, newName); // 可选:同时重命名 // 执行更新 int rowsUpdated = context.getContentResolver().update( ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id), values, null, null);
方案2:用SAF(存储访问框架)批量获取权限
像TotalCommander这类文件管理器,是通过SAF让用户授予整个目录的读写权限,从而可以批量操作文件。如果你的应用需要操作非媒体目录,或者更灵活的文件操作,可以用这种方式:
1. 申请目录权限
private static final int REQUEST_CODE_PICK_DIR = 1001; // 触发目录选择,让用户授权目标目录 private void requestDirectoryAccess() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_PICK_DIR); } // 处理权限回调 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_PICK_DIR && resultCode == RESULT_OK && data != null) { Uri treeUri = data.getData(); if (treeUri != null) { // 持久化权限,重启APP后仍有效 getContentResolver().takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 现在可以用DocumentFile操作这个目录下的文件 DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri); // 后续操作逻辑... } } }
2. 用SAF重命名/移动文件
拿到目录权限后,就可以通过DocumentFile操作文件:
// 示例:重命名文件 private void renameFileWithSAF(DocumentFile dir, String oldFileName) { DocumentFile file = dir.findFile(oldFileName); if (file != null) { String newFileName = oldFileName.substring(0, oldFileName.lastIndexOf(".")) + "_2" + oldFileName.substring(oldFileName.lastIndexOf(".")); if (file.renameTo(newFileName)) { Log.d("Files", "重命名成功"); } else { Log.d("Files", "重命名失败"); } } }
关于PendingIntent和createWriteRequest的说明
createWriteRequest()是SAF中用于批量申请多个文件写入权限的方法,适合需要操作单个或多个独立文件的场景;PendingIntent是配合createWriteRequest()使用的,用来触发系统权限弹窗,让用户确认授权。如果用MediaStore API,不需要这两个东西。
内容的提问来源于stack exchange,提问作者user22691873
相关产品推荐
相关产品推荐

