Android Countdowntimer - Additional Milliseconds Delay Between Ticks

android CountDownTimer - additional milliseconds delay between ticks

Rewrite

As you said, you also noticed that the next time in onTick() is calculated from the time the previous onTick() ran, which introduces a tiny error on every tick. I changed the CountDownTimer source code to call each onTick() at the specified intervals from the start time.

I build this upon the CountDownTimer framework, so cut & paste the source code into your project and give the class a unique name. (I called mine MoreAccurateTimer.) Now make a few changes:

  1. Add a new class variable:

    private long mNextTime;
  2. Change start():

    public synchronized final MoreAccurateTimer start() {
    if (mMillisInFuture <= 0) {
    onFinish();
    return this;
    }

    mNextTime = SystemClock.uptimeMillis();
    mStopTimeInFuture = mNextTime + mMillisInFuture;

    mNextTime += mCountdownInterval;
    mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG), mNextTime);
    return this;
    }
  3. Change the Handler's handlerMessage():

    @Override
    public void handleMessage(Message msg) {
    synchronized (MoreAccurateTimer.this) {
    final long millisLeft = mStopTimeInFuture - SystemClock.uptimeMillis();

    if (millisLeft <= 0) {
    onFinish();
    } else {
    onTick(millisLeft);

    // Calculate next tick by adding the countdown interval from the original start time
    // If user's onTick() took too long, skip the intervals that were already missed
    long currentTime = SystemClock.uptimeMillis();
    do {
    mNextTime += mCountdownInterval;
    } while (currentTime > mNextTime);

    // Make sure this interval doesn't exceed the stop time
    if(mNextTime < mStopTimeInFuture)
    sendMessageAtTime(obtainMessage(MSG), mNextTime);
    else
    sendMessageAtTime(obtainMessage(MSG), mStopTimeInFuture);
    }
    }
    }

android countdowntimer tick is not accurate

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.

Why is my milliseconds count down not consistent?

I found that onTick() is calculated from the time the previous onTick() ran, which creates a tiny error on every tick.

See this post: android CountDownTimer - additional milliseconds delay between ticks

android CountDownTimer - last onTick not called - what clean solution to use?

The behavior you are experiencing is actually explicitly defined in the CountdownTimer code; have a look at the source.

Notice inside of handleMessage(), if the time remaining is less than the interval, it explicitly does not call onTick() and just delays until complete.

Notice, though, from the source that CountdownTimer is just a very thin wrapper on Handler, which is the real timing component of the Android framework. As a workaround, you could very easily create your own timer from this source (less than 150 lines) and remove this restriction to get your final tick callback.

is using CountDownTimer to add styles every few milliseconds a correct approach?

You do not have to define your own thread for the Timer, so you
sidestep all the issues of thread management, letting the system handle it.

Here is the handler implementation:

    //updated the screen when we are testing for connectivity and errors.
private Handler handlerHighlightWords = new Handler() {
@Override
public void handleMessage(Message msg) {

...
// highlight current word
Spannable spannableText = new SpannableString(tv.getText());
spannableText.setSpan(new BackgroundColorSpan(Color.LTGRAY), startOffset, endOffset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(spannableText);
handlerMessageText.sendEmptyMessageDelayed(0, 1000);
}
};

//runnable to to feed to handler.
final Runnable handlerRunnableMessage = new Runnable() {
public void run() {
Spannable spannableText = new SpannableString(tv.getText());
spannableText.setSpan(new BackgroundColorSpan(Color.LTGRAY), startOffset, endOffset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(spannableText);

}
};

Then once you click a button:

handlerMessageText.post(handlerRunnableMessage);


Related Topics



Leave a reply



Submit