문제

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

도움이 되었습니까?

해결책

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.

다른 팁

This is an extension on what Noam Gal posted. I added extra functionality where you can pause and resume the timer. This was very helpful in my case.

public abstract class PreciseCountdownTimer extends Timer {

    private long totalTime, interval, delay;
    private TimerTask task;
    private long startTime = -1;
    private long timeLeft;
    private boolean restart = false;
    private boolean wasCancelled = false;
    private boolean wasStarted = false;

    public PreciseCountdownTimer(long totalTime, long interval) {
        this(totalTime, interval, 0);
    }


    public PreciseCountdownTimer(long totalTime, long interval, long delay ) {
        super("PreciseCountdownTimer", true);
        this.delay = delay;
        this.interval = interval;
        this.totalTime = totalTime;
        this.task = buildTask(totalTime);
    }

    private TimerTask buildTask(final long totalTime) {
        return new TimerTask() {

            @Override
            public void run() {
                if (startTime < 0 || restart) {
                    startTime = scheduledExecutionTime();
                    timeLeft = totalTime;
                    restart = false;
                } else {
                    timeLeft = totalTime - (scheduledExecutionTime() - startTime);

                    if (timeLeft <= 0) {
                        this.cancel();
                        wasCancelled = true;
                        startTime = -1;
                        onFinished();
                        return;
                    }
                }

                onTick(timeLeft);
            }
        };
    }

    public void start() {
        wasStarted = true;
        this.scheduleAtFixedRate(task, delay, interval);
    }

    public void stop() {
        this.wasCancelled = true;
        this.task.cancel();
    }

    public void restart() {
        if (!wasStarted) {
            start();
        } else if (wasCancelled) {
            wasCancelled = false;
            this.task = buildTask(totalTime);
            start();
        } else {
            this.restart = true;
        }
    }

    public void pause(){
        wasCancelled = true;
        this.task.cancel();
        onPaused();
    }

    public void resume(){
        wasCancelled = false;
        this.task = buildTask(timeLeft);
        this.startTime = - 1;
        start();
        onResumed();
    }

    // Call this when there's no further use for this timer
    public void dispose() {
        this.cancel();
        this.purge();
    }

    public abstract void onTick(long timeLeft);

    public abstract void onFinished();

    public abstract void onPaused();

    public abstract void onResumed();
}

Usage example would be almost exactly the same:

this.timer = new PreciseCountdownTimer(totalTime, interval, delay) {
            @Override
            public void onTick(long timeLeft) {
                // 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
            }

            @Override
            public void onPaused() {
                // runs after the timer has been paused
            }

            @Override
            public void onResumed() {
                // runs after the timer has been resumed
            }
        };

Enjoy and have fun :D

That's true and I observed the same behaviour (logging millisUntilFinished):

9999        // 1 ms lag
8997        // 3 ms lag
7995        // 5 ms lag
6993        // 7 ms lag
5991        // 9 ms lag
4987        // 13 ms lag
3985        // 15 ms lag
2979        // 21 ms lag
1975        // 25 ms lag
971         // 29 ms lag

The reason is that it's implementation doesn't take into account the time a message stays in thread's message queue and the time needed for synchronization.

I prepared the fixed version (repo, class source).

It prints the following sequence:

9999        // 1 ms lag
8999        // 1 ms lag
7999        // 1 ms lag
6997        // 3 ms lag
5997        // 3 ms lag
4998        // 2 ms lag
3997        // 3 ms lag
2998        // 2 ms lag
1997        // 3 ms lag
997         // 3 ms lag

Small lag is still here, but the most important thing is that it doesn't accumulate.

To install it add in your root build.gradle at the end of repositories:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Add the dependency:

dependencies {
    implementation 'com.github.cardinalby:accurate-count-down-timer:1.0'
}

Instead of using millisUntilFinished, you can use a variable to hold the remaining time and in every onTick, minus the variable with the interval. In this way, remainingTime is always accurate.

private class MyTimer(
    countDownTime: Long, 
    interval: Long
) : CountDownTimer(countDownTime, interval) {

    private var remainingTime = countDownTime

    override fun onFinish() {
    }

    override fun onTick(millisUntilFinished: Long) {
        // consume remainingTime here and then minus interval
        remainingTime -= interval
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top