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
相关产品推荐
相关产品推荐

