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

使用AlarmManager调度多通知时丢失问题排查求助

你的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_STARTBEDTIME_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_STARTBEDTIME_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

火山引擎 最新活动