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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:14:56