
本文介绍如何在 android(java)中可靠实现每日 1–3 次、指定时间段内触发的重复通知,推荐使用 workmanager 替代已弃用且系统限制严格的 alarmmanager,并给出完整可落地的实现方案。
本文介绍如何在 android(java)中可靠实现每日 1–3 次、指定时间段内触发的重复通知,推荐使用 workmanager 替代已弃用且系统限制严格的 alarmmanager,并给出完整可落地的实现方案。
在 Android 8.0(API 26)及以上版本中,AlarmManager.setRepeating() 已被系统大幅限制,尤其在设备待机(Doze)、应用处于后台或电池优化启用时,重复闹钟极易失效;而 setExactAndAllowWhileIdle() 仅支持单次精确触发,无法自动循环。你当前基于 onCreate 和广播接收器的“手动重调度”逻辑虽能短期生效,但因缺少持久化触发机制和系统级唤醒保障,长期运行后通知必然丢失——这正是 Telegram 等应用偶发漏推的根本原因,而 Duolingo 等稳定应用普遍采用的是 WorkManager + PeriodicWorkRequest 这一官方推荐的现代替代方案。
✅ 核心优势:
- ✅ 系统级兼容:自动适配 Doze、App Standby、后台执行限制(Android 8+)及省电策略;
- ✅ 持久可靠:即使 App 被杀、设备重启,WorkManager 仍能恢复任务(需正确配置
PersistableBundle); - ✅ 语义清晰:以“周期性工作”而非“定时闹钟”建模,更贴合通知场景本质;
- ✅ 多任务并行:可为早/中/晚三个时段分别创建独立
PeriodicWorkRequest,互不干扰。
? 具体实现步骤(Java):
-
添加依赖(app/build.gradle):
implementation 'androidx.work:work-runtime-ktx:2.9.0' // 推荐使用 KTX 版本(Java 也可用) // 若坚持纯 Java,用: // implementation 'androidx.work:work-runtime:2.9.0'
-
定义通知 Worker(继承
Worker):public class DailyNotificationWorker extends Worker { private static final String KEY_NOTIFICATION_TIME_SLOT = "time_slot"; // "morning", "afternoon", "evening" public DailyNotificationWorker(@NonNull Context context, @NonNull WorkerParameters params) { super(context, params); } @NonNull @Override public Result doWork() { String slot = getInputData().getString(KEY_NOTIFICATION_TIME_SLOT); if (slot == null) return Result.failure(); // 根据时段生成对应通知内容(示例) String title = switch (slot) { case "morning" -> "早安提醒"; case "afternoon" -> "午间小憩"; case "evening" -> "晚间总结"; default -> "通知"; }; sendNotification(title, getApplicationContext()); return Result.success(); } private void sendNotification(String title, Context context) { NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(title) .setContentText("点击查看详情") .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setAutoCancel(true) .setChannelId(CHANNEL_ID); NotificationManagerCompat.from(context).notify( title.hashCode(), // 简单去重 ID builder.build() ); } } -
创建并调度三个时段的周期任务(建议在 Application.onCreate 或首次启动时调用):
private void scheduleDailyNotifications() { // 必须先确保通知渠道已创建(Android 8+) createNotificationChannel(this); // 【早晨】目标窗口:09:00–11:00 → 设定首次触发在 09:00,之后每天重复 schedulePeriodicWork("morning", 9, 0); // 【下午】目标窗口:13:00–18:00 → 首次触发 13:00 schedulePeriodicWork("afternoon", 13, 0); // 【晚上】目标窗口:20:00–22:00 → 首次触发 20:00 schedulePeriodicWork("evening", 20, 0); }
private void schedulePeriodicWork(String slot, int hour, int minute) { // 计算首次触发的延迟(毫秒) Calendar firstRun = Calendar.getInstance(); firstRun.set(Calendar.HOUR_OF_DAY, hour); firstRun.set(Calendar.MINUTE, minute); firstRun.set(Calendar.SECOND, 0); firstRun.set(Calendar.MILLISECOND, 0);
// 若已过今日该时间,则顺延至明日
long now = System.currentTimeMillis();
if (firstRun.getTimeInMillis() <= now) {
firstRun.add(Calendar.DAY_OF_MONTH, 1);
}
long initialDelay = firstRun.getTimeInMillis() - now;
// 构建输入参数
Data inputData = new Data.Builder()
.putString(KEY_NOTIFICATION_TIME_SLOT, slot)
.build();
// 创建周期任务:最小间隔为 15 分钟,此处设为 24 小时(即每天一次)
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(
DailyNotificationWorker.class,
24, TimeUnit.HOURS)
.setInitialDelay(initialDelay, TimeUnit.MILLISECONDS)
.setInputData(inputData)
.setConstraints(new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 可选:如需联网
.build())
.build();
// 提交任务(WorkManager 自动去重,同名任务不会重复注册)
WorkManager.getInstance(this)
.enqueueUniquePeriodicWork(
"notification_" + slot,
ExistingPeriodicWorkPolicy.KEEP,
request
);}
⚠️ **关键注意事项:** - **时间精度说明:** WorkManager 不保证毫秒级准时(尤其在低功耗模式下),但默认窗口误差通常在 ±10 分钟内,完全满足“09:00–11:00”这类宽松时段要求;若需更高精度(如闹钟),应结合前台服务 + `AlarmManager.setExactAndAllowWhileIdle()`,但需严格遵循后台限制政策。 - **唯一性保障:** 使用 `enqueueUniquePeriodicWork(..., KEEP)` 可防止重复注册,避免多个相同任务堆积。 - **设备重启恢复:** WorkManager 默认支持重启恢复(需 `minSdkVersion >= 14`),无需额外处理。 - **权限与渠道:** Android 13+ 需动态申请 `POST_NOTIFICATIONS` 权限;所有通知必须绑定有效 `NotificationChannel`(代码中 `createNotificationChannel()` 已涵盖)。 - **调试技巧:** 开启 `adb shell dumpsys jobscheduler` 可查看当前排队的 WorkManager 任务状态。 ? **总结:** 放弃 `AlarmManager.setRepeating()` 和手动重调度的脆弱方案,全面转向 `WorkManager` 是 Android 8.0+ 实现可靠重复通知的**唯一官方推荐路径**。通过为每个通知时段创建独立的 `PeriodicWorkRequest`,配合合理的初始延迟计算与唯一性策略,即可实现如 Duolingo 般稳定、省电、免维护的每日多时段提醒体验——简洁、健壮,且面向未来。


















