Android Q下将录制视频存入图库:如何正确生成关联URI?
Let's break down the problems you're facing and fix them one by one:
1. Resolve the Unknown URI Error
The UnsupportedOperationException: Unknown URI happens because MediaStore.VOLUME_EXTERNAL_PRIMARY might not be fully compatible across all devices. Instead, use a version-agnostic approach to get the correct media collection URI:
Uri collection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // For Android 10+, use the standard external volume (more widely compatible) collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL); } else { // For Android 9 and below, use the legacy external content URI collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; }
2. Ensure You Have the Right Permissions
- Android 10+: No need for
WRITE_EXTERNAL_STORAGEpermission when using MediaStore, but you must requestCAMERAandRECORD_AUDIOpermissions dynamically. - Android 9 and below: You need to request
WRITE_EXTERNAL_STORAGE(alongside camera/audio permissions) dynamically before saving to public directories.
3. Correctly Copy Your Recorded Video to the Gallery
Your recorded video is stored in the app's private directory (getExternalFilesDir(null)), which the gallery app can't access. You need to copy this file to a public MediaStore directory and properly register it so the gallery detects it.
Here's a complete, tested method to handle this:
private Uri saveVideoToGallery(Context context, File recordedVideoFile) { String videoFileName = "video_" + System.currentTimeMillis() + ".mp4"; ContentValues values = new ContentValues(); // Set target directory (use "DCIM/YourFolder" if you prefer that location) values.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/YourCustomFolder"); values.put(MediaStore.Video.Media.TITLE, videoFileName); values.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName); values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000); values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis()); // Mark file as pending while writing (Android 10+) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.put(MediaStore.Video.Media.IS_PENDING, 1); } ContentResolver resolver = context.getContentResolver(); Uri collection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL); } else { collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } // Insert new entry into MediaStore Uri savedUri = resolver.insert(collection, values); if (savedUri == null) { Log.e("VideoSave", "Failed to create MediaStore entry"); return null; } // Copy recorded video to the MediaStore URI try (OutputStream out = resolver.openOutputStream(savedUri); InputStream in = new FileInputStream(recordedVideoFile)) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } // Mark file as ready for system to scan into gallery if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.clear(); values.put(MediaStore.Video.Media.IS_PENDING, 0); resolver.update(savedUri, values, null, null); } // Force media scanner to detect the new file immediately MediaScannerConnection.scanFile( context, new String[]{savedUri.toString()}, new String[]{"video/mp4"}, null ); return savedUri; } catch (IOException e) { // Clean up if copy fails resolver.delete(savedUri, null, null); e.printStackTrace(); return null; } }
How to Use This Method
After stopping your MediaRecorder, call this method with the recorded file from your getMediaFile() function:
// After stopping recording File recordedFile = getMediaFile(context); Uri galleryUri = saveVideoToGallery(context, recordedFile); if (galleryUri != null) { // Success! galleryUri links directly to your video in the gallery // Use this URI to share or access the video later } else { // Handle save failure }
4. Optional: Record Directly to MediaStore (Avoid Copying)
For better efficiency, configure MediaRecorder to record directly to a MediaStore URI, skipping the private directory copy step:
// Call this before starting your MediaRecorder private Uri prepareDirectRecordingToGallery(Context context) throws IOException { String videoFileName = "video_" + System.currentTimeMillis() + ".mp4"; ContentValues values = new ContentValues(); values.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/YourCustomFolder"); values.put(MediaStore.Video.Media.TITLE, videoFileName); values.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName); values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); values.put(MediaStore.Video.Media.IS_PENDING, 1); ContentResolver resolver = context.getContentResolver(); Uri collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL); Uri savedUri = resolver.insert(collection, values); if (savedUri == null) { throw new IOException("Failed to create MediaStore entry"); } // Get file descriptor for MediaStore URI and attach to MediaRecorder ParcelFileDescriptor pfd = resolver.openFileDescriptor(savedUri, "w"); mediaRecorder.setOutputFile(pfd.getFileDescriptor()); return savedUri; } // After stopping recording: private void finalizeDirectRecording(Context context, Uri savedUri) { try { // Mark file as ready ContentValues values = new ContentValues(); values.put(MediaStore.Video.Media.IS_PENDING, 0); context.getContentResolver().update(savedUri, values, null, null); // Trigger media scan MediaScannerConnection.scanFile(context, new String[]{savedUri.toString()}, null, null); } catch (Exception e) { e.printStackTrace(); } }
5. Why Uri.fromFile(f) Doesn't Work
On Android 7.0+, Uri.fromFile() throws a FileUriExposedException because file URIs can't be safely shared with other apps. Even if you bypass this, files in your app's private directory are not accessible to the gallery—only MediaStore URIs make files visible to apps like the gallery.
内容的提问来源于stack exchange,提问作者AK. Bab

