You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Java实现基于服务器时间、Activity刷新不重置的固定倒计时方案

改造实现方案

原有代码的核心问题是依赖本地系统时间、固定结束时间、和Activity生命周期绑定,针对你的需求改造如下:

1. 建立服务器时间基准,不受本地时间修改影响

首先要解决时间基准问题,不能用System.currentTimeMillis(),这个值用户可以手动修改,改用系统开机运行时长SystemClock.elapsedRealtime()(底层维护,用户无法修改)配合接口获取的服务器时间计算偏移量,后续所有时间计算都基于这个偏移量,不需要反复请求服务器:

// 全局存储该偏移量,建议放在Application全局变量或SharedPreferences中,页面销毁不会丢失
private long serverTimeOffset;
// 首次启动/APP回到前台时调用,请求后端接口获取当前服务器时间戳传入
public void initServerTimeOffset(long serverCurrentTimestamp) {
    serverTimeOffset = serverCurrentTimestamp - SystemClock.elapsedRealtime();
}
// 本地直接计算当前服务器时间,无需请求接口
private long getCurrentServerTime() {
    return SystemClock.elapsedRealtime() + serverTimeOffset;
}

该偏移量仅需冷启动/切前台时更新一次即可,误差控制在1秒内,完全不影响倒计时准确性。

2. 动态计算周期目标时间,支持时区配置

你的需求是每日11:15开放、次日11:00关闭循环,基于当前服务器时间自动计算最近的目标倒计时节点,可直接配置目标时区:

// 自定义配置参数
private static final String TARGET_TIME_ZONE = "Asia/Shanghai"; // 时区,东八区填该值即可
private static final int OPEN_HOUR = 11; // 开放时间小时
private static final int OPEN_MINUTE = 15; // 开放时间分钟
private static final int CLOSE_HOUR = 11; // 关闭时间小时
private static final int CLOSE_MINUTE = 0; // 关闭时间分钟
// 计算当前倒计时的目标时间戳
private long getTargetTime() {
    long currentServerTime = getCurrentServerTime();
    Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(TARGET_TIME_ZONE));
    cal.setTimeInMillis(currentServerTime);
    // 构造今日开放、关闭时间节点
    Calendar todayOpen = (Calendar) cal.clone();
    todayOpen.set(Calendar.HOUR_OF_DAY, OPEN_HOUR);
    todayOpen.set(Calendar.MINUTE, OPEN_MINUTE);
    todayOpen.set(Calendar.SECOND, 0);
    todayOpen.set(Calendar.MILLISECOND, 0);
    Calendar todayClose = (Calendar) cal.clone();
    todayClose.set(Calendar.HOUR_OF_DAY, CLOSE_HOUR);
    todayClose.set(Calendar.MINUTE, CLOSE_MINUTE);
    todayClose.set(Calendar.SECOND, 0);
    todayClose.set(Calendar.MILLISECOND, 0);
    long currentTime = cal.getTimeInMillis();
    // 场景1:当前在今日11:00~11:15,倒计时到今日11:15开放
    if (currentTime >= todayClose.getTimeInMillis() && currentTime < todayOpen.getTimeInMillis()) {
        return todayOpen.getTimeInMillis();
    }
    // 场景2:当前在今日11:15之后,倒计时到次日11:00关闭
    else if (currentTime >= todayOpen.getTimeInMillis()) {
        Calendar nextClose = (Calendar) todayClose.clone();
        nextClose.add(Calendar.DAY_OF_MONTH, 1);
        return nextClose.getTimeInMillis();
    }
    // 场景3:当前在今日11:00之前,倒计时到今日11:00关闭
    else {
        return todayClose.getTimeInMillis();
    }
}

3. 改造CountDownTimer逻辑,避免页面刷新重置

将倒计时逻辑从Activity中剥离,放到ViewModel或全局单例中管理,页面重建时仅绑定UI回调即可,不会重启倒计时:

private CountDownTimer mCountDownTimer;
// 倒计时回调,用于UI更新
public interface CountdownCallback {
    void onTick(long day, long hour, long minute, long second, boolean isCountdownToOpen);
    void onCycleFinish();
}
public void startCountdown(CountdownCallback callback) {
    // 先取消已有倒计时避免重复
    if (mCountDownTimer != null) {
        mCountDownTimer.cancel();
    }
    long targetTime = getTargetTime();
    long currentServerTime = getCurrentServerTime();
    long countDownDuration = targetTime - currentServerTime;
    mCountDownTimer = new CountDownTimer(countDownDuration, 1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            long totalSecond = millisUntilFinished / 1000;
            // 判断当前是倒数开放还是倒数关闭,getTodayOpenTime逻辑和第二步构造todayOpen的逻辑一致,提取为公共方法即可
            boolean isCountdownToOpen = getCurrentServerTime() < getTodayOpenTime();
            long day = totalSecond / 86400;
            long hour = (totalSecond % 86400) / 3600;
            long minute = (totalSecond % 3600) / 60;
            long second = totalSecond % 60;
            if (callback != null) {
                callback.onTick(day, hour, minute, second, isCountdownToOpen);
            }
        }
        @Override
        public void onFinish() {
            if (callback != null) {
                callback.onCycleFinish();
            }
            // 自动开启下一轮周期倒计时
            startCountdown(callback);
        }
    }.start();
}
// 页面销毁时仅清空回调,不要销毁倒计时实例
public void detachCallback() {
    this.callback = null;
}

补充说明

  • 如果使用Jetpack ViewModel存储倒计时实例,页面配置变更(旋转、重建)时ViewModel不会销毁,天然避免倒计时重置
  • 若需要更高精度,可在APP每次回到前台时重新请求服务器时间更新偏移量即可

内容的提问来源于stack exchange,提问作者Clinton Canarias

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 18:54:04