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

