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

使用Alarm Manager设置多重复闹钟仅最后一个生效的问题排查

问题分析与修复方案

核心问题1:通知ID重复导致覆盖

你在onReceive方法中使用固定的NOTIF_ID发送通知,所有闹钟触发时都会复用同一个ID。即使两个闹钟都正常触发,后弹出的通知会直接覆盖先弹出的,看起来就像只有最后一个闹钟生效了。

核心问题2:Alarm ID生成逻辑错误

在setMedicineScheduleAlarm里,alarmID的计算代码存在两处问题:

val alarmID = Integer.parseInt(timeArray[0]) + Integer.parseInt(timeArray[0]) + System.currentTimeMillis().toInt()
  • 重复累加了两次小时数(timeArray[0]是小时,被加了两次),导致不同时间的闹钟可能生成相同ID
  • System.currentTimeMillis()是13位的long类型数值,强制转int会溢出,生成的ID可能重复甚至变成负数,导致AlarmManager无法正确区分不同闹钟,后续设置的闹钟会覆盖之前的

核心问题3:冗余的通知渠道创建

每次触发通知都会执行notificationManager.createNotificationChannel(channel),虽然系统会忽略重复创建的请求,但这是不必要的性能损耗。


修复步骤

1. 为每个闹钟分配唯一的通知ID

修改setMedicineScheduleAlarm,把生成的alarmID通过Intent传递给Receiver:

// 在setMedicineScheduleAlarm中,创建Intent后添加
intent.putExtra(EXTRA_ALARM_ID, alarmID)

然后在onReceive中获取这个ID作为通知ID,同时把药名传入通知提升体验:

override fun onReceive(context: Context, intent: Intent) {
    val medicineName = intent.getStringExtra(EXTRA_MEDICINE_NAME)
    val notifId = intent.getIntExtra(EXTRA_ALARM_ID, NOTIF_ID)

    if (medicineName != null) {
        showAlarmNotification(context, notifId, medicineName)
    }
}

更新showAlarmNotification显示药名:

private fun showAlarmNotification(
    context: Context,
    notifId: Int,
    medicineName: String
) {
    val channelId = "Channel_1"
    val channelName = "AlarmManagerChannel"

    val notificationManager =
        context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    val alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
    val builder = NotificationCompat.Builder(context, channelId)
        .setSmallIcon(R.drawable.ic_notification_secondary_solid)
        .setContentTitle("该吃药啦")
        .setContentText("别忘了服用你的 $medicineName")
        .setColor(ContextCompat.getColor(context, android.R.color.transparent))
        .setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
        .setSound(alarmSound)

    val notification = builder.build()
    notificationManager.notify(notifId, notification)
}

2. 修复Alarm ID生成逻辑

改成能生成唯一ID的逻辑,比如结合时间和药名的哈希值:

val timeArray = time.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val hour = Integer.parseInt(timeArray[0])
val minute = Integer.parseInt(timeArray[1])
// 用小时*100+分钟 加上药名的哈希值,确保不同时间或不同药的闹钟ID唯一
val alarmID = (hour * 100 + minute) + medicineName.hashCode()

或者用更安全的方式生成唯一int值:

val alarmID = (System.nanoTime() % Int.MAX_VALUE).toInt()

3. 优化通知渠道创建

把通知渠道的创建移到应用启动时执行一次,比如在AlarmReceiver中添加静态初始化方法:

companion object {
    const val EXTRA_MEDICINE_NAME = "extra_medicine_name"
    const val EXTRA_ALARM_ID = "extra_alarm_id"
    const val NOTIF_ID = 1

    fun initNotificationChannel(context: Context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channelId = "Channel_1"
            val channelName = "AlarmManagerChannel"
            val channel = NotificationChannel(
                channelId,
                channelName,
                NotificationManager.IMPORTANCE_DEFAULT
            )
            channel.enableVibration(true)
            channel.vibrationPattern = longArrayOf(1000, 1000, 1000, 1000, 1000)
            val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}

然后在SettingsPreference的onCreatePreferences中调用一次:

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
    setPreferencesFromResource(R.xml.setting_preference, rootKey)
    AlarmReceiver.initNotificationChannel(requireContext())
    // ... 其他代码
}

4. 补充取消闹钟的逻辑

当关闭通知开关时,需要取消所有已设置的闹钟。建议把所有生成的alarmID保存到SharedPreferences中,取消时遍历删除:

// 在setMedicineScheduleAlarm中,保存alarmID到SharedPreferences
val sharedPref = context.getSharedPreferences("AlarmPrefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
val alarmIds = sharedPref.getStringSet("alarm_ids", mutableSetOf()) ?: mutableSetOf()
alarmIds.add(alarmID.toString())
editor.putStringSet("alarm_ids", alarmIds)
editor.apply()

然后在开关关闭时取消:

false -> {
    val alarmManager = requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
    val sharedPref = requireContext().getSharedPreferences("AlarmPrefs", Context.MODE_PRIVATE)
    val alarmIds = sharedPref.getStringSet("alarm_ids", mutableSetOf()) ?: mutableSetOf()
    
    alarmIds.forEach { idStr ->
        val id = idStr.toInt()
        val intent = Intent(requireContext(), AlarmReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(requireContext(), id, intent, PendingIntent.FLAG_NO_CREATE)
        pendingIntent?.let {
            alarmManager.cancel(it)
            it.cancel()
        }
    }
    
    // 清空保存的ID列表
    sharedPref.edit().remove("alarm_ids").apply()
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 19:36:18