CountDownTimer计时不恒定,如何实现亚毫秒计数器及固定500ms定时任务?
解决CountDownTimer计时波动与高分辨率定时需求的问题
兄弟,我太懂你这种定时不准的头疼了!CountDownTimer本质是靠Handler发延迟消息实现的,主线程只要有UI绘制、触摸事件这些活儿抢占资源,消息队列一堵,你的定时就开始飘了。想要实现恒定计时,得换个更可靠的底层计时机制,同时把计时逻辑和UI操作彻底分开,下面给你两个实用方案:
一、用ScheduledExecutorService实现后台稳定定时
这个是Java并发包提供的定时工具,基于线程池实现,不受主线程消息队列的干扰,分辨率能到毫秒级甚至更高,比CountDownTimer稳太多。
代码示例:
// 初始化单线程的定时线程池(避免多线程竞争) private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture<?> scheduledFuture; private long lastTimestamp; @Override protected void onResume() { super.onResume(); // 启动定时任务:首次延迟0ms,之后每隔500ms执行一次 scheduledFuture = scheduler.scheduleAtFixedRate(() -> { // 记录当前时间戳(用System.nanoTime()比System.currentTimeMillis()更适合计算间隔) long currentNano = System.nanoTime(); if (lastTimestamp != 0) { long intervalMs = (currentNano - lastTimestamp) / 1_000_000; Log.d("Timer", "实际间隔:" + intervalMs + "ms"); } lastTimestamp = currentNano; // 切换到主线程更新ImageView背景 runOnUiThread(() -> { // 这里设置你的棋盘格背景逻辑 imageView.setBackgroundResource(R.drawable.chessboard); }); }, 0, 500, TimeUnit.MILLISECONDS); } @Override protected void onPause() { super.onPause(); // 页面暂停时关闭定时任务,避免内存泄漏 if (scheduledFuture != null && !scheduledFuture.isCancelled()) { scheduledFuture.cancel(true); } } @Override protected void onDestroy() { super.onDestroy(); // 销毁时关闭线程池 scheduler.shutdown(); }
二、手动用System.nanoTime()校准计时(极致精度)
如果对定时精度要求极高,比如要把误差控制在几毫秒内,可以自己在后台线程里循环,用System.nanoTime()计算时间差,手动校准下一次的等待时间,抵消系统调度带来的误差。
代码示例:
private Thread timerThread; private volatile boolean isRunning = false; @Override protected void onResume() { super.onResume(); isRunning = true; timerThread = new Thread(() -> { long lastNano = System.nanoTime(); while (isRunning) { long currentNano = System.nanoTime(); long elapsedMs = (currentNano - lastNano) / 1_000_000; // 当间隔达到或接近500ms时触发UI更新 if (elapsedMs >= 500) { // 计算误差,下次等待时减去这个误差,校准时间 long errorMs = elapsedMs - 500; lastNano = currentNano - errorMs * 1_000_000; // 主线程更新UI runOnUiThread(() -> { imageView.setBackgroundResource(R.drawable.chessboard); }); } // 用LockSupport.parkNanos实现高精度等待,比Thread.sleep更准 LockSupport.parkNanos(100_000); // 每次等待100微秒,减少CPU占用 } }); timerThread.start(); } @Override protected void onPause() { super.onPause(); isRunning = false; // 唤醒线程,避免阻塞 LockSupport.unpark(timerThread); }
关键注意点:
- 必须避免在后台线程操作UI:所有更新ImageView的逻辑一定要通过
runOnUiThread、Handler.post或者View.post切回主线程。 - 及时释放资源:页面销毁或暂停时,一定要停止定时任务/线程,否则会导致内存泄漏。
- System.nanoTime() vs System.currentTimeMillis():前者是基于系统启动时间的单调递增时间,适合计算时间间隔;后者是系统时间,可能会因为时间同步被修改,不适合做间隔计算。
内容的提问来源于stack exchange,提问作者roxy
相关产品推荐
相关产品推荐

