Обратный отсчет времени мигает между секундами

#android #kotlin #countdown #countdowntimer #flicker

#Android #котлин #обратный отсчет #обратный отсчет времени #вспышка

Вопрос:

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

ЧЧ:ММ:СС

При обратном отсчете последняя секунда будет прыгать, например, с 9 до 7 до 8 за одну секунду. А затем в следующую секунду он будет быстро мигать с 8 до 6 до 7.

Я попытался передать переменную millisUntilFinished непосредственно методу, обновляющему TextView, но проблема сохраняется. Обратите внимание, что я сохраняю обратный отсчет и продолжаю его во время методов onStop и OnStart.

 private fun startVisibleCountdown() {   visibleCountdownRunning = true   object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {   override fun onTick(millisUntilFinished: Long) {  timeLeftInMillisecondsVisibleCounter = millisUntilFinished  updateCountDownTextVisible()  }   override fun onFinish() {  //not relevant here  }  }.start() }  fun updateCountDownTextVisible() {  var seconds = (timeLeftInMillisecondsVisibleCounter / 1000).toInt()  val hours = seconds / (60 * 60)  val tempMint = seconds - hours * 60 * 60  val minutes = tempMint / 60  seconds = tempMint - (minutes * 60);   textViewTimer.text = (String.format("d", hours)    ":"   String.format("d", minutes)    ":"   String.format("d", seconds)) }  

Сохранение и возврат к обратному отсчету при закрытии приложения:

 override fun onStop() {  super.onStop()   if (visibleCountdownRunning) {  timeLeftInMillisecondsVisibleCounter  = System.currentTimeMillis()  }   val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return  with (sharedPref.edit()) {  putLong(timeLeftVisibleCounterKey, timeLeftInMillisecondsVisibleCounter)  putBoolean(visibleCountdownRunningKey, visibleCountdownRunning)  apply()  } }  override fun onStart() {  super.onStart()   val sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) ?: return   timeLeftInMillisecondsVisibleCounter = sharedPref.getLong(timeLeftVisibleCounterKey, 43200000)   visibleCountdownRunning = sharedPref.getBoolean(visibleCountdownRunningKey, false)   if (visibleCountdownRunning) {  timeLeftInMillisecondsVisibleCounter -= System.currentTimeMillis()   if (timeLeftInMillisecondsVisibleCounter gt; 0) {  startVisibleCountdown()  } else {  timeLeftInMillisecondsVisibleCounter = 43200000   visibleCountdownRunning = false  }  } }  

Комментарии:

1. Вызываете ли вы функцию startVisibleCountdown() также где-то еще во всем вашем коде?

2. Да, извините, это просто нажатие кнопки, чтобы запустить его в onCreateView. Хотите, чтобы я добавил код для этого?

3. Посмотрите, поможет ли мой ответ, если нет, пожалуйста, предоставьте и эту часть кода.

Ответ №1:

Каждый раз onStart , когда срабатывает (например, при переходе на начальный экран, а затем обратно в приложение), если у текущего таймера еще осталось время, вы запускаете другой через этот startVisibleCountdown() вызов. Вы нигде не показали, что останавливаете старую версию — ваш код даже не содержит ссылки на нее, он просто запускается анонимно и запускается до тех пор, пока не завершится (или приложение не будет убито).

Если у вас запущено несколько таймеров, все они устанавливаются timeLeft в соответствии со значением , переданным в них onTick , а затем они вызывают функцию, которая отображает это. Поскольку все их обновления публикуются в очереди сообщений, возможно, между ними есть небольшое расхождение во времени (например, у одного есть millisUntilFinished = 6000 , а у другого есть 5999 ), и вы выводите их из строя.

Это объяснило бы, почему он вообще меняется (у вас есть несколько таймеров, устанавливающих текст в то TextView время, когда должен быть только один), и почему он идет в обратном направлении (нет жестких гарантий относительно того, какое сообщение находится в начале очереди или даже когда оно приходит точно).

Поэтому вам нужно убедиться, что вы когда — либо запускаете только один экземпляр своего таймера-есть несколько способов справиться с этим, это, вероятно, самый безопасный подход (не потокобезопасный, но это не проблема с тем, что вы здесь делаете).:

 private var myTimer: CountdownTimer? = null  ...  fun startVisibleCountdown() {  // enforce a single current timer instance  if (myTimer != null) return  myTimer = object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {  ...  override fun onFinish() {  // if a timer finishes (i.e. it isn't cancelled) clear it so another can be started  myTimer = null  }  } }  ...  override fun onStop() {  ...  // probably better in a function called stopVisibleCountdown() for symmetry and centralising logic  myTimer?.run { cancel() }  myTimer = null }  

Таким образом, на самом деле вы создаете и запускаете таймер только в одном месте, и этот код гарантирует, что одновременно запускается только один экземпляр. Этот экземпляр очищается только тогда, когда таймер успешно завершается или когда он явно остановлен. Централизовав его таким образом (и поместив все ваше защитное кодирование в одном месте), вы можете вызвать функцию запуска из нескольких мест ( onStart некоторые кнопки onClick ), и все это будет безопасно обработано

Комментарии:

1. Я ценю ваши усилия, но это не так. OnStart устанавливает отрицательное значение только в том случае, если обратный отсчет продолжался после 0. Да, я вычитаю большое число в начале. Но если вы заметили, я добавляю тот же номер на onStop. Вот как я отслеживаю обратный отсчет, пока приложение закрыто, используя системное время. Обратный отсчет не продолжается после остановки. Я также проверил в журнале, что таймер не запускается несколько раз.

2. О , я вижу, чем ты занимаешься onStop , мой плохой (и это довольно хорошее решение). Но в этом случае, если он правильно отслеживает оставшееся время, то вы иногда звоните startVisibleCountdown() onStart — это создает новый экземпляр таймера. Как ты остановишь старую? Ваш код не содержит ссылки на него

3. Я поместил больше информации в ответ, так как здесь нет места для объяснения эффектов!

4. Спасибо, и действительно, отмена таймера в моем методе onStop решила проблему. Я внес некоторые правки, чтобы вырезать пух и сделать его хорошим ресурсом, который решает проблему. Надеюсь, ты не возражаешь.

5. @TheFluffyTRex не беспокойтесь — я изменил код решения, чтобы он был более безопасным, применив один экземпляр с помощью функции запуска. На самом деле это не проблема для того, что вы делаете (или, по крайней мере, для того, что вы опубликовали), но я думаю, что это хорошая практика-защищаться и обеспечивать правильное поведение, и в качестве общего подхода я бы рекомендовал, я думаю!

Ответ №2:

Попробуйте это, это работает со мной, но это java, преобразуйте его в Kotlin, и он будет работать с вами

 long time = 20 *60;  new CountDownTimer( time * 1000, 1000) {   @SuppressLint("SetTextI18n")  public void onTick(long millisUntilFinished) {  String timeremaining = TimetoString(millisUntilFinished);  // update your textview here   }   public void onFinish() {  // here when the counter finish     }  }.start();  

Комментарии:

1. Вы не должны делать время * 1000 в таймере обратного отсчета. Зачем ты это делаешь?

2. потому что его миллисекунды, каждые 1000 миль = 1 сек @TheFluffyTRex

3. @TheFluffyTRex ..

Ответ №3:

Поскольку вы вызываете свою startVisibleCountdown() функцию как из onStart() , так и при нажатии кнопки, вполне вероятно, что у вас CountDownTimer одновременно запущено 2 объекта и вызывается ваш updateCountDownTextVisible() код. Это может странным образом объяснить мерцание и пропуск секунд.

Имейте в виду, что даже если вы закомментируете вызов startVisibleCountdown() внутри своей onStart() функции, Android иногда не предотвращает прохождение нескольких кликов (зависит от того, какой тип кнопки вы используете и как вы подключили свой OnClickListener), поэтому при нажатии на кнопку вы также можете запускать несколько таймеров обратного отсчета.

Добавьте в себя вывод журнала ( Log.d(...) ) startVisibleCountdown() , просто чтобы убедиться, что он не вызывается несколько раз.

РЕДАКТИРОВАТЬ: Поскольку вы проверили, что ваши обработчики щелчков не вызываются несколько раз, это означает, что вы не останавливаете обратный отсчет нигде в своем коде, когда жизненный цикл вашей активности/фрагмента останавливается и перезапускается (мы можем видеть только ваши методы onStop и OnStart, но вы не останавливаете обратный отсчет там).

Это означает, что таймер продолжает работать в фоновом режиме даже после вызова onStop. Поэтому, когда запуск будет вызван снова, он запустит другой таймер, и в зависимости от времени (вот почему он немного случайный) вы можете увидеть, как секунды «скачут» вверх, а затем снова вниз.

Так что мой совет будет таким же, как и раньше. Добавьте некоторые выходные данные журнала в свой startVisibleCountdown() , просто чтобы убедиться, что он не вызывается несколько раз. Воспроизведите поведение мерцания и понаблюдайте за журналом. В нем будет несколько вызовов startVisibleCountdown() .

Вы можете просто начать обратный отсчет, перевести приложение в фоновый режим, подождать секунду и снова открыть его. Повторите несколько раз, если это не сработает в первый раз, так как это зависит от времени.

Чтобы решить эту проблему, остановите таймер обратного отсчета в методе onStop ().

 // to keep the reference to the CountDownTimer private var countDown: CountDownTimer? = null  override fun onStop() {  // ...  countDown?.cancel()  countDown = null  // ... }  private fun startVisibleCountdown() {  //...  countDown = object : CountDownTimer(timeLeftInMillisecondsVisibleCounter, 1000) {  // ...  }  countDown.start() }  

Комментарии:

1. Нет. Это не тот случай. Я сверился с Журналом. Нет смысла начинать обратный отсчет без моего нажатия кнопки, когда я использую метод onClick. Сценарий, который вы описываете, означает, что код внутри метода OnClick будет вызван, даже если я не нажму кнопку.

2. Я описываю сценарий, в котором после нажатия кнопки код внутри метода OnClick будет вызываться несколько раз. Я никогда не говорил, что OnClick будет вызван без вашего взаимодействия с кнопкой. Но если вы подтвердили, что у вас нет 2 отсчета времени, запущенных одновременно в любой момент времени , то вам придется предоставить весь код, который запускается при нажатии кнопки, чтобы другие могли помочь. Не могли бы вы также включить строку журнала при обновлении вопроса.

3. @TheFluffyTRex смотрите мою правку