【问题标题】:CountDownTimer flickers between secondsCountDownTimer 在几秒之间闪烁
【发布时间】:2021-12-16 03:21:59
【问题描述】:

我有一个运行良好的 CountDownTimer。但是,它有时会在显示倒计时的 TextView 中开始闪烁。我的显示是这样的:

HH:MM:SS

在倒计时时,最后一秒会在一秒钟内从例如 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("%02d", hours)
            + ":" + String.format("%02d", minutes)
            + ":" + String.format("%02d", 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 > 0) {
            startVisibleCountdown()
        } else {
            timeLeftInMillisecondsVisibleCounter = 43200000

            visibleCountdownRunning = false
        }
    }
}

【问题讨论】:

  • 您是否还在整个代码的其他地方调用了 startVisibleCountdown() 函数?
  • 是的,抱歉,这只是在 OnCreateView 中单击一个按钮即可启动它。要我为其添加代码吗?
  • 看看我的回答有没有帮助,如果没有,也请提供那部分代码。

标签: android kotlin countdown countdowntimer flicker


【解决方案1】:

每次onStart 触发时(例如,通过转到主屏幕然后返回应用程序)如果当前计时器还有时间剩余,则您通过startVisibleCountdown() 调用启动另一个计时器。您还没有表明您正在任何地方停止旧代码 - 您的代码甚至没有保留对它的引用,它只是匿名启动,并一直运行到它完成(或应用程序被终止)

如果您有多个计时器在运行,它们都会根据传递给它们的onTick 的值设置timeLeft,然后调用显示该值的函数。由于它们的更新都已发布到消息队列中,因此它们之间可能存在细微的时间差异(例如,一个具有 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
}

这样,您实际上只在一个地方创建和启动计时器,并且该代码确保一次只运行一个实例。仅当计时器成功完成或明确停止时,该实例才会被清除。通过像这样集中它(并将所有防御性编码放在一个地方),您可以从多个地方调用 start 函数(onStart,一些按钮的onClick),这一切都将得到安全处理

【讨论】:

  • 我很欣赏这些努力,但事实并非如此。 OnStart 仅在倒计时继续超过 0 时将其设置为负值。是的,我确实在 OnStart 中减去了一个很大的数字。但是,如果您注意到我在 OnStop 上添加了相同的数字。这就是我在使用系统时间关闭应用程序时跟踪倒计时的方式。倒计时不会在 onStop 之后继续运行。我还用 Log 检查了计时器没有多次启动。
  • 哦,我知道你在 onStop 中所做的事情,我的错(这是一个非常好的解决方案)。但在这种情况下,如果它正确地跟踪剩余时间,那么您有时会在onStart 中调用startVisibleCountdown() - 这会创建一个新的计时器实例。你怎么阻止旧的?您的代码没有保留对它的引用
  • 我在答案中添加了更多信息,因为这里没有空间来解释效果!
  • 谢谢,在我的 OnStop 方法中取消计时器确实解决了这个问题。我进行了一些编辑以消除绒毛并使其成为解决问题的好资源。希望你不要介意。
  • @TheFluffyTRex 不用担心 - 我将解决方案代码调整为我认为更安全的代码,通过 start 函数强制执行单个实例。对于您正在做的事情(或至少您发布的内容)来说,这并不是一个真正的问题,但我认为防御并强制执行有效行为是一种很好的做法,作为一种一般方法,我建议我认为!
【解决方案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();

【讨论】:

  • 您不应该在倒数计时器中执行 time * 1000。你为什么这样做?
  • 因为它的毫秒数,每 1000 英里 = 1 秒 @TheFluffyTRex
  • @TheFluffyTRex ..
【解决方案3】:

由于您同时从onStart() 调用startVisibleCountdown() 函数,并且当单击按钮时,很可能您有2 个CountDownTimer 对象正在运行并同时调用您的updateCountDownTextVisible() 代码。这可以用一种奇怪的方式解释闪烁和跳秒。

请记住,即使您在 onStart() 函数中注释掉对 startVisibleCountdown() 的调用,Android 有时也不会阻止多次点击(取决于您使用的按钮类型以及您是如何操作的)连接您的 onClickListener),因此您可能还会在单击按钮时启动多个倒数计时器。

startVisibleCountdown() 中添加一些日志输出(Log.d(...)),以确保它不会被多次调用。

编辑:由于您检查了您的点击处理程序没有被多次调用,那么这意味着当您的活动/片段生命周期停止和重新启动时,您不会在代码中的任何地方停止倒计时(我们只能看到您的 onStop 和 onStart方法,但你不会在那里停止倒计时)。

这意味着即使在调用 onStop 之后,计时器也会继续在后台运行。因此,当再次调用 onStart 时,它将启动另一个计时器,并且根据时间(这就是它有点随机的原因),您可能会看到秒数“跳跃”然后再次向下。

所以我的建议还是和以前一样。 在您的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()
}

【讨论】:

  • 不。事实并非如此。我检查了日志。当我使用 onClick 方法时,如果不单击按钮就开始倒计时是没有意义的。您描述的场景意味着即使我没有按下按钮,也会调用 OnClick 方法中的代码。
  • 我描述了一个场景,在您单击按钮后,OnClick 方法中的代码将被多次调用。我从来没有说过不会在您与按钮交互的情况下调用 OnClick。但是,如果您确认没有在任何时间点同时运行 2 个倒计时,那么您将必须提供单击按钮时运行的所有代码,以供其他人使用能够提供帮助。您能否在更新问题时也包括日志行。
  • @TheFluffyTRex 查看我的编辑
猜你喜欢
  • 2016-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-03
  • 1970-01-01
  • 2015-05-16
相关资源
最近更新 更多