Android代码设置铃声报错:_data不可修改
Android设置铃声报错
java.lang.IllegalArgumentException: Mutation of _data is not allowed解决方案 问题原因
从Android 10(API 29)开始,系统引入Scoped Storage机制,禁止直接修改MediaStore.MediaColumns.DATA字段——你代码中直接写入文件路径到该字段的操作,正是触发这个异常的核心原因。同时,直接通过文件路径操作媒体文件的方式也不再被官方推荐。
适配高版本的解决方案
以下是适配API 29+的正确实现,同时兼容旧版本系统:
1. 声明必要权限
在AndroidManifest.xml中添加权限配置:
<uses-permission android:name="android.permission.SET_RINGTONE" /> <!-- Android 13以下访问外部存储文件需要 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Android 13+访问音频文件需要 --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
2. 修改铃声设置代码
import android.Manifest; import android.content.ContentValues; import android.content.pm.PackageManager; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.widget.Toast; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public void changeRingtone(String title, File file) { // 检查必要权限 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "请先授予存储权限", Toast.LENGTH_SHORT).show(); return; } try { ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.TITLE, title); values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3"); values.put(MediaStore.Audio.Media.ARTIST, "Unknown"); values.put(MediaStore.Audio.Media.IS_RINGTONE, true); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false); values.put(MediaStore.Audio.Media.IS_ALARM, false); values.put(MediaStore.Audio.Media.IS_MUSIC, false); values.put(MediaStore.MediaColumns.DISPLAY_NAME, title + ".mp3"); values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_RINGTONES); // 插入媒体条目到系统铃声目录 Uri mediaUri = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); Uri newUri = getContentResolver().insert(mediaUri, values); if (newUri == null) { throw new IOException("无法创建媒体条目"); } // 将目标音频文件复制到系统媒体库 try (InputStream inputStream = new FileInputStream(file); OutputStream outputStream = getContentResolver().openOutputStream(newUri)) { byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } } // 设置为默认铃声 RingtoneManager.setActualDefaultRingtoneUri( this, RingtoneManager.TYPE_RINGTONE, newUri ); Toast.makeText(this, "铃声设置成功", Toast.LENGTH_LONG).show(); } catch (Exception e) { Toast.makeText(this, "设置失败:" + e.getMessage(), Toast.LENGTH_LONG).show(); e.printStackTrace(); } } // 兼容API <29的旧版本实现 @RequiresApi(api = Build.VERSION_CODES.KITKAT) private void changeRingtoneLegacy(String title, File file) { try { ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); values.put(MediaStore.MediaColumns.TITLE, title); values.put(MediaStore.MediaColumns.SIZE, file.length()); values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3"); values.put(MediaStore.Audio.Media.ARTIST, "Unknown"); values.put(MediaStore.Audio.Media.IS_RINGTONE, true); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false); values.put(MediaStore.Audio.Media.IS_ALARM, false); values.put(MediaStore.Audio.Media.IS_MUSIC, false); Uri uri = MediaStore.Audio.Media.getContentUriForPath(file.getAbsolutePath()); // 先删除已有相同路径的条目,避免重复插入 getContentResolver().delete(uri, MediaStore.MediaColumns.DATA + "=?", new String[]{file.getAbsolutePath()}); Uri newUri = getContentResolver().insert(uri, values); RingtoneManager.setActualDefaultRingtoneUri( this, RingtoneManager.TYPE_RINGTONE, newUri ); Toast.makeText(this, "铃声设置成功", Toast.LENGTH_LONG).show(); } catch (Exception e) { Toast.makeText(this, "设置失败:" + e.getMessage(), Toast.LENGTH_LONG).show(); e.printStackTrace(); } }
注意事项
- 如果音频文件存放在应用私有目录(如
getFilesDir()或getCacheDir()),无需申请存储权限 - 确保音频文件格式为系统支持的类型(如MP3、WAV、OGG等)
- 插入媒体条目后,可调用
MediaScannerConnection.scanFile刷新媒体库,确保系统能快速识别新铃声
内容的提问来源于stack exchange,提问作者Binil George
相关产品推荐
相关产品推荐

