CountDownTimer 在秒之间闪烁

CountDownTimer flickers between seconds

我有一个 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
        }
    }
}

试试这个,它适用于我,但它 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();

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

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

在你startVisibleCountdown()中添加一些日志输出(Log.d(...)),只是为了确保它不会被多次调用。

编辑:由于您检查过您的点击处理程序没有被多次调用,这意味着当您的 activity/fragment 生命周期停止并重新启动时,您不会在代码中的任何地方停止倒计时(我们只能看到你的 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()
}

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

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