Search code examples
javaandroidcountdowntimer

android countdowntimer tick is not accurate


I am using a countdown timer for audio notification... and it's not accurate from the start...

using initial parameters

private final long startCountDown; 
private final long intervalCountDown;
    ...
    startCountDown = 180 * 1000;   // 3 mns  - to be set from Preferences later
intervalCountDown = 60 * 1000;   // 1 mns - to be set from Preferences later
    ...
    public void onTick(long millisUntilFinished) {
       Log.d(TAG, "notify countDown: " + millisUntilFinished + " msecs");
    }


    countDownTimer = new SwimCountDownTimer(startCountDown,intervalCountDown);
    ....

public void startCountDown() {
    Log.d(TAG, "start countDown for " + startCountDown + " msecs" );
    countDownTimer.start();
}

I can see in the log that the initial countdown is correctly set to 180000 but the next one should be 120000 and it's set to 119945 !!!

04-27 14:50:42.146: I/SWIMMER(8670): notify countDown: 180000 msecs
04-27 14:51:42.206: I/SWIMMER(8670): notify countDown: 119945 msecs

This is quite annoying as the audio notifier is expecting to say only '2 minutes" and not "1 minute and fifty nine seconds" ...; why the interval is not right ... ? I can tricj it in setting myself the text to speech string ... but is there any way to get correct data ?

thanks for suggestions


Solution

  • I know it's an old question- but I've also encountered the problem, and thought I would share my solution.

    Apperantly CountDownTimer isn't very accurate, so I've decided to implement a more percise countdown timer, using java.util.Timer:

    public abstract class PreciseCountdown extends Timer {
        private long totalTime, interval, delay;
        private TimerTask task;
        private long startTime = -1;
        private boolean restart = false, wasCancelled = false, wasStarted = false;
    
        public PreciseCountdown(long totalTime, long interval) {
            this(totalTime, interval, 0);
        }
    
        public PreciseCountdown(long totalTime, long interval, long delay) {
            super("PreciseCountdown", true);
            this.delay = delay;
            this.interval = interval;
            this.totalTime = totalTime;
            this.task = getTask(totalTime);
        }
    
        public void start() {
            wasStarted = true;
            this.scheduleAtFixedRate(task, delay, interval);
        }
    
        public void restart() {
            if(!wasStarted) {
                start();
            }
            else if(wasCancelled) {
                wasCancelled = false;
                this.task = getTask(totalTime);
                start();
            }
            else{
                this.restart = true;
            }
        }
    
        public void stop() {
            this.wasCancelled = true;
            this.task.cancel();
        }
    
        // Call this when there's no further use for this timer
        public void dispose(){
            cancel();
            purge();
        }
    
        private TimerTask getTask(final long totalTime) {
            return new TimerTask() {
    
                @Override
                public void run() {
                    long timeLeft;
                    if (startTime < 0 || restart) {
                        startTime = scheduledExecutionTime();
                        timeLeft = totalTime;
                        restart = false;
                    } else {
                        timeLeft = totalTime - (scheduledExecutionTime() - startTime);
    
                        if (timeLeft <= 0) {
                            this.cancel();
                            startTime = -1;
                            onFinished();
                            return;
                        }
                    }
    
                    onTick(timeLeft);
                }
            };
        }
    
        public abstract void onTick(long timeLeft);
        public abstract void onFinished();
    }
    

    Usage example would be:

    this.countDown = new PreciseCountdown(totalTime, interval, delay) {
        @Override
        public void onTick(long timeLeft) {
            // update..
            // note that this runs on a different thread, so to update any GUI components you need to use Activity.runOnUiThread()
        }
    
        @Override
        public void onFinished() {
            onTick(0); // when the timer finishes onTick isn't called
            // count down is finished
        }
    };
    

    to start the countdown, simply call countDown.start(). countDown.stop() stops the countDown, which could be restarted using countDown.restart().

    Hope this is any help for anyone in the future.