You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android应用新增从自定义文件夹设MP3为铃声按钮技术求助

实现「设置为铃声」按钮的完整方案

一、先搞定必要权限

设置铃声需要特殊权限和存储权限,先在AndroidManifest.xml里添加:

<!-- 特殊权限:修改系统设置 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"
    android:maxSdkVersion="32" />
<!-- Android 13+ 音频文件读取权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"
    android:minSdkVersion="33" />
<!-- Android 12及以下 外部存储读取权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

注意WRITE_SETTINGS是特殊权限,不能通过普通动态请求获取,得引导用户跳转到系统设置页面授权:

private fun checkWriteSettingsPermission(): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Settings.System.canWrite(this)
    } else {
        true // 低版本默认允许修改设置
    }
}

// 调用这个方法引导用户授权
private fun requestWriteSettingsPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
        intent.data = Uri.parse("package:" + packageName)
        startActivityForResult(intent, 1001)
    }
}

二、获取自定义文件夹里的MP3文件

假设你的自定义文件夹路径是/sdcard/MyAppDownloads(根据你实际的路径调整),这里提供两种适配不同Android版本的方式:

方式1:直接遍历文件夹(适合Android 9及以下)

private fun getCustomFolderMP3s(): List<File> {
    val customFolder = File(Environment.getExternalStorageDirectory(), "MyAppDownloads")
    val mp3List = mutableListOf<File>()
    if (customFolder.exists() && customFolder.isDirectory) {
        customFolder.listFiles()?.forEach { file ->
            if (file.name.endsWith(".mp3", ignoreCase = true)) {
                mp3List.add(file)
            }
        }
    }
    return mp3List
}

方式2:用MediaStore查询(Android 10+推荐,适配分区存储)

private fun getCustomFolderMP3sViaMediaStore(): List<Uri> {
    val mp3Uris = mutableListOf<Uri>()
    val projection = arrayOf(MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DISPLAY_NAME)
    // 这里的%MyAppDownloads%对应你的自定义文件夹相对路径
    val selection = "${MediaStore.Audio.Media.RELATIVE_PATH} LIKE ?"
    val selectionArgs = arrayOf("%MyAppDownloads%")
    val cursor = contentResolver.query(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        projection,
        selection,
        selectionArgs,
        null
    )
    cursor?.use {
        val idColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
        while (it.moveToNext()) {
            val id = it.getLong(idColumn)
            val uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id)
            mp3Uris.add(uri)
        }
    }
    return mp3Uris
}

三、核心:设置铃声的工具方法

这个方法适配Android各个版本,把选中的MP3设置为系统铃声:

private fun setAsRingtone(audioUri: Uri, context: Context) {
    try {
        val values = ContentValues().apply {
            put(MediaStore.Audio.Media.IS_RINGTONE, true)
            put(MediaStore.Audio.Media.IS_NOTIFICATION, false)
            put(MediaStore.Audio.Media.IS_ALARM, false)
            put(MediaStore.Audio.Media.IS_MUSIC, false)
        }
        contentResolver.update(audioUri, values, null, null)

        val ringtoneManager = RingtoneManager(context)
        ringtoneManager.setType(RingtoneManager.TYPE_RINGTONE)
        ringtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE, audioUri)

        // 通知系统刷新铃声列表,避免设置后不生效
        context.sendBroadcast(Intent(RingtoneManager.ACTION_RINGTONE_CHANGED))
        Toast.makeText(context, "铃声设置成功!", Toast.LENGTH_SHORT).show()
    } catch (e: Exception) {
        e.printStackTrace()
        Toast.makeText(context, "设置失败:${e.message}", Toast.LENGTH_SHORT).show()
    }
}

四、按钮点击事件逻辑

把以上逻辑整合到你的「设置为铃声」按钮点击事件里:

btnSetRingtone.setOnClickListener {
    // 先检查权限
    if (!checkWriteSettingsPermission()) {
        requestWriteSettingsPermission()
        return@setOnClickListener
    }

    // 根据Android版本选择对应的文件获取方式
    val mp3Files = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        getCustomFolderMP3sViaMediaStore()
    } else {
        getCustomFolderMP3s().map { Uri.fromFile(it) }
    }

    if (mp3Files.isEmpty()) {
        Toast.makeText(this, "自定义文件夹里没有MP3文件哦", Toast.LENGTH_SHORT).show()
        return@setOnClickListener
    }

    // 弹出对话框让用户选择具体的MP3文件
    val mp3Names = mp3Files.map {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentResolver.query(it, arrayOf(MediaStore.Audio.Media.DISPLAY_NAME), null, null, null)?.use { cursor ->
                cursor.moveToFirst()
                cursor.getString(0)
            } ?: "未知文件"
        } else {
            File(it.path).name
        }
    }.toTypedArray()

    AlertDialog.Builder(this)
        .setTitle("选择要设置的铃声")
        .setItems(mp3Names) { _, which ->
            val selectedUri = mp3Files[which]
            setAsRingtone(selectedUri, this)
        }
        .show()
}

最后几个注意点

  • 如果你是Android 10+,建议把自定义文件夹放在getExternalFilesDir(Environment.DIRECTORY_MUSIC)下的子文件夹,这样不需要额外的存储权限(属于应用私有目录)
  • 测试时记得先下载一个MP3到自定义文件夹,再点击按钮
  • 部分国产ROM可能有额外的权限限制,需要确保应用已获取「修改系统设置」的权限

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

火山引擎 最新活动