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

Android 11/API 30下基于MediaStore与ContentValues的视频重命名异常问题排查

问题排查与解决方案

核心问题原因

你的代码在Android 11(API 30)上失效,主要是因为**Scoped Storage(分区存储)**的强制限制,具体点:

  1. MediaStore.Video.Media.DATA字段只读:Android 11开始,媒体库的DATA字段(文件路径)变为只读,直接更新它会抛出IllegalArgumentException,这是你遇到的直接报错原因。
  2. File.renameTo权限不足:在分区存储模式下,应用无法直接操作外部存储的文件路径(尤其是三星默认文件管理器这类第三方应用创建的目录),renameTo会因权限限制失败。
  3. 路径依赖逻辑过时:原代码通过文件路径构建新文件对象的方式,不符合Android 11及以上的存储访问规范。

分步解决方案

1. 替换文件重命名逻辑(废弃File.renameTo)

在showSelectedVideoRenameDialog的确认按钮点击事件中,改用ContentResolver的renameDocument方法(API 29+支持)完成重命名,这个方法会自动同步媒体库的记录:

confirmButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View p1) {
        final String typedName = input.getText().toString();
        if (typedName.length() == 0) {
            input.setError("Name can't be empty");
            return;
        }
        final String newDisplayName = typedName + extensionValue;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // Android 11及以上:使用ContentResolver重命名
            ContentResolver resolver = activity.getContentResolver();
            Uri videoUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, videoId);
            
            try {
                // 检查新文件名是否已存在(通过媒体库查询)
                Cursor cursor = resolver.query(
                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                        new String[]{MediaStore.Video.Media._ID},
                        MediaStore.Video.Media.DISPLAY_NAME + "=?",
                        new String[]{newDisplayName},
                        null
                );
                if (cursor != null && cursor.getCount() > 0) {
                    input.setError("File name already exists");
                    cursor.close();
                    return;
                }
                if (cursor != null) cursor.close();

                // 执行重命名
                boolean renameSuccess = resolver.renameDocument(videoUri, newDisplayName);
                if (renameSuccess) {
                    videosLoader.updateOnMediaStoreChanged();
                    dialog.dismiss();
                } else {
                    input.setError("Failed to rename video");
                }
            } catch (Exception e) {
                input.setError("Rename error: " + e.getMessage());
            }
        } else {
            // Android 11以下:保留原逻辑但增加结果判断
            final File originalName = new File(videoPath);
            final File newFileName = new File(videoPath.replace(videoTitleWithoutExtension, typedName));
            
            if (newFileName.exists()) {
                input.setError("File name already exists");
            } else {
                boolean renameSuccess = originalName.renameTo(newFileName);
                if (renameSuccess) {
                    String newTitleWithExtension = typedName + extensionValue;
                    videosLoader.updateOnVideoRenamed(videoId, newFileName.getAbsolutePath(), newTitleWithExtension);
                    videosLoader.updateOnMediaStoreChanged();
                    dialog.dismiss();
                } else {
                    input.setError("Failed to rename video");
                }
            }
        }
    }
});

2. 修改updateOnVideoRenamed方法(移除DATA字段更新)

Android 11+禁止修改DATA字段,我们只更新允许修改的DISPLAY_NAME,且Android 11+下renameDocument已经自动同步媒体库,这个方法可以简化:

@Override
public void updateOnVideoRenamed(long id, String newTitle) {
    try {
        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, newTitle);
        Uri videoUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
        mContext.getContentResolver().update(videoUri, contentValues, null, null);
    } catch (Exception e) {
        Toast.makeText(mContext, "Can't rename: " + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
}

注意:Android 11及以上场景下,这个方法可以不用调用,因为renameDocument已经完成了媒体库更新。

3. 兼容三星等厂商的特殊目录

如果需要支持三星默认文件管理器创建的目录,你需要让用户授予目录访问权限:

  1. 启动目录选择器:
private static final int REQUEST_CODE_PICK_DIRECTORY = 1001;

// 在需要的地方调用,比如设置页
public void requestDirectoryAccess(AppCompatActivity activity) {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    activity.startActivityForResult(intent, REQUEST_CODE_PICK_DIRECTORY);
}
  1. 处理权限结果,持久化访问权限:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_PICK_DIRECTORY && resultCode == RESULT_OK && data != null) {
        Uri treeUri = data.getData();
        // 持久化权限,后续可以直接访问该目录下的文件
        getContentResolver().takePersistableUriPermission(
                treeUri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        );
        // 可以将treeUri保存到SharedPreferences,后续使用
    }
}

关键注意事项

  • 始终通过版本判断区分Android 11+和旧版本的逻辑,避免兼容性问题。
  • 不要依赖MediaStore的DATA字段获取或修改文件路径,改用ContentResolver和Uri操作。
  • 对于非媒体目录的文件,必须获取用户授予的目录访问权限才能操作。

内容的提问来源于stack exchange,提问作者private static

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 09:02:46