首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Android 鬧鐘自定義重復周期:計算下次觸發時間間隔的高效算法

     編輯:程式設計綜合問答

問題描述

需要支持用戶自定義重復周期(如:每周三、周五 7:30),計算從當前時間到下一次鬧鐘觸發的時間間隔(毫秒)。單次/每天邏輯簡單,但多天周期(尤其是 4-5 天)邏輯較複雜。

核心算法思路:時間軸映射 + 取模運算

將「一周」展開為連續時間軸,以周一 00:00:00 為基準點 (Epoch),所有時間點轉換為「距離基準點的毫秒數」,問題轉化為在有序集合中查找第一個大於當前時間的值。

步驟詳解

1. 定義常量

private static final long DAY_MS = 24 * 60 * 60 * 1000;
private static final long WEEK_MS = 7 * DAY_MS;
// 周一=0 ... 周日=6 (Calendar 規則: SUN=1, MON=2... 需轉換)

2. 將用戶設置的「周幾 + 時間」轉為絕對時間偏移 (相對周一 00:00)


// 例如:周三 07:30 → (2 * DAY_MS) + (7*3600+30*60)*1000
// 周五 07:30 → (4 * DAY_MS) + (7*3600+30*60)*1000
List alarmOffsets = new ArrayList<>();
for (DayTimeSetting setting : userSettings) { // setting: {dayOfWeek, hour, minute}
    int dayIndex = convertToMonBasedIndex(setting.dayOfWeek); // 周一=0
    long offset = dayIndex * DAY_MS + setting.hour * 3600 * 1000 + setting.minute * 60 * 1000;
    alarmOffsets.add(offset);
}
Collections.sort(alarmOffsets); // 確保有序

3. 計算當前時間相對同一基準點的偏移

Calendar now = Calendar.getInstance();
int todayIndex = convertToMonBasedIndex(now.get(Calendar.DAY_OF_WEEK));
long currentOffset = todayIndex * DAY_MS + now.get(Calendar.HOUR_OF_DAY) * 3600 * 1000 
                    + now.get(Calendar.MINUTE) * 60 * 1000 + now.get(Calendar.SECOND) * 1000 
                    + now.get(Calendar.MILLISECOND);

4. 核心查找邏輯:二分查找或線性掃描


long nextTriggerOffset = -1;

// 情況 A: 本周內有未來的鬧鐘
for (long offset : alarmOffsets) {
    if (offset > currentOffset) {
        nextTriggerOffset = offset;
        break;
    }
}

// 情況 B: 本周無未來鬧鐘,取下周第一個 (加一周毫秒數)
if (nextTriggerOffset == -1) {
    nextTriggerOffset = alarmOffsets.get(0) + WEEK_MS;
}

// 5. 計算最終間隔
long intervalMs = nextTriggerOffset - currentOffset;
// 即可用於 AlarmManager.setExact / setWindow

完整工具類實現


public class AlarmScheduleUtil {
    private static final long DAY_MS = 86400000L;
    private static final long WEEK_MS = 604800000L;

    /** 將 Calendar.DAY_OF_WEEK (SUN=1..SAT=7) 轉為 周一=0..周日=6 */
    private static int toMonBasedIndex(int calDayOfWeek) {
        return (calDayOfWeek + 5) % 7; // SUN=1→6, MON=2→0, TUE=3→1 ...
    }

    /** 計算下次觸發間隔毫秒數
     * @param alarmDays 重復的星期集合,如 {Calendar.WEDNESDAY, Calendar.FRIDAY}
     * @param hourOfDay 時 (0-23)
     * @param minute 分 (0-59)
     * @return 距離下次觸發的毫秒數,>0
     */
    public static long getNextIntervalMs(Set alarmDays, int hourOfDay, int minute) {
        if (alarmDays == null || alarmDays.isEmpty()) return -1;

        // 1. 構建本周所有鬧鐘偏移量
        List offsets = new ArrayList<>();
        for (int day : alarmDays) {
            long offset = toMonBasedIndex(day) * DAY_MS + hourOfDay * 3600000L + minute * 60000L;
            offsets.add(offset);
        }
        Collections.sort(offsets);

        // 2. 當前時間偏移
        Calendar now = Calendar.getInstance();
        long currentOffset = toMonBasedIndex(now.get(Calendar.DAY_OF_WEEK)) * DAY_MS
                + now.get(Calendar.HOUR_OF_DAY) * 3600000L
                + now.get(Calendar.MINUTE) * 60000L
                + now.get(Calendar.SECOND) * 1000L
                + now.get(Calendar.MILLISECOND);

        // 3. 查找下一個
        for (long off : offsets) {
            if (off > currentOffset) return off - currentOffset;
        }
        // 本周無,取下周第一個
        return offsets.get(0) + WEEK_MS - currentOffset;
    }
}

特殊情況處理

場景處理方式
用戶修改系統時間註冊 `ACTION_TIME_CHANGED` / `ACTION_TIMEZONE_CHANGED` 廣播,重新計算並重置鬧鐘
跨夏令時使用 `Calendar` 自動處理,或改用 `ZonedDateTime` (API 26+)
精確鬧鐘權限 (API 19+)需 `SET_ALARM` 權限或用戶授權「鬧鐘與提醒」特殊權限
Doze 模式/待機優化使用 `setExactAndAllowWhileIdle` 或 `setAlarmClock` (顯示鬧鐘圖標)

優勢總結

  • O(N) → O(log N):排序後可用二分查找 (`Collections.binarySearch`),N=7 時線性掃描也極快。
  • 統一時間基準:避免「周日→周一」跨週邊界的複雜 if-else 判斷。
  • 易擴展:支持「每月幾號」、「每年幾月幾日」只需調整基準點與週期常量。
  • 可測試:純邏輯函數,易於編寫單元測試覆蓋各種邊界情況。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved