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, а текст – «».

Я не знаю, почему последний тик не работает, но вы можете создать собственный таймер с 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 ДЛЯ ВОПРОСА ГУФЕТА

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

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

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

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

class активности

 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

        

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

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

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

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

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

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

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

 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 миллисекунд, но визуально отображаются только секунды.

Я потратил часы, пытаясь понять эту проблему, и я рад показать вам хорошую работу. Не беспокойтесь, ожидая 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(); 

Хотя вышеприведенное решение действительно, оно может быть дополнительно улучшено. Он излишне имеет runnable внутри другого classа (который уже может быть обработан на своем собственном). Поэтому просто создайте class, который расширяет stream (или запускается).

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

Я нашел легкое решение. Мне нужно 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); } }; 

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

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

 /** * 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()); } } } 

}

Добавьте несколько миллисекунд к вашему таймеру, чтобы дать ему время для обработки кода. Я добавил +100 к вашей длине таймера, а также Math.ceil() чтобы округлить результат, а не добавлять 1.

Также … первый тик – ПОСЛЕ 2000 миллинов, поэтому вы не получите запись «10 секунд слева», если вы не добавите ее.

 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView tv = (TextView) findViewById(R.id.tv); tv.setText("10 Seconds remain\n"); //displayed before the first tick. new CountDownTimer(10000+25, 1000) { //25 to account for processing time public void onTick(long m) { long sec = (long) Math.ceil(m / 2000 ); //round up, don't add 1 tv.append(sec + " seconds remain\n"); } public void onFinish() { tv.append("Done!"); } }.start(); } 

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

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

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

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

Поэтому я решил использовать дуэт @ ocanal и написать собственный простой CustomCountDownTimer.

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

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

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

  3. странный вид. Методы с заглавной буквы, статус вместо classического 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); } } 

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

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

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

  • Почему объект Object.toString () по умолчанию содержит hash-код?
  • javax.mail.AuthenticationFailedException выбрасывается при отправке электронной почты в java
  • Метод сравнения нарушает его общий контракт в Java 7
  • Как запустить Домашний экран программно в Android
  • Что такое sharedUserId в Android и как оно используется?
  • Как нарисовать заполненный треугольник в андроидном canvasе?
  • Какова основная цель annotations @SerializedName в андроиде с помощью GSon
  • Использование сертификатов клиент / сервер для двухсторонней аутентификации SSL-сокета на Android
  • Могу ли я выполнять арифметические операции над базовым classом Number?
  • Java: вычитает «0» из char, чтобы получить int ... почему это работает?
  • Как активировать кнопку «Поделиться» в приложении Android?
  • Давайте будем гением компьютера.