Подтвердить что ты не робот

Android: CountDownTimer пропускает последний onTick()!

Код:

public class SMH extends Activity {  

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

        TextView tv = (TextView) findViewById(R.id.tv);  

        new CountDownTimer(10000, 2000) {  
            public void onTick(long m) {  
               long sec = m/1000+1;  
               tv.append(sec+" seconds remain\n");  
            }  
            public void onFinish() {  
               tv.append("Done!");  
            }  
        }.start();  
   }

Вывод:
  10 секунд остаются
  Осталось 8 секунд   Осталось 6 секунд   Осталось 4 секунды   Готово!

Проблема:

Как мне показать, что "осталось 2 секунды"? Прошло время действительно 10 секунд, но последний onTick() никогда не происходит. Если я изменю второй параметр от 2000 до 1000, то это будет выход:

Осталось 10 секунд
Осталось 9 секунд
Осталось 8 секунд Осталось 7 секунд Осталось 6 секунд Осталось 5 секунд Осталось 4 секунды Осталось 3 секунды Осталось 2 секунды Готово!

Итак, вы видите, что это пропускает последний вызов onTick(). И, кстати, XML файл в основном представляет собой файл main.xml по умолчанию, а TextView назначил идентификатор tv, а текст - "".

4b9b3361

Ответ 1

Я не знаю, почему последний тик не работает, но вы можете создать свой собственный таймер с Runable, например.

class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
        }
    public void Start() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                if(millisInFuture <= 0) {
                    Log.v("status", "done");
                } else {
                    long sec = millisInFuture/1000;
                    Log.v("status", Long.toString(sec) + " seconds remain");
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

и запустить его,

new MyCountDownTimer(10000, 2000).Start();

РЕДАКТИРОВАТЬ ВОПРОС О ГОРЯЧЕЙ

у вас должна быть переменная для хранения состояния счетчика (логическое значение). то вы можете написать метод Stop(), например Start().

РЕДАКТИРОВАНИЕ-2 ДЛЯ ВОССТАНОВЛЕНИЯ КРОВООБРАЩЕНИЯ

на самом деле нет ошибки при остановке счетчика, но после остановки (возобновления) появляется ошибка при запуске.

Я пишу новый обновленный полный код, который я только что попробовал, и он работает. Это базовый счетчик, отображающий время на экране с кнопкой запуска и остановки.

класс для счетчика

public class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    private boolean status;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
            status = false;
            Initialize();
    }

    public void Stop() {
        status = false;
    }

    public long getCurrentTime() {
        return millisInFuture;
    }

    public void Start() {
        status = true;
    }
    public void Initialize() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                long sec = millisInFuture/1000;
                if(status) {
                    if(millisInFuture <= 0) {
                        Log.v("status", "done");
                    } else {
                        Log.v("status", Long.toString(sec) + " seconds remain");
                        millisInFuture -= countDownInterval;
                        handler.postDelayed(this, countDownInterval);
                    }
                } else {
                    Log.v("status", Long.toString(sec) + " seconds remain and timer has stopped!");
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

класс активности

public class CounterActivity extends Activity {
    /** Called when the activity is first created. */
    TextView timeText;
    Button startBut;
    Button stopBut;
    MyCountDownTimer mycounter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        timeText = (TextView) findViewById(R.id.time);
        startBut = (Button) findViewById(R.id.start);
        stopBut = (Button) findViewById(R.id.stop);
        mycounter = new MyCountDownTimer(20000, 1000);
        RefreshTimer();
    }

    public void StartTimer(View v) {
        Log.v("startbutton", "saymaya basladi");
        mycounter.Start();
    }

    public void StopTimer(View v) {
        Log.v("stopbutton", "durdu");
        mycounter.Stop();
    }

    public void RefreshTimer() 
    {
        final Handler handler = new Handler();
        final Runnable counter = new Runnable(){

            public void run(){
                timeText.setText(Long.toString(mycounter.getCurrentTime()));
                handler.postDelayed(this, 100);
            }
        };

        handler.postDelayed(counter, 100);
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:weightSum="1">
    <TextView android:textAppearance="?android:attr/textAppearanceLarge" 
              android:text="TextView" android:layout_height="wrap_content" 
              android:layout_width="wrap_content" 
              android:id="@+id/time">
    </TextView>
    <Button android:text="Start" 
            android:id="@+id/start" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StartTimer">
    </Button>
    <Button android:text="Stop" 
            android:id="@+id/stop" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StopTimer">
    </Button>
</LinearLayout>

Ответ 2

Я проверил исходный код CountDownTimer. "Отсутствующий тик" исходит из специальной функции CountDownTimer, которую я еще не видел в документах в другом месте:

В начале каждого тика, до вызова onTick(), вычисляется оставшееся время до конца обратного отсчета. Если это время меньше, чем время обратного отсчета, onTick больше не. Вместо этого запланирован только следующий тик (где будет вызываться метод onFinish()).

Учитывая тот факт, что аппаратные часы не всегда супер точные, могут быть другие процессы в фоновом режиме, которые задерживают поток, запущенный CountDownTimer, плюс тот, что сам Android, вероятно, создаст небольшую задержку при вызове обработчика сообщений CountDownTimer, это больше чем вероятность того, что вызов последнего галочка перед окончанием отсчета будет по крайней мере на одну миллисекунду позже, и поэтому onTick() не будет вызываться.

Для моего приложения я решил эту проблему просто, сделав интервалы тика "немного" меньше (500 мс)

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

и я мог оставить свой код так, как есть. Для приложений, где длина интервала времени критическая, другие решения, размещенные здесь, вероятно, являются лучшими.

Ответ 3

Я потратил часы, пытаясь понять эту проблему, и я рад показать вам хорошую работу. Не беспокойтесь, ожидая вызова onFinish(), просто добавьте 1 (или независимо от вашего интервала) к вашим устройствам, а затем добавьте оператор if в вызовы onTick(). Просто выполните свои задачи onFinish() на последнем onTick(). Вот что у меня есть:

    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();

Ответ 4

Несмотря на то, что решение выше, оно может быть дополнительно улучшено. Он излишне имеет 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);
        }
      }
    }

Ответ 5

Самое простое решение, с которым я столкнулся, заключается в следующем. Обратите внимание, что он работает только в том случае, если вам нужен простой экран для отображения с обратным отсчетом секунд.

mTimer = new CountDownTimer(5000, 100){
            public void onTick(long millisUntilFinished) {
                mTimerView.setText(Long.toString(millisUntilFinished/1000));                
             }

             public void onFinish() {
                 mTimerView.setText("Expired");
             }
        };

        mTimer.start();

В приведенном выше коде onTick() вызывается каждые 100 миллисекунд, но визуально отображаются только секунды.

Ответ 6

Я нашел легкое решение. Мне нужно CountDown для обновления ProgressBar, поэтому я сделал это:

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);
    }

};

Маленький тик делает трюк:)

Ответ 7

Итак, я думаю, что я немного перешел на борт, потому что мой таймер работает в своем потоке вместо использования обработчиков postDelay, хотя он всегда отправляется обратно в поток, в котором он был создан. Я также знал, что я заботился только о секундах, поэтому его упрощается вокруг этой идеи. Он также позволяет вам отменить его и перезапустить. У меня нет паузы, потому что это не в моих потребностях.

/**
* Created by MinceMan on 8/2/2014.
*/
public abstract class SecondCountDownTimer {

private final int seconds;
private TimerThread timer;
private final Handler handler;

/**
 * @param secondsToCountDown Total time in seconds you wish this timer to count down.
 */
public SecondCountDownTimer(int secondsToCountDown) {
    seconds = secondsToCountDown;
    handler = new Handler();
    timer = new TimerThread(secondsToCountDown);
}

/** This will cancel your current timer and start a new one.
 *  This call will override your timer duration only one time. **/
public SecondCountDownTimer start(int secondsToCountDown) {
    if (timer.getState() != State.NEW) {
        timer.interrupt();
        timer = new TimerThread(secondsToCountDown);
    }
    timer.start();
    return this;
}

/** This will cancel your current timer and start a new one. **/
public SecondCountDownTimer start() {
    return start(seconds);
}

public void cancel() {
    if (timer.isAlive()) timer.interrupt();
    timer = new TimerThread(seconds);
}

public abstract void onTick(int secondsUntilFinished);
private Runnable getOnTickRunnable(final int second) {
    return new Runnable() {
        @Override
        public void run() {
            onTick(second);
        }
    };
}

public abstract void onFinish();
private Runnable getFinishedRunnable() {
    return new Runnable() {
        @Override
        public void run() {
            onFinish();
        }
    };
}

private class TimerThread extends Thread {

    private int count;

    private TimerThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        try {
            while (count != 0) {
                handler.post(getOnTickRunnable(count--));
                sleep(1000);
            }
        } catch (InterruptedException e) { }
        if (!isInterrupted()) {
            handler.post(getFinishedRunnable());
        }
    }
}

}

Ответ 8

Расширить ответ Нантоки. Здесь мой код для обеспечения правильного обновления представления:

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
    }
}

Ответ 9

Вы неправильно вычисляете время. Обратный вызов получает количество миллисекунд до завершения задачи.

public void onTick(long m) {  
    long sec = m/1000+1;  
    tv.append(sec+" seconds remain\n");  
}  

должен быть

public void onTick(long m) {  
    long sec = m/1000;  
    tv.append(sec+" seconds remain\n");  
}

Я никогда не использовал этот класс сам, но похоже, что вы не получите обратный вызов в тот момент, когда он начнется, поэтому он выглядит так, будто вам не хватает записи. например 10000 мс, 1000 мс за галочку, вы получите в общей сложности 9 обратных вызовов обновления, а не 10 - 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, финиш.

Ответ 10

Я также столкнулся с той же проблемой с CountDownTimer, и я пробовал разные подходы. Таким образом, одним из самых простых способов является решение, предоставленное @Nantoca - он предлагает удвоить частоту от 1000 мс до 500 мс. Но мне не нравится это решение, потому что он делает больше работы, которая будет потреблять дополнительный ресурс аккумулятора.

Итак, я решил использовать @ocanal soultion и написать собственный простой CustomCountDownTimer.

Но я обнаружил пару недостатков в его коде:

  • Это немного неэффективно (создание второго обработчика для публикации результатов)

  • Он начинает публиковать первый результат с задержкой. (Для первой инициализации вам нужно выполнить post(), а не postDelayed())

  • странный вид. Методы с заглавной буквы, статус вместо классического isCanceled boolean и некоторые другие.

Итак, я немного его очистил, и вот более распространенная версия его подхода:

private class CustomCountDownTimer {

    private Handler mHandler;
    private long millisUntilFinished;
    private long countDownInterval;
    private boolean isCanceled = false;

    public CustomCountDownTimer(long millisUntilFinished, long countDownInterval) {
        this.millisUntilFinished = millisUntilFinished;
        this.countDownInterval = countDownInterval;
        mHandler = new Handler();
    }

    public synchronized void cancel() {
        isCanceled = true;
        mHandler.removeCallbacksAndMessages(null);
    }

    public long getRemainingTime() {
        return millisUntilFinished;
    }

    public void start() {

        final Runnable counter = new Runnable() {

            public void run() {

                if (isCanceled) {
                    publishUpdate(0);
                } else {

                    //time is out
                    if(millisUntilFinished <= 0){
                        publishUpdate(0);
                        return;
                    }

                    //update UI:
                    publishUpdate(millisUntilFinished);

                    millisUntilFinished -= countDownInterval;
                    mHandler.postDelayed(this, countDownInterval);
                }
            }
        };

        mHandler.post(counter);
    }
}

Ответ 11

если ваш интервал времени больше 4 секунд, каждый вызов onTick() не подходит. Поэтому, если вы хотите получить точный результат, продолжайте интервал менее 5 секунд. Reseaon находится в начале каждого тика до того, как будет вызываться onTick(), оставшееся время до конца отсчета будет рассчитано, и если это время меньше, чем временной интервал обратного отсчета, onTick() больше не будет вызываться. Вместо этого запланирован только следующий тик (где будет вызываться метод onFinish()).