You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 07:48:12