From my observation the android CountDownTimer countDownInterval between ticks happens to be not accurate, the countDownInterval is regularly a few milliseconds longer than specified. The countDownInterval in my specific app is 1000ms, just counting down a certain amount of time with one second steps.
Due to this prolonged ticks I end up having less ticks then wanted when the the countdowntimer runs long enough which screws up the displayed countdown of the time (a 2 second step happens on the UI level when enough additional ms have summed up)
Looking into the source of CountDownTimer it seems possible to twist it so it corrects this unwanted inaccuracy yet I was wondering if there is already a better CountDownTimer available in the java/android world.
Thanks buddies for any pointer ...
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:
Add a new class variable:
private long mNextTime;
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;
}
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);
}
}
}
So this is what I came up with. It is a small modification of the original CountDownTimer. What it adds is a variable mTickCounter which counts the amount of ticks called. This variable is used together with the new variable mStartTime to see how accurate we are with our ticks. Based on this input the delay to the next tick is adjusted ... It seems to do what I was looking for yet I am sure this can be improved upon.
Look for
// ************AccurateCountdownTimer***************
in the source code to find the modifications I have added to the original class.
package com.dorjeduck.xyz;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
/**
* Schedule a countdown until a time in the future, with regular notifications
* on intervals along the way.
*
* Example of showing a 30 second countdown in a text field:
*
* <pre class="prettyprint">
* new CountDownTimer(30000, 1000) {
*
* public void onTick(long millisUntilFinished) {
* mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
* }
*
* public void onFinish() {
* mTextField.setText("done!");
* }
* }.start();
* </pre>
*
* The calls to {@link #onTick(long)} are synchronized to this object so that
* one call to {@link #onTick(long)} won't ever occur before the previous
* callback is complete. This is only relevant when the implementation of
* {@link #onTick(long)} takes an amount of time to execute that is significant
* compared to the countdown interval.
*/
public abstract class AccurateCountDownTimer {
/**
* Millis since epoch when alarm should stop.
*/
private final long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
private final long mCountdownInterval;
private long mStopTimeInFuture;
// ************AccurateCountdownTimer***************
private int mTickCounter;
private long mStartTime;
// ************AccurateCountdownTimer***************
/**
* @param millisInFuture
* The number of millis in the future from the call to
* {@link #start()} until the countdown is done and
* {@link #onFinish()} is called.
* @param countDownInterval
* The interval along the way to receive {@link #onTick(long)}
* callbacks.
*/
public AccurateCountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
// ************AccurateCountdownTimer***************
mTickCounter = 0;
// ************AccurateCountdownTimer***************
}
/**
* Cancel the countdown.
*/
public final void cancel() {
mHandler.removeMessages(MSG);
}
/**
* Start the countdown.
*/
public synchronized final AccurateCountDownTimer start() {
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
// ************AccurateCountdownTimer***************
mStartTime = SystemClock.elapsedRealtime();
mStopTimeInFuture = mStartTime + mMillisInFuture;
// ************AccurateCountdownTimer***************
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
/**
* Callback fired on regular interval.
*
* @param millisUntilFinished
* The amount of time until finished.
*/
public abstract void onTick(long millisUntilFinished);
/**
* Callback fired when the time is up.
*/
public abstract void onFinish();
private static final int MSG = 1;
// handles counting down
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
synchronized (AccurateCountDownTimer.this) {
final long millisLeft = mStopTimeInFuture
- SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else if (millisLeft < mCountdownInterval) {
// no tick, just delay until done
sendMessageDelayed(obtainMessage(MSG), millisLeft);
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// ************AccurateCountdownTimer***************
long now = SystemClock.elapsedRealtime();
long extraDelay = now - mStartTime - mTickCounter
* mCountdownInterval;
mTickCounter++;
long delay = lastTickStart + mCountdownInterval - now
- extraDelay;
// ************AccurateCountdownTimer***************
// take into account user's onTick taking time to execute
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0)
delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
}
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With