Android 11/API 30下基于MediaStore与ContentValues的视频重命名异常问题排查
问题排查与解决方案
核心问题原因
你的代码在Android 11(API 30)上失效,主要是因为**Scoped Storage(分区存储)**的强制限制,具体点:
MediaStore.Video.Media.DATA字段只读:Android 11开始,媒体库的DATA字段(文件路径)变为只读,直接更新它会抛出IllegalArgumentException,这是你遇到的直接报错原因。File.renameTo权限不足:在分区存储模式下,应用无法直接操作外部存储的文件路径(尤其是三星默认文件管理器这类第三方应用创建的目录),renameTo会因权限限制失败。- 路径依赖逻辑过时:原代码通过文件路径构建新文件对象的方式,不符合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. 兼容三星等厂商的特殊目录
如果需要支持三星默认文件管理器创建的目录,你需要让用户授予目录访问权限:
- 启动目录选择器:
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); }
- 处理权限结果,持久化访问权限:
@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
相关产品推荐
相关产品推荐

