Android 9+ 系统下设置铃声失败的完整解决方案

心靈之曲

心靈之曲

2026-07-25

979人浏览

原创

Android 9+ 系统下设置铃声失败的完整解决方案

本文详解 android 9(api 28)及以上版本中因存储权限变更、媒体库插入方式调整及外部存储访问限制导致铃声设置功能失效的问题,并提供适配 android 11(api 30)及 android 12+(api 31+)的兼容性修复方案。

本文详解 android 9(api 28)及以上版本中因存储权限变更、媒体库插入方式调整及外部存储访问限制导致铃声设置功能失效的问题,并提供适配 android 11(api 30)及 android 12+(api 31+)的兼容性修复方案。

在 Android 9(Pie)起,系统逐步收紧外部存储访问权限;至 Android 10(API 29),WRITE_EXTERNAL_STORAGE 权限默认失效,应用被强制进入分区存储(Scoped Storage) 模式;而 Android 11(API 30)进一步废弃 MediaStore.Audio.Media.getContentUriForPath() 方法,Android 12(API 31)则要求获取 “所有文件访问权限”(MANAGE_EXTERNAL_STORAGE) 才能写入公共目录(如 Music/)。原代码在新系统上失败的根本原因在于:

  • ❌ 继续使用已弃用的 Environment.getExternalStoragePublicDirectory() + WRITE_EXTERNAL_STORAGE;
  • ❌ 在 Android 11+ 中仍调用 getContentUriForPath() —— 该方法返回 null,导致 contentResolver.insert() 失败;
  • ❌ 未正确申请 MANAGE_EXTERNAL_STORAGE 权限(Android 11+ 必需),且未处理 Environment.isExternalStorageManager() 检查;
  • ❌ 文件 I/O 未使用 try-with-resources,存在资源泄漏风险;
  • ❌ ContentValues 键名使用硬编码字符串(如 "_data"),应改用 MediaStore.Audio.Media.DATA 等标准常量。

✅ 正确适配方案(支持 Android 9–14)

1. 动态权限申请逻辑分层处理

private void requestPermission() {
    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 {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Android 11+
            try {
                Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(Uri.parse("package:" + getPackageName()));
                startActivity(intent);
            } catch (ActivityNotFoundException e) {
                // 部分厂商 ROM 使用旧版 Intent Action
                Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                startActivity(intent);
            }
        } else {
            // Android 6.0–10:申请传统存储权限
            ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                             Manifest.permission.READ_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) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                // Android 11+:检查 MANAGE_EXTERNAL_STORAGE 是否已授予
                if (!Environment.isExternalStorageManager()) {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                    intent.setData(Uri.parse("package:" + getPackageName()));
                    startActivity(intent);
                }
            }
        }
    }
}

⚠️ 注意:MANAGE_EXTERNAL_STORAGE 权限需在 AndroidManifest.xml 中声明,并通过 Google Play 审核豁免(仅限特定用例,如文件管理器类应用)。普通铃声应用建议优先采用 MediaStore API(见下文替代方案)。

2. 铃声写入与媒体库注册(兼容 Android 10+)

关键修改点:

Android开发教程与笔记pdf版
Android开发教程与笔记pdf版

Android文件存取与数据库编程知识,文件操作主要是读文件、写文件、读取静态文件等,同时还介绍了创建添加文件内容并保存,打开文件并显示内容;数据库编程方面主要介绍了SQLite数据库的使用、包括创建、删除、打开数据库、非查询SQL操作指令、查询SQL指令-游标Cursors等知识。

下载
  • ✅ 使用 Environment.getExternalStorageDirectory()(Android 11+ 需配合 MANAGE_EXTERNAL_STORAGE);
  • ✅ 替换 getContentUriForPath() 为 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
  • ✅ 使用标准 MediaStore 常量填充 ContentValues;
  • ✅ 采用 try-with-resources 确保流安全关闭。
private void set_tone(String title, String fileName) {
    AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
    audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);

    // 创建目标文件(如 /sdcard/AndRody/{fileName})
    File dir = new File(Environment.getExternalStorageDirectory(), "AndRody");
    File file = new File(dir, fileName);
    dir.mkdirs(); // 自动创建父目录

    try {
        if (!file.exists()) file.createNewFile();
    } catch (IOException e) {
        Toast.makeText(this, "创建文件失败", Toast.LENGTH_SHORT).show();
        return;
    }

    // 从 raw 资源复制音频数据
    Uri resourceUri = Uri.parse("android.resource://" + getPackageName() + "/raw/" + fileName);
    try (InputStream is = getContentResolver().openInputStream(resourceUri);
         OutputStream os = new FileOutputStream(file)) {
        byte[] buffer = new byte[4096];
        int len;
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(this, "复制音频失败", Toast.LENGTH_SHORT).show();
        return;
    }

    // 插入 MediaStore(推荐方式,无需 MANAGE_EXTERNAL_STORAGE)
    ContentValues values = new ContentValues();
    values.put(MediaStore.Audio.Media.DISPLAY_NAME, fileName);
    values.put(MediaStore.Audio.Media.TITLE, title);
    values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mpeg");
    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);

    // ✅ 关键:使用 MediaStore.EXTERNAL_CONTENT_URI 替代已废弃的 getContentUriForPath()
    Uri uri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
    if (uri != null) {
        try (OutputStream out = getContentResolver().openOutputStream(uri)) {
            Files.copy(file.toPath(), out);
            // 设置为默认铃声
            RingtoneManager.setActualDefaultRingtoneUri(
                this, RingtoneManager.TYPE_RINGTONE, uri);
            Toast.makeText(this, "铃声设置成功", Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "写入媒体库失败", Toast.LENGTH_SHORT).show();
        }
    } else {
        Toast.makeText(this, "媒体库插入失败", Toast.LENGTH_SHORT).show();
    }
}

3. 清单文件配置(必要补充)

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxsdkversion="29"></uses-permission><!-- 仅 Android 10 及以下生效 --><uses-permission android:name="android.permission.POST_NOTIFICATIONS"></uses-permission><!-- Android 13+ 需要 --><uses-permission android:name="android.permission.READ_MEDIA_AUDIO"></uses-permission><!-- Android 13+ 新权限 --><uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:required="false"></uses-permission><!-- 仅 Android 11+ 且必须声明 android:requestLegacyExternalStorage="false" -->

? 总结:

  • Android 10+ 推荐完全放弃 getExternalStoragePublicDirectory(),改用 MediaStore API 写入媒体库,既规避权限问题,又符合隐私沙盒规范;
  • 若必须访问任意路径(如导出到用户指定目录),则需申请 MANAGE_EXTERNAL_STORAGE 并通过 Play Store 特殊用途审核;
  • 所有文件操作务必使用 try-with-resources 或显式 close();
  • ContentValues 字段必须使用 MediaStore.* 常量,避免硬编码字段名引发兼容性崩溃。

遵循上述改造后,应用即可在 Android 9 至最新 Android 14 系统上稳定设置铃声,同时满足 Google Play 政策要求。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

android

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
android开发三大框架
android开发三大框架

android开发三大框架是XUtil框架、volley框架、ImageLoader框架。本专题为大家提供android开发三大框架相关的各种文章、以及下载和课程。

2023.08.14

757

4

android是什么系统
android是什么系统

Android是一种功能强大、灵活可定制、应用丰富、多任务处理能力强、兼容性好、网络连接能力强的操作系统。本专题为大家提供android相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.22

2058

5

android权限限制怎么解开
android权限限制怎么解开

android权限限制可以使用Root权限、第三方权限管理应用程序、ADB命令和Xposed框架解开。详细介绍:1、Root权限,通过获取Root权限,用户可以解锁所有权限,并对系统进行自定义和修改;2、第三方权限管理应用程序,用户可以轻松地控制和管理应用程序的权限;3、ADB命令,用户可以在设备上执行各种操作,包括解锁权限;4、Xposed框架,用户可以在不修改系统文件的情况下修改应用程序的行为和权限。

2023.09.19

3299

7

android重启应用的方法有哪些
android重启应用的方法有哪些

android重启应用有通过Intent、PendingIntent、系统服务、Runtime等方法。本专题为大家提供Android相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.18

382

5

Android语音播放功能实现方法
Android语音播放功能实现方法

实现方法有使用MediaPlayer实现、使用SoundPool实现两种。可以根据具体的需求选择适合的方法进行实现。想了解更多语音播放的相关内容,可以阅读本专题下面的文章。

2024.03.01

720

5

墨刀AI提示词教学
墨刀AI提示词教学

本合集由PHP中文网精心整理,为您提供全面的墨刀AI提示词教学。内容涵盖高质量原型撰写公式与实操窍门,助您轻松掌握AI设计工具。无论是零基础入门还是进阶技巧,都能让您快速上手,大幅提升产品设计与协作效率。

2026.08.04

10

21

墨刀AI完整入门
墨刀AI完整入门

PHP中文网为您倾力打造墨刀AI保姆级入门指南完整版!本合集从零基础讲起,涵盖AI生成原型、提示词优化、图片转原型及多轮对话等核心功能。无论您是新手还是进阶用户,都能轻松掌握产品设计全流程。快来PHP中文网,一键解锁高效设计技巧,让想法即刻成型!

2026.08.04

8

20

墨刀AI进阶技巧
墨刀AI进阶技巧

本合集由PHP中文网精心整理,为您提供墨刀AI核心进阶策略指南。内容涵盖高效提示词写作、原型智能生成与微调、结构化导图制作及行业分析报告输出等实战技巧。助您轻松掌握AI设计工具,大幅提升产品设计与团队协作效率。

2026.08.04

10

14

火山引擎实名认证失败怎么办
火山引擎实名认证失败怎么办

火山引擎实名认证失败可能与证件信息填写错误、姓名或企业信息不一致、证件照片不清晰、营业执照状态异常、手机号验证失败或审核资料不完整有关。本专题整理个人认证、企业认证、资料上传、审核退回、重新提交和认证不通过的常见处理方法。

2026.08.04

5

10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 39.5万人学习

Java 教程
Java 教程

共578课时 | 172万人学习

Uniapp从零开始实现新闻资讯应用
Uniapp从零开始实现新闻资讯应用

共64课时 | 8.6万人学习