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
相关产品推荐
相关产品推荐

