Android如何实现指定星期按小时间隔重复的周循环通知提醒
实现方案
你原有的setRepeating方案无法支持星期筛选,主要有两个问题:一是该方法从API 19开始触发时间不精确,会被系统做省电对齐;二是你用的ELAPSED_REALTIME_WAKEUP基于开机时长计算,无法直接关联日期星期属性。
最稳定的实现方式是单次精确闹钟 + 触发后动态排下一次任务,自动跳过未选中的星期,具体实现步骤如下:
核心逻辑说明
- 放弃全局固定重复闹钟,每次只设置下一次的精确触发闹钟
- 每次闹钟触发后,先执行通知发送逻辑,再根据用户配置的星期、时间间隔计算下一次合法的触发时间,重新设置闹钟
- 计算触发时间时自动跳过用户未选中的星期,跳转到下一个符合要求的时间点
具体实现代码
1. 下一次触发时间计算方法
/** * 计算下一个合法的触发时间 * @param selectedWeekDays 用户选中的生效星期集合,取值为Calendar.MONDAY~Calendar.SUNDAY * @param repeatIntervalMs 用户设置的重复间隔,单位毫秒 * @return 下一次触发的时间戳(RTC时区) */ private long calculateNextTriggerTime(Set<Integer> selectedWeekDays, long repeatIntervalMs) { Calendar cal = Calendar.getInstance(); long now = cal.getTimeInMillis(); // 先叠加一次间隔,避免和当前触发时间冲突 cal.setTimeInMillis(now + repeatIntervalMs); // 最多遍历8天,覆盖一周7天的范围,避免死循环 for (int i = 0; i < 8; i++) { int currentDayOfWeek = cal.get(Calendar.DAY_OF_WEEK); if (selectedWeekDays.contains(currentDayOfWeek)) { return cal.getTimeInMillis(); } // 当前日期不在选中范围内,跳转到下一天的0点重新判断 cal.add(Calendar.DAY_OF_YEAR, 1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); } // 容错返回,正常逻辑不会走到这里 return now + 24 * 60 * 60 * 1000L; }
2. 修改排期方法
注意把闹钟类型从ELAPSED_REALTIME_WAKEUP改为RTC_WAKEUP,基于系统真实时间计算才能正确匹配星期:
private void scheduleNotification(String notificationTitle, int notificationID, int hours, int minutes, Set<Integer> selectedWeekDays) { Intent intent = new Intent(mContext, NotificationPanelReceiver.class); intent.putExtra("title", notificationTitle); intent.putExtra("notificationID", notificationID); // 把配置参数存入Intent,供触发后排下一次闹钟使用 intent.putExtra("hours", hours); intent.putExtra("minutes", minutes); intent.putIntegerArrayListExtra("selectedWeekDays", new ArrayList<>(selectedWeekDays)); int piFlag = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent pendingIntent = PendingIntent.getBroadcast( mContext, notificationID, intent, piFlag); AlarmManager manager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); long repeatInterval = (minutes * 60 * 1000L) + (hours * 60 * 60 * 1000L); long nextTriggerTime = calculateNextTriggerTime(selectedWeekDays, repeatInterval); // 分版本设置精确闹钟 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { manager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { manager.setExact( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } else { manager.set( AlarmManager.RTC_WAKEUP, nextTriggerTime, pendingIntent ); } }
3. 修改广播接收逻辑
在通知发送完成后,自动排下一次的闹钟:
public class NotificationPanelReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 原有通知发送逻辑 String title = intent.getStringExtra("title"); int notificationId = intent.getIntExtra("notificationID", 0); // 这里执行你构造通知、发送通知的原有逻辑 // sendNotification(title, notificationId); // 计算并设置下一次闹钟 int hours = intent.getIntExtra("hours", 0); int minutes = intent.getIntExtra("minutes", 0); ArrayList<Integer> weekDaysList = intent.getIntegerArrayListExtra("selectedWeekDays"); Set<Integer> selectedWeekDays = new HashSet<>(weekDaysList); long repeatInterval = (minutes * 60 * 1000L) + (hours * 60 * 60 * 1000L); long nextTriggerTime = calculateNextTriggerTime(selectedWeekDays, repeatInterval); Intent nextIntent = new Intent(context, NotificationPanelReceiver.class); nextIntent.putExtras(intent); PendingIntent nextPi = PendingIntent.getBroadcast( context, notificationId, nextIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT ); AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { manager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextTriggerTime, nextPi); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { manager.setExact(AlarmManager.RTC_WAKEUP, nextTriggerTime, nextPi); } else { manager.set(AlarmManager.RTC_WAKEUP, nextTriggerTime, nextPi); } } }
注意事项
Calendar.DAY_OF_WEEK的取值规则为:周日=1、周一=2、周二=3、周三=4、周四=5、周五=6、周六=7,前端星期选择控件的返回值要和这个规则对齐,避免筛选失效- Android 12及以上需要在Manifest中声明
SCHEDULE_EXACT_ALARM权限,Android 13及以上需要声明POST_NOTIFICATIONS权限,否则闹钟和通知会被系统拦截 - 设备重启后AlarmManager设置的闹钟会被清空,需要额外注册监听
BOOT_COMPLETED的广播,开机后重新执行所有提醒的排期逻辑 - 用户修改提醒配置时,要先用相同的notificationID构造PendingIntent,调用
AlarmManager.cancel()取消旧闹钟,再重新排期,避免出现重复提醒 - 针对国产定制ROM,需要引导用户将应用加入电池优化白名单、允许后台自启动,否则系统可能会拦截后台闹钟触发。
内容的提问来源于stack exchange,提问作者johneggo
相关产品推荐
相关产品推荐

