Android 14(API 34)中从Activity设置铃声崩溃的修复请求
修复Android 14+设置默认铃声崩溃问题
问题根源
Android 14(API 34)开始,系统禁止直接修改MediaStore.MediaColumns.DATA字段(该字段变为只读),你的代码中在ContentValues里添加了这个字段,导致插入MediaStore时抛出IllegalArgumentException: Mutation of _data is not allowed。
修复方案
1. 移除DATA字段的设置
删除values.put(MediaStore.MediaColumns.DATA, f.getAbsolutePath());这一行,Android 14+会自动管理媒体文件的存储路径。
2. 简化文件写入流程
不需要先将raw资源写入自己的外部文件目录,直接通过MediaStore返回的Uri写入流,减少冗余操作。
3. 修正其他细节
MediaStore.Audio.Media.ARTIST应该传入字符串,而非资源ID(原代码用R.string.app_name,需改为getString(R.string.app_name))。- 统一使用try-with-resources管理流资源,避免资源泄漏。
修改后的完整代码
interface Tone { String PHONE_RINGTONE = "ringtone"; String NOTIFICATION_TONE = "notification"; String ALARM = "alarm"; } public void setRingtone(String type) { ContentResolver mCr = getContentResolver(); Uri mUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.horse); AssetFileDescriptor soundFile; try { soundFile = mCr.openAssetFileDescriptor(mUri, "r"); } catch (FileNotFoundException e) { Toast.makeText(this, "无法读取音频资源", Toast.LENGTH_SHORT).show(); return; } // 构建MediaStore插入参数 ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.TITLE, getString(R.string.app_name)); values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mpeg"); values.put(MediaStore.Audio.Media.ARTIST, getString(R.string.app_name)); if (Objects.equals(type, Tone.NOTIFICATION_TONE)) { values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true); } else if (Objects.equals(type, Tone.ALARM)) { values.put(MediaStore.Audio.Media.IS_ALARM, true); } else { values.put(MediaStore.Audio.Media.IS_RINGTONE, true); } Uri newUri = null; try { // 插入MediaStore获取Uri newUri = mCr.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values); if (newUri == null) { Toast.makeText(this, "无法创建媒体条目", Toast.LENGTH_SHORT).show(); return; } // 将raw资源写入MediaStore对应的流 try (InputStream fis = soundFile.createInputStream(); OutputStream os = mCr.openOutputStream(newUri)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { os.write(buffer, 0, bytesRead); } os.flush(); } // 设置默认铃声/通知/闹钟 int ringtoneType; String settingsKey; String successMsg; if (Objects.equals(type, Tone.NOTIFICATION_TONE)) { ringtoneType = RingtoneManager.TYPE_NOTIFICATION; settingsKey = Settings.System.NOTIFICATION_SOUND; successMsg = "通知铃声修改成功"; } else if (Objects.equals(type, Tone.ALARM)) { ringtoneType = RingtoneManager.TYPE_ALARM; settingsKey = Settings.System.ALARM_ALERT; successMsg = "闹钟铃声修改成功"; } else { ringtoneType = RingtoneManager.TYPE_RINGTONE; settingsKey = Settings.System.RINGTONE; successMsg = "来电铃声修改成功"; } RingtoneManager.setActualDefaultRingtoneUri(this, ringtoneType, newUri); Settings.System.putString(mCr, settingsKey, newUri.toString()); showMessage("完成", successMsg); } catch (IOException e) { Toast.makeText(this, "写入音频失败: " + e.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); // 插入失败时删除已创建的MediaStore条目 if (newUri != null) { mCr.delete(newUri, null, null); } } catch (Throwable t) { Toast.makeText(this, t.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); if (newUri != null) { mCr.delete(newUri, null, null); } } }
额外说明
- 权限:Android 13及以下需申请
WRITE_EXTERNAL_STORAGE权限,Android 14+写入MediaStore无需该权限,但访问自身外部文件目录仍需适配权限规则。 - MIME类型:直接指定
audio/mpeg比通过getMIMEType解析更可靠,避免路径解析错误。 - 错误处理:新增Uri为空的判断,插入失败时删除已创建的媒体条目,避免残留无效内容。
内容的提问来源于stack exchange,提问作者Ivan Rancic
相关产品推荐
相关产品推荐

