4

I am trying to use android.os.CountDownTimer to literally show a countdown timer via a textview for fitness purposes. The issue I am having is the timer seems to be having trouble running on the main thread i.e. the countdown will jump 2-4 secs and is clearly being "lagged" - the timer is intended to be in an endless loop until a stop button is pressed.

I am new to Java and Android and cannot figure out how to get the countdown timer running and updating the UI without any conflicts or lagging.

I have attempted to put the CountDown in a Handler/Runnable and an Asynctask with no luck.

MainActivity

CountDownTimer timer;
void countdownTimer() {
    long min = countdownMins * 60000;
    long sec = countdownSecs * 1000;
    long milliseconds = min+sec;
    timer = null;
    timer = new CountDownTimer(milliseconds, 1000) {

        public void onTick(long millisUntilFinished) {

            long mins = millisUntilFinished / 60000;
            long secs = millisUntilFinished % 60000 / 1000;
            String display = String.format("%02d:%02d", mins, secs);
            tvTextView.setText(display);
        }
        public void onFinish() {
            countdownTimer();
        }
    }.start();
}

Many thanks to anyone who can show me how to get this running off the main thread so my UI elements will be smooth.

Tyler Thomas
  • 173
  • 1
  • 13

3 Answers3

4

I decided to take a different approach which has served my purposes very well. No lag or thread issues and can easily be restarted over and over. Hope this helps someone out.

int startCountdown = 5;
int currentCountdown;
Handler countdownHandler = new Handler();
Timer countdownTimer = new Timer();
public void startCountdownTimer() {
    currentCountdown = startCountdown;
    for (int i = 0; i <= startCountdown; i++) {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                countdownHandler.post(doA);
            }
        };
        countdownTimer.schedule(task, i * 1000);
    }
}
final Runnable doA = new Runnable() {
    @Override
    public void run() {
        if (currentCountdown != 0) {
            tvTextView.setText("" + currentCountdown);
        currentCountdown--;
        } else {
            currentCountdown = startCountdown;
            startCountdownTimer();
        }
    }
};
Tyler Thomas
  • 173
  • 1
  • 13
  • 1
    This approach will consume threads and Android doesn't have a lot of threads to spare. Using "Timer" rarely is fine, but be careful because you can run into thread starvation if you use a lot. Another approach is to use a Handler with a delay, or potentially the AlarmManager. – Aaron T Harris Dec 08 '16 at 17:11
  • 1
    @AaronTHarris The `CountDownTimer` class actually already uses `Handler` internally to control its timing behaviour. See usage of `mHandler` here: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/CountDownTimer.java – Eric Schnipke Jun 09 '21 at 21:01
  • This solution based on `System.Timing.Timers` probably works because that class will execute Elapsed event handlers on the threadpool rather than the thread that instantiated the timer, thereby side-stepping any responsiveness issues existing on the instantiating thread. Based on the original problem description (from '14 lol) your main thread's `MessageQueue` was probably heavily burdened, therefore slowing processing of messages used to pace your `CountDownTimer`. – Eric Schnipke Jun 09 '21 at 21:04
3

Try to use Handler and runnable instead of CountDownTimer. Here is a good article about this approach

http://www.mopri.de/2010/timertask-bad-do-it-the-android-way-use-a-handler/

One more thing you could try is

void countdownTimer() {

    final Runnable runnable = new Runnable() {
        @Override
        public void run() {
            tvTextView.setText(display);
        }
    };
    long min = countdownMins * 60000;
    long sec = countdownSecs * 1000;
    long milliseconds = min + sec;
    timer = null;
    timer = new CountDownTimer(milliseconds, 1000) {

        public void onTick(long millisUntilFinished) {

            long mins = millisUntilFinished / 60000;
            long secs = millisUntilFinished % 60000 / 1000;
            final String display = String.format("%02d:%02d", mins, secs);
            textView.post(runnable);
        }

        public void onFinish() {
            countdownTimer();
        }
    }.start();
}
Stepango
  • 4,721
  • 3
  • 31
  • 44
  • I have tried this with no change in the result. Can you show me a working example? Thanks for taking the time to answer. – Tyler Thomas Dec 31 '14 at 03:12
  • thank you for your response. The code was able to run, but the TextView would still skip numbers. For instance on a countdown from 10 it would go 10,8,7,6,4,2,10. Thank you for your help, do you have any other ideas? – Tyler Thomas Dec 31 '14 at 07:48
  • @TylerThomas long secs = Math.round(millisUntilFinished % 60000 / 1000f); – Stepango Dec 31 '14 at 09:09
  • The above still produces a 2 second lag in each countdown. I don't think we are addressing the CountDownTimer accessing the main thread to complete its task. – Tyler Thomas Dec 31 '14 at 21:48
2

If you countdown with changing your TextView fast and frequently. You have to set your TextView width and height in a fix number, or it will lag while calculating your view scale.

user3168958
  • 111
  • 3
  • This won't be possible, my design needs the text to grow and shrink based on orientation and screen size. I know the textview is not the issue because I can successfully run an up counting timer (not CountDownTimer class) in a Runnable/Handler/TimerTask scenario with no lag. – Tyler Thomas Dec 31 '14 at 07:51