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

Kotlin安卓闹钟更换外部存储音频后仍播放旧音频的问题求助

问题描述

我正在用Kotlin开发一款安卓闹钟实验应用,希望能从外部存储更换闹钟音频。已设置外部存储中的音频A作为闹钟音,之后重新选择外部存储中的音频B替换,但触发闹钟时仍播放旧音频A而非新音频B。


相关代码

选择音频按钮

binding.selectAudio.setOnClickListener()
{
    if (checkPermission())
    {
        openAudio()
    }
    else
    {
        requestPermission()
    }
}

跳转选择音频并获取路径的逻辑

private fun openAudio()
{
    //soundPath = null
    onStop()
    val openFileIntent = Intent(Intent.ACTION_GET_CONTENT)
    openFileIntent.type = "audio/*"

    getResult.launch(openFileIntent)
}
private val getResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{
    soundPath = null

    if (it.resultCode == RESULT_OK)
    {
        val uri: Uri? = it.data!!.data
        File(uri!!.getPath())

        val path = getRealPath(this, uri)
        soundPath = path

        if (soundPath != null)
        {
            Toast.makeText(this, "Audio file has been set", Toast.LENGTH_SHORT).show()
        }
    }
}

设置闹钟按钮

binding.setAlarmBtn.setOnClickListener()
{
    createNotificationChannel(soundPath)
    setAlarm()
    Log.e("notif", "soundpath" + soundPath.toString())
    Log.d("soundPath", soundPath.toString())
}

设置闹钟时间的逻辑

private fun setAlarm()
{
    try
    {
        alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
        val intent = Intent(this, AlarmReceiver::class.java)

        pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)

        alarmManager[AlarmManager.RTC_WAKEUP, calendar.timeInMillis] = pendingIntent

        Toast.makeText(this, "Alarm set successfully", Toast.LENGTH_SHORT).show()
    }
    catch (e: Exception)
    {
        Toast.makeText(this, "Time is not been set", Toast.LENGTH_SHORT).show()
    }
}

创建通知频道(关联音频)的逻辑

private fun createNotificationChannel(path: String?)
{
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
    {
        val uri = getUri(path)
        val attributes = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()

        val name : CharSequence = "AlarmReminderChannel"
        val description = "Channel For Alarm Manager"
        val importance = NotificationManager.IMPORTANCE_HIGH
        val channel = NotificationChannel("alarmManagerDemo", name, importance)
        channel.description = description
        channel.setSound(uri, attributes)

        Log.e("notif", "uri" + uri.toString())
        Log.e("notif", "uri from channel" + channel.sound.toString())

        val notificationManager = getSystemService(NotificationManager::class.java)
        notificationManager.createNotificationChannel(channel)
    }
}

获取音频Uri的逻辑

private fun getUri(path: String?) : Uri?
{
    Log.e("notif", "path: "+path )
    if(path == null)
    {
        Log.e("notif", "null" )
        return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + this.packageName + "/raw/music")
    }
    else
    {
        Log.e("notif", "tidak null" )
        return Uri.parse(path)
    }
}

广播接收器的onReceive逻辑

class AlarmReceiver : BroadcastReceiver()
{
    override fun onReceive(context: Context?, intent: Intent?)
    {
        val bundle : Bundle? = intent!!.extras
        val i = Intent(context, DestinationActivity::class.java)

        intent!!.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent = PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_IMMUTABLE)

        val builder = NotificationCompat.Builder(context!!, "alarmManagerDemo")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setContentTitle("Demo")
            .setContentText("Bla bla bla bla bla bla bla bla")
            .setAutoCancel(true)
            .setDefaults(NotificationCompat.DEFAULT_ALL)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setContentIntent(pendingIntent)

        val notificationManager = NotificationManagerCompat.from(context)
        notificationManager.notify(123, builder.build())

    }
}

问题原因

  1. 通知频道属性不可修改:Android O及以上版本,通知频道创建后,声音等核心属性无法直接更新,重复调用createNotificationChannel不会覆盖已有配置,导致旧音频设置保留。
  2. PendingIntent未更新:原代码中创建PendingIntent时未使用更新标记,可能导致广播接收器无法获取最新的音频配置。
  3. 文件路径处理存在风险:通过getRealPath获取的本地路径在Android 10+上可能因存储权限限制无法访问,且路径转Uri的方式容易引发权限问题。

解决方案

1. 删除旧通知频道后重建

修改createNotificationChannel函数,每次创建前删除已存在的频道,确保新配置生效:

private fun createNotificationChannel(path: String?) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val notificationManager = getSystemService(NotificationManager::class.java)
        // 先删除旧频道
        notificationManager.deleteNotificationChannel("alarmManagerDemo")
        
        val uri = getUri(path)
        val attributes = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build()

        val name: CharSequence = "AlarmReminderChannel"
        val description = "Channel For Alarm Manager"
        val importance = NotificationManager.IMPORTANCE_HIGH
        val channel = NotificationChannel("alarmManagerDemo", name, importance)
        channel.description = description
        channel.setSound(uri, attributes)

        Log.e("notif", "uri" + uri.toString())
        Log.e("notif", "uri from channel" + channel.sound.toString())

        notificationManager.createNotificationChannel(channel)
    }
}

2. 更新PendingIntent创建逻辑

使用FLAG_UPDATE_CURRENT标记确保PendingIntent更新,同时传递音频路径到广播接收器:

private fun setAlarm() {
    try {
        alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
        val intent = Intent(this, AlarmReceiver::class.java)
        // 传递音频路径到接收器
        intent.putExtra("SOUND_PATH", soundPath)

        // 确保PendingIntent更新
        pendingIntent = PendingIntent.getBroadcast(
            this, 
            0, 
            intent, 
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        alarmManager[AlarmManager.RTC_WAKEUP, calendar.timeInMillis] = pendingIntent

        Toast.makeText(this, "Alarm set successfully", Toast.LENGTH_SHORT).show()
    } catch (e: Exception) {
        Toast.makeText(this, "Time is not been set", Toast.LENGTH_SHORT).show()
    }
}

3. 优化音频Uri存储方式

直接存储选择音频时得到的Content Uri,避免文件路径转换的权限问题:
修改getResult回调:

private val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    soundPath = null

    if (it.resultCode == RESULT_OK) {
        val uri: Uri? = it.data?.data
        if (uri != null) {
            // 直接存储Uri字符串
            soundPath = uri.toString()
            Toast.makeText(this, "Audio file has been set", Toast.LENGTH_SHORT).show()
        }
    }
}

4. 可选:在广播接收器中直接播放音频

如果依赖通知频道不够灵活,可在接收器中用MediaPlayer直接播放最新音频:

class AlarmReceiver : BroadcastReceiver() {
    private var mediaPlayer: MediaPlayer? = null

    override fun onReceive(context: Context?, intent: Intent?) {
        val soundPath = intent?.getStringExtra("SOUND_PATH")
        
        // 播放自定义音频
        soundPath?.let {
            try {
                mediaPlayer = MediaPlayer().apply {
                    setAudioAttributes(
                        AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_ALARM)
                            .build()
                    )
                    setDataSource(context, Uri.parse(it))
                    prepare()
                    start()
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

        val i = Intent(context, DestinationActivity::class.java)
        i.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent = PendingIntent.getActivity(context, 0, i, PendingIntent.FLAG_IMMUTABLE)

        val builder = NotificationCompat.Builder(context!!, "alarmManagerDemo")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setContentTitle("Demo")
            .setContentText("Bla bla bla bla bla bla bla bla")
            .setAutoCancel(true)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setContentIntent(pendingIntent)
            // 移除默认声音,避免冲突
            // .setDefaults(NotificationCompat.DEFAULT_ALL)

        val notificationManager = NotificationManagerCompat.from(context)
        notificationManager.notify(123, builder.build())

        // 播放完毕释放资源
        mediaPlayer?.setOnCompletionListener {
            it.release()
            mediaPlayer = null
        }
    }
}

内容的提问来源于stack exchange,提问作者Pup In The Tree

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 04:10:35