Android 12设备录制音频至外部公共存储遇EPERM权限错误
问题描述
在已授予File and Media Permission权限的Android 12设备上,调用recorder.prepare()时抛出权限错误:
prepare() failed /storage/emulated/0/Music/Exotel/Media/Exotel Audio/Voice Messages/Exotel Temp/Exotel_Voice1658709937668.3gpp: open failed: EPERM (Operation not permitted)
当前代码尝试将音频保存到外部公共存储的Music目录子文件夹,使用Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)获取路径,但始终触发相同错误。核心代码如下:
String subfolder = "Exotel/Media/Exotel Audio/Voice Messages/Exotel Temp"; String time = new SimpleDateFormat("yyyyMMddhhmmss", Locale.US).format(new Date()); String filename = Session.getUserFname()+"_Voice"+time+".3gp"; String TempPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath()+ "/" + subfolder; File dir = new File(TempPath); if (!dir.exists()){ dir.mkdirs(); } TempPath = TempPath+"/"+filename; recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setOutputFile(TempPath); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { recorder.prepare(); recorder.start(); } catch (IOException | IllegalStateException e) { Log.e(TAG, "prepare() failed "+e.getMessage()); }
错误原因
Android 12(API 31)强化了分区存储规则:
- 即便授予了
READ_MEDIA_AUDIO等File and Media权限,直接通过File API写入公共媒体目录(如Music、Pictures)会被系统拒绝,这类共享媒体区域必须通过MediaStoreAPI操作。 Environment.getExternalStoragePublicDirectory()在Android Q(API 29)已废弃,Android 12中完全限制了该路径的直接写入权限。
解决方案
方案1:使用MediaStore API写入公共Music目录(推荐)
这是符合Android 12+存储规范的标准做法,无需额外特殊权限,仅需已有的File and Media权限即可。
示例代码:
// 定义文件名和相对路径 String time = new SimpleDateFormat("yyyyMMddhhmmss", Locale.US).format(new Date()); String filename = Session.getUserFname() + "_Voice" + time + ".3gp"; String relativePath = Environment.DIRECTORY_MUSIC + "/Exotel/Media/Exotel Audio/Voice Messages/Exotel Temp"; // 构建MediaStore插入元数据 ContentValues values = new ContentValues(); values.put(MediaStore.Audio.Media.DISPLAY_NAME, filename); values.put(MediaStore.Audio.Media.RELATIVE_PATH, relativePath); values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp"); values.put(MediaStore.Audio.Media.IS_PENDING, 1); // 标记为待处理,避免媒体扫描干扰 // 获取音频文件的内容URI Uri audioUri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values); // 初始化并配置MediaRecorder MediaRecorder recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); try { // 通过内容URI获取文件描述符,设置给MediaRecorder if (audioUri != null) { try (OutputStream os = getContentResolver().openOutputStream(audioUri)) { if (os instanceof FileOutputStream) { FileDescriptor fd = ((FileOutputStream) os).getFD(); recorder.setOutputFile(fd); recorder.prepare(); recorder.start(); } } } } catch (IOException | IllegalStateException e) { Log.e(TAG, "prepare() failed " + e.getMessage()); // 失败时清理临时条目 if (audioUri != null) { getContentResolver().delete(audioUri, null, null); } } // 录制完成后,标记文件为已完成(允许媒体扫描) if (audioUri != null) { ContentValues updateValues = new ContentValues(); updateValues.put(MediaStore.Audio.Media.IS_PENDING, 0); getContentResolver().update(audioUri, updateValues, null, null); }
方案2:申请MANAGE_EXTERNAL_STORAGE权限(不推荐)
仅适用于文件管理器、备份类等必须全局访问存储的应用,Google Play审核会严格限制该权限的使用。
- 在
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
- 动态请求权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, 1001); } }
权限通过后,即可使用原有的File API代码进行写入操作。
内容的提问来源于stack exchange,提问作者Ritu
相关产品推荐
相关产品推荐

