使用AlarmManager调度多通知时丢失问题排查求助
我来帮你拆解下你遇到的问题——调度最多14个通知时随机丢失,尤其是未连调试线时,前几个触发后后续就没反应,重新调度又恢复。结合你的代码和Android的后台机制,主要有这几个核心原因:
1. 唤醒通知渠道的动态删除重建,导致旧闹钟的通知失效
看你的createWakeUpNotificationChannel方法:每次调用它时,都会删除所有包含WAKE_UP_ALARM_NOTIFICATION_CHANNEL的渠道,然后创建一个带随机数的新渠道。
这是个致命问题:当你第一次调度唤醒闹钟时,生成的通知是绑定到当时的渠道ID的。但如果之后你再次调用configureAlarmNotification(比如修改闹钟设置后),旧渠道会被删掉,之前已经调度好的闹钟触发时,对应的渠道已经不存在了——系统没法显示一个没有对应渠道的通知,这就会让你误以为闹钟没触发,但实际上是通知无法展示。
而且未连调试线时,应用更容易被后台回收,你可能会更频繁地重新调度,这个问题就会更突出。
2. Android后台限制(Doze模式/应用待机)拦截了普通闹钟
当设备没连调试线时,很容易进入Doze模式(Android 6.0及以上)或者应用待机模式,这两种模式会严格限制后台操作:
- 你现在用的
setExact()属于普通闹钟,在Doze模式下会被推迟到设备的维护窗口才触发,甚至直接被忽略,尤其是应用不在前台的时候。 - 如果你的闹钟间隔较长,系统会把它们判定为非关键任务,进一步限制触发。
3. PendingIntent的复用风险(潜在隐患)
你的getPendingIntent用了PendingIntent.FLAG_UPDATE_CURRENT,虽然你给每个闹钟分配了唯一的notificationId作为requestCode,理论上不会复用,但如果WAKE_UP_NOTIFICATION_ID_START和BEDTIME_NOTIFICATION_ID_START的取值范围有重叠,就可能导致PendingIntent被意外覆盖,进而让后续闹钟失效。
针对性的解决方案
修复唤醒通知渠道的问题
别每次都删旧渠道建新的,固定渠道ID,只在渠道不存在时创建:
private fun createWakeUpNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationManager = m_context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // 先检查渠道是否存在,不存在才创建 val existingChannel = notificationManager.getNotificationChannel(WAKE_UP_ALARM_NOTIFICATION_CHANNEL) if (existingChannel == null) { createNotificationChannel( WAKE_UP_NOTIFICATION_CHANNEL_NAME, WAKE_UP_NOTIFICATION_CHANNEL_DESCRIPTION, WAKE_UP_ALARM_NOTIFICATION_CHANNEL, true ) } } }
这样所有唤醒通知都绑定到同一个固定渠道,不会因为渠道被删除而“消失”。
用高优先级API突破后台限制
对于像起床闹钟这种需要可靠触发的任务,要用setExactAndAllowWhileIdle()或者setAlarmClock(),这两个API可以绕过Doze模式的限制:
// 替换原来的setExact调用 val alarmManager = m_context.getSystemService(ALARM_SERVICE) as AlarmManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 适配Android 6.0+的Doze模式 alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) } else { alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pendingIntent) }
注意:setExactAndAllowWhileIdle()有每15分钟最多触发一次的限制,但对于每天一次的闹钟来说完全够用。
确保PendingIntent的requestCode绝对唯一
确认WAKE_UP_NOTIFICATION_ID_START和BEDTIME_NOTIFICATION_ID_START的取值范围完全不重叠,比如:
// 示例:唤醒通知ID从100开始,睡前通知从200开始,避免重叠 private const val WAKE_UP_NOTIFICATION_ID_START = 100 private const val BEDTIME_NOTIFICATION_ID_START = 200
这样每个闹钟的requestCode都是唯一的,彻底避免PendingIntent被意外覆盖。
额外提醒:监听设备重启,重新调度闹钟
AlarmManager的闹钟在设备重启后会全部失效,所以你需要注册BOOT_COMPLETED广播接收器,在设备重启完成后重新调度所有闹钟。
内容的提问来源于stack exchange,提问作者Oguz Uncu




