Android: CountDownTimer skips last onTick()!

前端 未结 12 630
眼角桃花
眼角桃花 2020-11-27 14:51

Code:

public class SMH extends Activity {  

    public void onCreate(Bundle b) {  
        super.onCreate(b);  
        setContentView(R.layou         


        
相关标签:
12条回答
  • 2020-11-27 14:56

    I've spent hours trying to figure out this problem, and I'm happy to show you a nice work around. Don't bother waiting for the onFinish() call, just add 1 (or whatever your interval is) to your units, then add an if statement in the onTick() calls. Just do your onFinish() task(s) on the last onTick(). Here's what I've got:

        new CountDownTimer( (countDownTimerValue + 1) * 1000, 1000) { //Added 1 to the countdownvalue before turning it into miliseconds by multiplying it by 1000.
            public void onTick(long millisUntilFinished) {
    
              //We know that the last onTick() happens at 2000ms remaining (skipping the last 1000ms tick for some reason, so just throw in this if statement.
                if (millisUntilFinished < 2005){ 
                    //Stuff to do when finished.
                }else{
                    mTextField.setText("Time remaining: " + (((millisUntilFinished) / 1000) - 1));  //My textfield is obviously showing the remaining time. Note how I've had to subtrack 1 in order to display the actual time remaining.
                }
            }
    
            public void onFinish() {
            //This is when the timer actually finishes (which would be about 1000ms later right? Either way, now you can just ignore this entirely.
    
    
            }
        }.start();
    
    0 讨论(0)
  • 2020-11-27 15:00

    I found easy solution. I need CountDown to update ProgressBar, so I did this:

    new CountDownTimer(1000, 100) {
    
        private int counter = 0;
    
        @Override
        public void onTick(long millisUntilFinished) {
            Log.d(LOG_TAG, "Tick: " + millisUntilFinished);
            if (++counter == 10) {
                timeBar.setProgress(--lenght); // timeBar and lenght defined in calling code
                counter = 0;
            }
        }
    
    
        @Override
        public void onFinish() {
            Log.d(LOG_TAG, "Finish.");
    
            timeBar.setProgress(0);
        }
    
    };
    

    Small tick do the trick :)

    0 讨论(0)
  • 2020-11-27 15:01

    I checked the source code of CountDownTimer. The "missing tick" comes from a special feature of CountDownTimer that I have not yet seen being documented elsewhere:

    At the start of every tick, before onTick() is called, the remaining time until the end of the countdown is calculated. If this time is smaller than the countdown time interval, onTick is not called anymore. Instead only the next tick (where the onFinish() method will be called) is scheduled.

    Given the fact that hardware clocks are not always super precise, that there may be other processes in the background that delay the thread running CountDownTimer plus that Android itself will probably create a small delay when calling the message handler of CountDownTimer it is more than likely that the call for the last tick before the end of the count down will be at least one millisecond late and therefore onTick() will not be called.

    For my application I solved this problem simply by making the tick intervals "slightly" smaller (500 ms)

        myCountDownTimer = new CountDownTimer(countDownTime, intervalTime - 500) {
                                       ...
        }
    

    and I could leave my code just as it is. For applications where the length of the interval time is critical, the other solutions posted here are probably the best.

    0 讨论(0)
  • 2020-11-27 15:01

    While the solution above is valid, it can be further improved. It unnecessarily has a runnable inside another class (which can already be treated on it's own). So just create a class that extends a thread (or runnable).

        class MyTimer extends Thread {
          private long millisInFuture;
          private long countDownInterval;
          final Handler mHandler = new Handler();
    
          public MyTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
          }
    
          public void run() {
            if(millisInFuture <= 0) {
              Log.v("status", "done");
            } else {
              millisInFuture -= countDownInterval;
              mHandler.postDelayed(this, countDownInterval);
            }
          }
        }
    
    0 讨论(0)
  • 2020-11-27 15:11

    To expand on Nantoka's answer. Here's my code to ensure the view is updated correctly:

    countDownTimer = new CountDownTimer(countDownMsec, 500) 
    {
        public void onTick(long millisUntilFinished)
        {
            if(millisUntilFinished!=countDownMsec)
            {
                completedTick+=1;
                if(completedTick%2==0)      // 1 second has passed
                {
                    // UPDATE VIEW HERE based on "seconds = completedTick/2"
                }
                countDownMsec = millisUntilFinished;  // store in case of pause
            }
        }
    
        public void onFinish()
        {
            countDownMsec = 0;
            completedTick+=2;       // the final 2 ticks arrive together
            countDownTimer = null;
    
            // FINAL UPDATE TO VIEW HERE based on seconds = completedTick/2 == countDownMsec/1000
        }
    }
    
    0 讨论(0)
  • 2020-11-27 15:11

    if Your time Interval is more than 4 sec then every onTick() call would not be proper. So if you want precise result then keep interval less than 5 sec. The Reseaon is at the start of every tick, before onTick() is called, the remaining time until the end of the countdown is calculated and If this time is smaller than the countdown time interval, onTick() would not not called anymore. Instead only the next tick (where the onFinish() method will be called) is scheduled.

    0 讨论(0)
提交回复
热议问题