Usage
Firstly, let’s talk about how we use this class in our code. This example will be written in Kotlin and Java.
Let’s implement a case where we need to do a countdown of 60 seconds with a 1-second interval.
What does this exactly mean? It means I want to do a countdown of 60 seconds and you let me know how much time is left every second.
val countDownTimer = object : CountDownTimer(60000, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.d("tekloon", "millisUntilFinished $millisUntilFinished")
} override fun onFinish() {
Log.d("tekloon", "onFinish") }}
countDownTimer.start()
Explanation
Let’s deep dive into CountDownTimer code to figure out how this implemented.
Constructor
Let’s look at CountDownTimer constructor first.
From the image above, we knew that this constructor performs value assignment only. Let’s move on. In order to start the countdown, we call the start function.
countDownTimer.start()
Let’s look at what the start function does.
public synchronized final StudyCountDownTimer start() {
//Reset the cancel flag before perform any logic
mCancelled = false;
/**
* If you wish to perform countdown that is lesser or equal to
* 0 seconds,
* It will give u a finish callback to inform your countdown is
* finished
*/
if (mMillisInFuture <= 0) {
onFinish();
return this;
}
/**
* However, if your countdown is more than 0 second.
* It will calculate your future stop time and assign it to a
* variable
*/
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
/**
* Here, we send message to the handler
*/
mHandler.sendMessage(mHandler.obtainMessage(MSG));
return this;
}
The logic of this function is quite straight forward and I added some of the comment into the source code for clarification as well. The main logic for the countdown is actually occurring in the mHandler itself. Let’s dive into mHandler code.
CountDownTimer’s mHandler
From the code snippet, we actually saw that the handleMessage() is override and let’see what logic they did inside that function.
Firstly, we saw a synchronized block. What exactly did a synchronized block do? I found a good explanation online.
Putting in simple word, it just similar as when you go into a toilet and locked the door, there is no one else can enter the toilet anymore until you finished your business, unlock the door and go out. It means when you executed that function, there is no way others party(thread) able to use that function till you finished and quit that block.
Let’s move on to what happened within the synchronized block.
synchronized (CountDownTimer.this) {
if (mCancelled) {
return;
}
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
} else {
long lastTickStart = SystemClock.elapsedRealtime();
onTick(millisLeft);
// take into account user's onTick taking time to execute
long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
long delay;
if (millisLeft < mCountdownInterval) {
// just delay until done
delay = millisLeft - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, trigger onFinish without delay
if (delay < 0) delay = 0;
} else {
delay = mCountdownInterval - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
}
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
Let’s break down this different block and understand them.
P/S: All those bolded comment is the explanation
// Do nothing when user trying to cancel the countdown
if (mCancelled) {
return;
}/**
* Since we calculated our count down stop time in start(),
* What we do here is we calculating the milliseconds left
*/
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
Now, let’s see the if else block.
/**
* If leftover time is less than or equal to zero,
* perform the finish callback
*/
if (millisLeft <= 0) {
onFinish();
}else {
long lastTickStart = SystemClock.elapsedRealtime();
/**
* Callback fired to inform user how many time is left
*/
onTick(millisLeft);
/**
* Take into account user's onTick taking time to execute
* The reason to calculate this time is it might be perform
* heavy-processing task within the onTick().
*/
long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
long delay;
/**
* The main purpose for the upcoming if else statement
* is to calculate the delay time.
* This delay time is basically your countdown interval
* However, there is logic to handle your delay on your
* onTick function as well
* Let's look at the if statement first.
*/
/**
* When remaining time lesser than countdown interval
*/
if (millisLeft < mCountdownInterval) {
// just delay until done
/**
* Calculate the delay with leftoverTime
* minus time-taking to perform onTick
*/
delay = millisLeft - lastTickDuration;
// special case: user's onTick took more than interval
// to complete, trigger onFinish without delay
if (delay < 0) delay = 0;
}
/**
* For the leftoverTime is higher than countDownInterval
*/
else {
/**
* For e.g, if your on Tick() took 2.5 second
* means lastTickDuration = 2500
* which is your higher than our countdown interval,
* means mCountdownInterval = 1000
* delay = mCountdownInterval - lastTickDuration;
* delay = -1500
*/
delay = mCountdownInterval - lastTickDuration;
/**
* special case: user's onTick took more than interval
* to complete, thus, skip to next interval
* With our case above, due to the delay is -1500.
* The while loop will loop 2 times
*
* it's equivalent to skip to next interval
*/
while (delay < 0) delay += mCountdownInterval;
}
/**
* Send message delay is equivalent to
* handler.postDelayed(runnable, delayInMilles)
* It will only send the message after delay.
* For.eg, if your countdownInterval is 1 second and
* there is no delay in onTick(),
* delay = countdownInterval == 1000
* Then it will back to perform the handleMessage() again
* from the beginning after the delay
*/
sendMessageDelayed(obtainMessage(MSG), delay);
}
}
Conclusion
By deep-diving into each line of code, I realized how actually CountDownTimer works. After all, they’re still using Handler to do the delay like how we use handler.postDelayed().
Don’t satisfy for being an API user, be a developer
Lastly, if anyone knows how to simulate delay in onTick(), please do share with me. Thanks for reading.