Android:如何构建满足特定重复规则的Periodic WorkManager以定时发送通知?
用WorkManager实现定时重复通知方案
嘿,我来帮你搞定这两个定时通知需求!WorkManager确实是Android上处理这类后台重复任务的最佳选择之一,不过针对每周固定时间和每月固定日期且限次这两种不同规则,实现方式略有区别,咱们一步步来拆解:
1. 基础准备:创建Worker类与通知渠道
首先,我们需要一个核心的Worker类来处理通知发送逻辑,同时要记得为Android 8.0+创建通知渠道(否则通知发不出来)。
1.1 通知渠道初始化(在Application或启动页完成)
class MyApp : Application() { override fun onCreate() { super.onCreate() createNotificationChannel() } private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "REMINDER_CHANNEL", "定时提醒频道", NotificationManager.IMPORTANCE_DEFAULT ).apply { description = "用于发送每周/每月的定时提醒通知" } val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) } } }
1.2 核心Worker类实现
这个Worker既处理每周通知,也处理每月通知,通过输入数据区分任务类型,同时负责每月任务的后续调度(直到满6次):
class NotificationWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // 获取任务类型和当前执行次数 val taskType = inputData.getString("TASK_TYPE") ?: "" val currentCount = inputData.getInt("EXECUTION_COUNT", 1) // 发送对应类型的通知 sendReminderNotification(taskType) // 每月任务专属:如果还没到6次,调度下一次 if (taskType == "MONTHLY" && currentCount < 6) { scheduleNextMonthlyTask(currentCount + 1) } return Result.success() } private fun sendReminderNotification(taskType: String) { val (title, content) = when(taskType) { "WEEKLY" -> "每周周二提醒" to "今天是周二14:30,别忘了你的待办哦!" "MONTHLY" -> "每月15号提醒" to "今天是15号,月度提醒已送达!" else -> "定时提醒" to "你的定时任务已触发" } val notification = NotificationCompat.Builder(applicationContext, "REMINDER_CHANNEL") .setSmallIcon(R.drawable.ic_notification) // 替换成你的通知图标 .setContentTitle(title) .setContentText(content) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .build() val notificationManager = applicationContext.getSystemService(NotificationManager::class.java) notificationManager.notify(System.currentTimeMillis().toInt(), notification) } private fun scheduleNextMonthlyTask(nextCount: Int) { // 计算下一个15号14:30的时间戳 val targetCalendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 14) set(Calendar.MINUTE, 30) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) // 判断当前时间是否已过本月15号14:30,是的话直接跳到下个月 val isPastTarget = when { get(Calendar.DAY_OF_MONTH) > 15 -> true get(Calendar.DAY_OF_MONTH) == 15 -> get(Calendar.HOUR_OF_DAY) > 14 || (get(Calendar.HOUR_OF_DAY) == 14 && get(Calendar.MINUTE) >= 30) else -> false } if (isPastTarget) { add(Calendar.MONTH, 1) } set(Calendar.DAY_OF_MONTH, 15) } val delayMillis = targetCalendar.timeInMillis - System.currentTimeMillis() // 构建下一次的单次任务请求 val nextWorkRequest = OneTimeWorkRequestBuilder<NotificationWorker>() .setInitialDelay(delayMillis, TimeUnit.MILLISECONDS) .setInputData(workDataOf( "TASK_TYPE" to "MONTHLY", "EXECUTION_COUNT" to nextCount )) .build() WorkManager.getInstance(applicationContext).enqueue(nextWorkRequest) } }
2. 实现每周二14:30的重复通知
每周任务是固定间隔(7天)的重复任务,我们用PeriodicWorkRequest来实现,关键是计算第一次执行的初始延迟,确保第一次在周二14:30触发:
fun scheduleWeeklyReminder(context: Context) { val targetCalendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 14) set(Calendar.MINUTE, 30) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) // 找到下一个周二 while (get(Calendar.DAY_OF_WEEK) != Calendar.TUESDAY) { add(Calendar.DAY_OF_MONTH, 1) } // 如果今天就是周二,但已经过了14:30,就推迟到下周二 if (System.currentTimeMillis() > timeInMillis) { add(Calendar.DAY_OF_MONTH, 7) } } val initialDelay = targetCalendar.timeInMillis - System.currentTimeMillis() // 构建周期性任务,间隔7天 val weeklyWorkRequest = PeriodicWorkRequestBuilder<NotificationWorker>(7, TimeUnit.DAYS) .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) .setInputData(workDataOf("TASK_TYPE" to "WEEKLY")) .build() // 用唯一名称确保不会重复创建任务 WorkManager.getInstance(context).enqueueUniquePeriodicWork( "WEEKLY_REMINDER_TASK", ExistingPeriodicWorkPolicy.REPLACE, // 如果已有相同任务,替换掉 weeklyWorkRequest ) }
3. 实现每月15号(仅执行6次)的重复通知
WorkManager没有直接支持每月重复的API(因为每月天数不固定),所以我们用OneTimeWorkRequest链式调度:每次执行完后,自动调度下一次,直到满6次为止。
3.1 启动第一个每月任务
fun scheduleFirstMonthlyReminder(context: Context) { val targetCalendar = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 14) set(Calendar.MINUTE, 30) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) // 判断是否已过本月15号14:30 val isPastTarget = when { get(Calendar.DAY_OF_MONTH) > 15 -> true get(Calendar.DAY_OF_MONTH) == 15 -> get(Calendar.HOUR_OF_DAY) > 14 || (get(Calendar.HOUR_OF_DAY) == 14 && get(Calendar.MINUTE) >= 30) else -> false } if (isPastTarget) { add(Calendar.MONTH, 1) } set(Calendar.DAY_OF_MONTH, 15) } val initialDelay = targetCalendar.timeInMillis - System.currentTimeMillis() val firstMonthlyRequest = OneTimeWorkRequestBuilder<NotificationWorker>() .setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) .setInputData(workDataOf( "TASK_TYPE" to "MONTHLY", "EXECUTION_COUNT" to 1 )) .build() WorkManager.getInstance(context).enqueue(firstMonthlyRequest) }
关键注意事项
- 权限问题:Android 13及以上需要申请
POST_NOTIFICATIONS权限,记得在Manifest中声明,并在运行时请求用户授权。 - 后台限制:Android 12+对后台任务有严格限制,WorkManager会尽量保证任务执行,但如果设备处于深度休眠,可能会延迟。如果需要更精确的触发,可以考虑结合
AlarmManager(但需要处理Doze模式)。 - 任务唯一性:每周任务用
enqueueUniquePeriodicWork避免重复创建;每月任务只需调用一次scheduleFirstMonthlyReminder,后续由Worker自动调度。 - 测试技巧:开发时可以把初始延迟改成几秒(比如
setInitialDelay(5, TimeUnit.SECONDS)),快速验证任务是否正常执行。
内容的提问来源于stack exchange,提问作者RFM




