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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 17:05:56