将视频URI存入数据库再读取后,VideoView报‘Cant play video’错误如何解决
解决Android中存储Content URI后无法播放视频的问题
这个问题我做视频相关功能时也踩过坑!核心原因是你通过ACTION_GET_CONTENT获取的content://开头的URI是临时权限——系统只给了当前App会话的一次性访问权限,当你把这个URI字符串存入数据库,下次重启App或过段时间再读取使用时,App已经失去了访问该URI的权限,自然就会弹出“Can't play video”的错误。
下面给你几个可行的解决方案,按推荐程度排序:
方案1:保存视频的绝对文件路径(最直接)
与其存储URI字符串,不如先把URI转换成视频的绝对文件路径,再把路径存入数据库。这样后续读取路径后,直接用VideoView.setVideoPath()或者Uri.fromFile()来播放,就不会有权限问题。
获取绝对路径的代码示例:
private String getRealPathFromUri(Context context, Uri uri) { String realPath = null; // 针对不同Android版本做兼容处理 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10及以上,通过MediaStore查询路径 Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Video.Media.DATA}, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { realPath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); } cursor.close(); } } else { // Android 9及以下,通过DocumentContract解析 if (DocumentsContract.isDocumentUri(context, uri)) { String docId = DocumentsContract.getDocumentId(uri); if ("com.android.providers.media.documents".equals(uri.getAuthority())) { String id = docId.split(":")[1]; String selection = MediaStore.Video.Media._ID + "=?"; String[] selectionArgs = new String[]{id}; realPath = getDataColumn(context, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { realPath = getDataColumn(context, uri, null, null); } } return realPath; } private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = MediaStore.Video.Media.DATA; final String[] projection = {column}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int columnIndex = cursor.getColumnIndexOrThrow(column); return cursor.getString(columnIndex); } } finally { if (cursor != null) cursor.close(); } return null; }
使用方式:
在onActivityResult里替换原代码:
if (resultCode == RESULT_OK && requestCode == SELECT_VIDEO) { Uri selectedVideoUri = data.getData(); if (selectedVideoUri != null) { String videoPath = getRealPathFromUri(this, selectedVideoUri); if (videoPath != null) { videoPathList.add(videoPath); // 存入数据库 } } }
播放时直接用路径:
String videoPath = getIntent().getStringExtra("video_path"); videoView.setVideoPath(videoPath); // 后续mediaController设置不变 videoView.start();
方案2:申请持久化URI权限
如果你必须存储URI字符串,可以在获取URI后,向系统申请持久化访问权限,这样即使App重启,也能继续访问该URI。
代码示例:
在onActivityResult里添加权限申请逻辑:
if (resultCode == RESULT_OK && requestCode == SELECT_VIDEO) { Uri selectedVideoUri = data.getData(); if (selectedVideoUri != null) { // 申请持久化读权限 int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION); getContentResolver().takePersistableUriPermission(selectedVideoUri, takeFlags); // 存储URI字符串到数据库 videoPathList.add(selectedVideoUri.toString()); } }
注意事项:
- 该方法需要用户在文件选择时授权App访问该文件(系统一般会自动弹出授权提示);
- 如果用户后续删除了该文件或修改了文件权限,URI还是会失效;
- 仅适用于支持SAF(Storage Access Framework)的文件管理器返回的URI。
方案3:复制视频到App私有目录(最可靠)
把选中的视频复制到App的私有存储目录(比如getFilesDir()或者getCacheDir()),然后存储这个私有路径。因为App对自己的私有目录有完全的访问权限,不会出现权限失效的问题。
代码示例:
private String copyVideoToPrivateDir(Context context, Uri uri) { String fileName = "video_" + System.currentTimeMillis() + ".mp4"; File outputFile = new File(context.getFilesDir(), fileName); try (InputStream inputStream = context.getContentResolver().openInputStream(uri); OutputStream outputStream = new FileOutputStream(outputFile)) { byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, length); } return outputFile.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); return null; } }
使用方式:
在onActivityResult里调用该方法:
if (resultCode == RESULT_OK && requestCode == SELECT_VIDEO) { Uri selectedVideoUri = data.getData(); if (selectedVideoUri != null) { String privatePath = copyVideoToPrivateDir(this, selectedVideoUri); if (privatePath != null) { videoPathList.add(privatePath); // 存入数据库 } } }
播放时直接用私有路径即可,和方案1的播放代码一致。
优缺点:
- 优点:完全避免权限问题,兼容性最好;
- 缺点:会占用App的存储空间,如果视频很大,可能会让用户反感,记得添加清理缓存的功能。
内容的提问来源于stack exchange,提问作者Sahana Kamath
相关产品推荐
相关产品推荐

