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

Android 9及以上版本铃声存储功能失效问题求助

铃声设置应用在Android 9+无法完成铃声存储的解决方案

我开发的铃声设置应用在旧版Android系统可正常运行,但在Android 9及以上版本无法完成铃声存储操作。推测是新版本系统的存储路径或权限机制变更导致,多次排查未找到解决方案,相关代码如下:

private void requestPermission () {

    if (ActivityCompat.shouldShowRequestPermissionRationale ( this , android.Manifest.permission.WRITE_EXTERNAL_STORAGE )) {
        Intent intent = new Intent ( );
        intent.setAction ( Settings.ACTION_APPLICATION_DETAILS_SETTINGS );
        Uri uri = Uri.fromParts ( "package" , getPackageName ( ) , null );
        intent.setData ( uri );
        startActivity ( intent );
    } else {
        ActivityCompat.requestPermissions ( this , new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE} , REQUEST_CODE );
    }
}

@SuppressLint("ObsoleteSdkInt")
@Override
public void onRequestPermissionsResult ( int requestCode , @NonNull String[] permissions , @NonNull int[] grantResults ) {
    super.onRequestPermissionsResult ( requestCode , permissions , grantResults );
    if (requestCode == REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[ 0 ] == PackageManager.PERMISSION_GRANTED) {


            if (Build.VERSION.SDK_INT >= 23) {
                if (! Settings.System.canWrite ( this )) {
                    Intent intent = new Intent ( Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION )
                            .setData ( Uri.parse ( "package:" + getPackageName ( ) ) )
                            .addFlags ( Intent.FLAG_ACTIVITY_NEW_TASK );
                    startActivity ( intent );
                }
            }
        }
    }
}


@SuppressLint("Recycle")
private void set_tone ( String N1 , String N2 ) {
    AssetFileDescriptor openAssetFileDescriptor;
    (( AudioManager ) getSystemService ( AUDIO_SERVICE )).setRingerMode ( 2 );
    File file = new File ( Environment.getExternalStoragePublicDirectory ( Environment.DIRECTORY_MUSIC ) + "/AndRody" , N2 );
    if (! file.getParentFile ( ).exists ( )) {

        file.getParentFile ( ).mkdirs ( );

    }
    if (! file.exists ( )) {
        try {
            file.createNewFile ( );
        } catch (IOException e) {
            e.printStackTrace ( );
        }
    }
    Uri parse = Uri.parse ( "android.resource://" + getPackageName ( ) + "/raw/" + N2 );
    ContentResolver contentResolver = getContentResolver ( );
    try {
        openAssetFileDescriptor = contentResolver.openAssetFileDescriptor ( parse , "r" );
    } catch (FileNotFoundException e2) {
        openAssetFileDescriptor = null;
    }
    try {
        byte[] bArr = new byte[1024];
        assert openAssetFileDescriptor != null;
        FileInputStream createInputStream = openAssetFileDescriptor.createInputStream ( );
        FileOutputStream fileOutputStream = new FileOutputStream ( file );
        for (int read = createInputStream.read ( bArr ); read != - 1; read = createInputStream.read ( bArr )) {
            fileOutputStream.write ( bArr , 0 , read );
        }
        fileOutputStream.close ( );
    } catch (IOException e3) {
        e3.printStackTrace ( );
    }

    ContentValues contentValues = new ContentValues ( );
    contentValues.put ( "_data" , file.getAbsolutePath ( ) );
    contentValues.put ( "title" , N1 );
    contentValues.put ( "mime_type" , "audio/mpeg" );
    contentValues.put ( "_size" , file.length () );
    contentValues.put ( "is_ringtone" , Boolean.TRUE );

    try {
        Toast.makeText ( this , new StringBuilder ( ).append ( "تم تعيين النغمة بنجاح" ) , Toast.LENGTH_LONG ).show ( );
        RingtoneManager.setActualDefaultRingtoneUri ( getBaseContext ( ) , 1, contentResolver.insert ( MediaStore.Audio.Media.getContentUriForPath ( file.getAbsolutePath ( ) ) , contentValues ) );
    } catch (Throwable th) {
        Toast.makeText ( this , new StringBuilder ( ).append ( "يوجد خلل ما" ) , Toast.LENGTH_LONG ).show ( );
    }
}

我认为修改应集中在以下代码部分:

ContentValues contentValues = new ContentValues ( );
contentValues.put ( "_data" , file.getAbsolutePath ( ) );
contentValues.put ( "title" , N1 );
contentValues.put ( "mime_type" , "audio/mpeg" );
contentValues.put ( "_size" , file.length () );
contentValues.put ( "is_ringtone" , Boolean.TRUE );

问题根源

Android 9(API 28)开始限制直接访问_data字段,Android 10(API 29)引入分区存储机制,禁止直接通过文件路径操作公共目录的媒体文件,必须通过MediaStoreAPI进行插入和更新。同时你的权限申请逻辑存在错误:ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION是Android 11(API 30)才新增的权限入口,低版本调用会导致崩溃,且分区存储下操作媒体文件无需该权限。

修复方案

1. 修正权限申请逻辑

根据Android版本适配权限要求,避免无效权限请求:

private void requestPermission(String N1, String N2) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10+ 操作媒体文件无需WRITE_EXTERNAL_STORAGE权限
        set_tone(N1, N2);
        return;
    }

    if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        startActivity(intent);
    } else {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 权限通过后执行铃声设置(需确保N1、N2可访问,可通过成员变量或参数传递)
            set_tone(N1, N2);
        }
    }
}

2. 重写铃声存储与媒体库插入逻辑

完全适配Android 9+的存储机制,放弃直接操作文件路径,改用MediaStoreAPI:

@SuppressLint("Recycle")
private void set_tone(String N1, String N2) {
    AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
    audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);

    ContentResolver contentResolver = getContentResolver();
    Uri rawUri = Uri.parse("android.resource://" + getPackageName() + "/raw/" + N2);
    AssetFileDescriptor assetFd;

    try {
        assetFd = contentResolver.openAssetFileDescriptor(rawUri, "r");
    } catch (FileNotFoundException e) {
        Toast.makeText(this, "未能找到音频资源", Toast.LENGTH_LONG).show();
        return;
    }

    // 构建媒体库插入参数
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.Audio.Media.TITLE, N1);
    contentValues.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mpeg");
    contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, true);

    Uri audioUri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10+ 使用RELATIVE_PATH指定存储路径,无需手动创建文件
        contentValues.put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_MUSIC + "/AndRody");
        audioUri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues);
    } else {
        // Android 9及以下,先创建文件再关联媒体库
        File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + "/AndRody", N2);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        contentValues.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
        audioUri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, contentValues);
    }

    if (audioUri == null) {
        Toast.makeText(this, "媒体库插入失败", Toast.LENGTH_LONG).show();
        return;
    }

    // 将raw资源写入到媒体库对应的Uri
    try (InputStream inputStream = assetFd.createInputStream();
         OutputStream outputStream = contentResolver.openOutputStream(audioUri)) {
        byte[] buffer = new byte[1024];
        int readBytes;
        while ((readBytes = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, readBytes);
        }

        // Android 9及以下手动更新文件大小(部分系统自动处理)
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            ContentValues updateValues = new ContentValues();
            updateValues.put(MediaStore.Audio.Media.SIZE, new File(audioUri.getPath()).length());
            contentResolver.update(audioUri, updateValues, null, null);
        }
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(this, "写入音频文件失败", Toast.LENGTH_LONG).show();
        return;
    }

    // 设置默认铃声
    try {
        RingtoneManager.setActualDefaultRingtoneUri(getBaseContext(), RingtoneManager.TYPE_RINGTONE, audioUri);
        Toast.makeText(this, "تم تعيين النغمة بنجاح", Toast.LENGTH_LONG).show();
    } catch (Throwable th) {
        th.printStackTrace();
        Toast.makeText(this, "يوجد خلل ما", Toast.LENGTH_LONG).show();
    }
}

3. 关键修改点说明

  • 移除直接操作_data字段的代码,改用官方API常量MediaStore.Audio.Media.DATA(仅Android 9及以下)和RELATIVE_PATH(Android 10+)
  • Android 10+通过MediaStore直接插入Uri并写入内容,无需手动创建文件,完全适配分区存储
  • 权限逻辑按版本拆分,避免不必要的权限申请和错误跳转
  • 使用try-with-resources自动关闭流,避免资源泄漏
  • 用RingtoneManager.TYPE_RINGTONE替代硬编码的1,提升代码可读性和兼容性

内容的提问来源于stack exchange,提问作者Ahmed elghetany

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 18:55:01