Android AlarmManager 通过时区或夏时制调度

Posted

技术标签:

【中文标题】Android AlarmManager 通过时区或夏时制调度【英文标题】:Android AlarmManager scheduling through time zone or daylight shifts 【发布时间】:2021-06-25 18:28:31 【问题描述】:

我有一个使用 AlarmManager 安排提醒的逻辑。我需要实现以下内容:

逻辑1:当用户改变时区时,例如他从英国(UTC+0)到中欧(UTC​​+1),警报应该跟随时区。 例如,安排在 UTC+0 下午 3 点的提醒应该在 UTC+1 下午 4 点触发

逻辑2:当时间偏移发生时,例如春季时间偏移到夏令时(从UTC+1到UTC+2),警报应该保持原来的时间 例如,安排在 UTC+1 下午 3 点的提醒应该在 UTC+2 下午 3 点触发

我怎样才能做到这一点?到目前为止,我还没有特定的逻辑,所有的警报都遵循 Logic 1。我无法确定何时发生时移。

调度逻辑很简单:

LocalDateTime reminderTime = LocalDateTime.of(...)
ZoneOffset currentOffsetForMyZone = ZoneId.systemDefault().getRules().getOffset(Instant.now());
reminderTime.toInstant(currentOffsetForMyZone).toEpochMilli();
alarmManager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);

【问题讨论】:

为什么不只使用 UTC 而不必担心时区? 如何仅使用 UTC 实现上述两个逻辑? UTC 时间不会改变,它没有时区,也没有夏令时。您可以使用Calendar 获取特定日期的UTC 时间戳 它不起作用。在您的场景中,所有警报都使用我的问题的逻辑 1 重新安排。当时间切换到 DST 时,所有警报也会重新安排,而我需要保持实际时间(逻辑 2)。 【参考方案1】:

为每个闹钟存储一天中的时间和设置它的时区。无论用户当前是否在不同的时区,这足以在正确的时间触发警报。 Java 将考虑夏令时 (DST)。

您的示例时间和 UTC 偏移量对应于这些时区的标准时间,所以让我们从标准时间的示例日期开始,即使它是几天前的日期:

    LocalTime alarmTime = LocalTime.of(15, 0);
    ZoneId alarmTimeZone = ZoneId.of("Europe/London");
    
    // Travel to Paris and see the alarm go off at 4, assuming standard time
    ZoneId currentTimeZone = ZoneId.of("Europe/Paris");
    Instant actualAlarmTime = LocalDate.of(2021, Month.MARCH, 18)
            .atTime(alarmTime)
            .atZone(alarmTimeZone)
            .toInstant();
    ZonedDateTime timeOnLocation = actualAlarmTime.atZone(currentTimeZone);
    System.out.format("Scheduled at %s or %d millis, goes off at %s local time%n",
            actualAlarmTime, actualAlarmTime.toEpochMilli(), timeOnLocation);

代码打印:

计划于 2021-03-18T15:00:00Z 或 1616079600000 毫秒,起飞时间为 2021-03-18T16:00+01:00[欧洲/巴黎]当地时间

让我们也尝试在一年中的夏季时间约会。我已将Paris 更改为LondonMARCH 更改为APRIL

    // Stay back home in the UK
    ZoneId currentTimeZone = ZoneId.of("Europe/London");
    Instant actualAlarmTime = LocalDate.of(2021, Month.APRIL, 18)
            .atTime(alarmTime)
            .atZone(alarmTimeZone)
            .toInstant();

计划于 2021-04-18T14:00:00Z 或 1618754400000 毫秒,起飞时间 2021-04-18T15:00+01:00[欧洲/伦敦]当地时间

基本技巧是:不要将当前偏移量用于设置闹钟的时区。让 Java 自动应用警报响起的日期和时间的偏移量。

【讨论】:

时区更改工作,谢谢。我仍然无法弄清楚如何管理 DST 时移(逻辑 2)。 AlarmManager 仅在绝对时间工作,发生时移时我没有收到任何事件。我必须设置自己的闹钟吗? 好吧,我想我明白你说的了。我的调度逻辑错了,其实解决方法很简单。【参考方案2】:

如果有人感兴趣,解决方法是为警报发出的日期和时间应用正确的偏移量,正如 Ole 指出的那样。我的愚蠢错误是始终应用​​ current 时区。

LocalDateTime alarmTime = LocalDateTime.of(...)
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime zoneDateTime = ZonedDateTime.of(alarmTime , zone);
long startAtMillis = zoneDateTime.toInstant().toEpochMilli();
//Fire alarm
notificationAlarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, startAtMillis, pendingIntent);

【讨论】:

以上是关于Android AlarmManager 通过时区或夏时制调度的主要内容,如果未能解决你的问题,请参考以下文章

Android中AlarmManager使用示例(持续更新)

即使在应用程序关闭时也能监听时区变化

Android开发:利用AlarmManager不间断向服务器发送请求以及notification通知

Android Doze模式下的AlarmManager策略

Android Doze模式下的AlarmManager策略

AlarmManager 在错误的时间触发警报