在Android中使用Kotlin实现每年固定日期推送用户通知的最优方案是什么
Android Kotlin实现年度指定日期发送通知的最优方案
核心选型说明
选择 AlarmManager 作为核心实现组件,WorkManager 仅适用于非时间敏感的后台任务,系统会对其做批量调度优化,无法保证每年固定日期准点触发,因此AlarmManager + 广播接收器是该需求的最优实现。
前置权限配置
需要提前申请的权限如下:
- Android 13(API 33)及以上:
POST_NOTIFICATIONS运行时权限,是发送通知的前提 - Android 12(API 31)及以上:
SCHEDULE_EXACT_ALARM权限,用于设置精确时间的闹钟 - 系统重启监听权限:
RECEIVE_BOOT_COMPLETED,用于系统重启后恢复闹钟配置 - 可选适配:国产ROM(小米、华为、vivo等)需要引导用户开启应用自启动权限、加入省电白名单,避免应用被后台查杀后无法收到闹钟广播
核心实现步骤
1. 定义闹钟触发广播接收器
用于接收闹钟触发事件,触发后发送通知,代码如下:
class AnnualNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Android 8.0及以上需先创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "annual_notify_channel", "年度定时提醒", NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "每年指定日期触发的通知提醒" } notificationManager.createNotificationChannel(channel) } // 构建通知,可根据需求自定义内容 val notification = NotificationCompat.Builder(context, "annual_notify_channel") .setSmallIcon(R.drawable.ic_notification) // 替换为应用自身的通知图标 .setContentTitle("年度提醒") .setContentText("你预设的年度提醒时间已到") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) .build() notificationManager.notify((System.currentTimeMillis() % 10000).toInt(), notification) // 触发后自动设置下一年的闹钟 val savedDate = getSavedTargetDate(context) // 从本地存储读取用户设置的月、日、时间 setAnnualAlarm(context, savedDate.month, savedDate.day, savedDate.hour, savedDate.minute) } }
在AndroidManifest.xml中注册该接收器:
<receiver android:name=".AnnualNotificationReceiver" android:exported="false" />
2. 实现年度闹钟设置方法
将用户设置的目标日期持久化存储到本地(如SharedPreferences),调用如下方法设置闹钟:
fun setAnnualAlarm(context: Context, targetMonth: Int, targetDay: Int, hour: Int, minute: Int) { val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val intent = Intent(context, AnnualNotificationReceiver::class.java) val pendingIntent = PendingIntent.getBroadcast( context, 10001, // 固定唯一请求码,用于区分不同闹钟 intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) // 计算下次触发的时间戳 val triggerTime = Calendar.getInstance().apply { set(Calendar.MONTH, targetMonth - 1) // Calendar月份从0开始计数 set(Calendar.DAY_OF_MONTH, targetDay) set(Calendar.HOUR_OF_DAY, hour) set(Calendar.MINUTE, minute) set(Calendar.SECOND, 0) // 若今年的目标时间已过,自动顺延到下一年 if (timeInMillis < System.currentTimeMillis()) { add(Calendar.YEAR, 1) } }.timeInMillis // 设置精确闹钟,低电模式下也可触发 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ) } else { alarmManager.setExact( AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent ) } }
注意:AlarmManager没有原生支持年度重复的触发配置,每次触发后必须手动重新设置下一年的闹钟
3. 处理系统重启后闹钟丢失问题
系统重启后所有AlarmManager设置的闹钟会被清空,需要监听开机广播重新设置:
首先在AndroidManifest.xml中添加权限和广播注册:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <receiver android:name=".BootCompleteReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
实现开机广播接收器:
class BootCompleteReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent?) { if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { val savedDate = getSavedTargetDate(context) setAnnualAlarm(context, savedDate.month, savedDate.day, savedDate.hour, savedDate.minute) } } }
适配注意事项
- 每次设置闹钟前需要检查是否已获取
SCHEDULE_EXACT_ALARM权限,Android 14及以上该权限需要用户主动授权,未授权时只能设置非精确闹钟,误差可达数小时 - 若允许用户设置多个不同日期的年度提醒,需要为每个提醒分配不同的PendingIntent请求码,避免覆盖
- 不要使用
setRepeating方法设置年度重复,该方法设置的是不精确闹钟,无法保证触发时间准确性
内容的提问来源于stack exchange,提问作者Paweł Mańczak
相关产品推荐
相关产品推荐

