Android Studio音频存储问题:钢琴录制App保存失败排查
解决Android钢琴App录制音频保存失败、Uri为空的问题
Hey there, let's break down the issues causing your recording to fail and return a null Uri:
1. 录制路径指向的是目录而非具体文件
你的startRecording方法里,filePath被设置为direPath + "/recording",这会创建一个目录而非具体的音频文件。MediaRecorder需要写入到具体的文件路径,否则录制过程会静默失败,后续的audioFile实际上是不存在的,导致插入MediaStore时返回null。
修复代码:
修改文件名,添加时间戳和正确的后缀(对应你设置的THREE_GPP格式,后缀用.3gp):
private void startRecording() throws IOException { try { String direPath = context.getExternalFilesDir(null).getAbsolutePath(); // 生成唯一的文件名,带3gp后缀 String fileName = "recording_" + System.currentTimeMillis() + ".3gp"; String filePath = direPath + File.separator + fileName; audioFile = new File(filePath); } catch (Exception e) { Log.e("Error Tag", "external storage access error " + e.getMessage()); return; } // 剩余MediaRecorder初始化代码不变... }
用File.separator代替硬编码的/,适配不同Android设备的文件系统分隔符。
2. Scoped Storage限制导致MediaStore插入失败(Android 10+)
从Android 10(API 29)开始,系统引入了Scoped Storage,直接设置MediaStore.Audio.Media.DATA字段会被系统忽略,这是导致contentResolver.insert返回null的核心原因之一。另外,你设置的MIME类型audio/mpeg和你使用的THREE_GPP输出格式不匹配(正确的MIME类型是audio/3gpp),这也可能导致MediaStore拒绝你的插入请求。
修复saveToDisk方法,适配Scoped Storage:
public void saveToDisk() { ContentResolver contentResolver = context.getContentResolver(); ContentValues values = new ContentValues(); long current = System.currentTimeMillis(); values.put(MediaStore.Audio.Media.TITLE, "audio_" + current); values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000)); values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp"); // 匹配THREE_GPP格式 values.put(MediaStore.Audio.Media.DATE_MODIFIED, (int) (current / 1000)); Uri uri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 使用RELATIVE_PATH指定存储位置(比如音乐目录下的自定义文件夹) values.put(MediaStore.Audio.Media.RELATIVE_PATH, "Music/PianoRecordings"); uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values); // 将录制好的文件复制到MediaStore管理的Uri位置 if (uri != null) { try (OutputStream os = contentResolver.openOutputStream(uri); InputStream is = new FileInputStream(audioFile)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } } catch (IOException e) { Log.e("SaveError", "Failed to copy recording", e); uri = null; } } } else { // Android 9及以下,可继续使用DATA字段(需确保已申请WRITE_EXTERNAL_STORAGE权限) values.put(MediaStore.Audio.Media.DATA, audioFile.getAbsolutePath()); uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values); } if (uri != null) { sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); Toast.makeText(context, "Added File " + uri, Toast.LENGTH_LONG).show(); } else { Toast.makeText(context, "Failed to save recording", Toast.LENGTH_LONG).show(); } }
3. 权限检查不可少
确保你已经处理了必要的权限:
- Android 6.0+(API 23):动态申请
RECORD_AUDIO权限(录制音频必须) - Android 9及以下:额外申请
WRITE_EXTERNAL_STORAGE权限 - Android 10+:无需
WRITE_EXTERNAL_STORAGE权限,但要确保MediaStore的RELATIVE_PATH设置正确
在调用startRecording前,一定要检查权限是否已授予,否则会抛出IO异常或静默失败。
内容的提问来源于stack exchange,提问作者Arnab




