安卓倒计时器 - 最后一个onTick没有被调用 - 有什么清晰易懂的解决方法可用?

5

我刚刚遇到了“CountDownTimer - last onTick not called”问题,这是许多人在这里报告的。

这里有一个简单的演示,展示了这个问题。

package com.example.gosh;

import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.util.Log;

public class CountDownTimerSucksActivity extends Activity {

int iDontWantThis = 0; // choose 100 and it works yet ...

private static final String TAG = "CountDownTimerSucksActivity";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    new MyCountDownTimer(10000 + iDontWantThis , 1000).start();
}

class MyCountDownTimer extends CountDownTimer {

    long startSec;

    public MyCountDownTimer(long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
        // TODO Auto-generated constructor stub
        startSec = System.currentTimeMillis() ;
    }

    @Override
    public void onFinish() {
        // TODO Auto-generated method stub
        Log.e(TAG, " onFinish (" + getSeconds() + ")");
    }

    @Override
    public void onTick(long millisUntilFinished) {
        // TODO Auto-generated method stub
        Log.e(TAG, millisUntilFinished + " millisUntilFinished" + " (" + getSeconds() + ")");

    }

    protected long getSeconds() {
        return  (((System.currentTimeMillis() - startSec) / 1000) % 60);

    }

}

}

测试运行中的logcat输出...

logcat ouput

正如您所看到的,最后一次onTick调用发生在1963毫秒之后,然后下一次调用将近2秒后的onFinished。这肯定是一个有缺陷的行为。我在许多帖子中找到了这个问题,但还没有干净的解决方案。我在源代码中包含了一个解决方案,如果将iDontWantThis字段设置为100,则可以解决该问题。
我不介意在较小的领域使用变通方法,但这似乎是如此核心的功能,以至于我无法想象它尚未修复。你们做了什么来找到一个干净的解决方案?
非常感谢
马丁
更新:
Sam对CountDownTimer进行了非常有用的修改,它不会由于内部毫秒延迟而抑制最后一个tick,并且还可以防止随着时间的推移每个tick累积ms延迟。 在这里 可以找到。

可能是重复的问题:倒计时定时器从预期时间延迟一秒后开始计时 - deepGrave
4个回答

8
你所经历的行为实际上是在CountdownTimer代码中明确定义的;查看源码

请注意,在handleMessage()内部,如果剩余时间小于间隔时间,它会明确地不调用onTick()并延迟直到完成。

请注意,从源代码中可以看出CountdownTimer只是Android框架中真正计时组件Handler的一个非常薄的包装。作为解决方法,你可以非常容易地从这个源代码(少于150行)创建自己的计时器,并删除此限制以获取最终的tick回调。


非常感谢,我对Android还很陌生,像这种情况下我还没有深入研究过它的源代码。非常感谢您指引我迈出这一步。我将按照建议实现自己的CountDownTimer,如果millisUntilFinished > 0,它将始终调用onTick,并且我可以轻松检查剩余的毫秒数是否允许我想要做的事情(实际上我只需要一个倒计时器,显示6,5,4,3,2,1,0)。 - dorjeduck
仅仅为了完整性而言,移除下面的else if代码块就可以让onTick在剩余毫秒数不足mCountdownInterval时被调用。如果没有tick,则只需延迟直到完成。通过sendMessageDelayed方法发送一个消息(MSG),延迟时间为剩余毫秒数(millisLeft)。 - dorjeduck

2
我认为挫败感来自于对“tick”应该是什么的错误期望。正如其他答案所指出的那样,这种行为是有意的。另一种处理方式是简单地指定较小的间隔。例如,如果您正在实现某种倒计时时钟,将间隔更改为500不会有害。如果重要的是只在秒数变化时执行某些工作,则可以通过存储getSeconds()的结果并仅在该值更改时执行该工作来实现。
如果CountdownTimer被更改为始终触发最后一个“tick”,即使剩余时间小于间隔,我相信StackOverflow会有一堆问题,比如“为什么在CountdownTimer的最后一个“tick”中没有足够的时间?”

你说得对,我只是期望了不同的行为,这也是导致沮丧的根源。我很惊讶在API文档中没有提到预期的行为,特别是在上面的logcat中可以看到倒计时器每次tick都会比给定的secondsLeft/intervals晚47毫秒,而这47毫秒导致onTick没有按照我的期望被调用。想象一下,如果你将4小时作为间隔,谁会期望因为43毫秒而不调用onTick呢?但是,这就是期望的问题。 - dorjeduck

1
我不明白你为什么说这是故意的行为,API明确说明:

“安排一个倒计时,直到未来的某个时间,并在途中按照间隔定期通知。”

new CountDownTimer(30000, 1000) {

    public void onTick(long millisUntilFinished) {
        mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
    }

    public void onFinish() {
        mTextField.setText("done!");
    }
}.start();

如果您将时间设置为30秒,并将countDownInterval设置为1000,就像API所说的那样,它应该准确地触发30次。

我认为这不是一种故意的行为,而是一种错误的实现。

解决方案应该是Sam在这里提出的:

{{link1:android倒计时器-间隔毫秒延迟}}


谢谢Juan - Sam的解决方案已经在帖子的更新中提到了。 - dorjeduck

1

Android的CountDownTimer在计时器启动后(如第93行代码所示),立即调用onTick()

之后,onTick()根据计时器完成前剩余的时间进行调用。

如果计时器完成前剩余时间小于指定的时间间隔,则不会调用onTick()(第136行代码)。这就是你的最后一个onTick()未被调用的原因。

修改后的CountDownTimer类

我已经将计时器修改为在指定延迟后调用所有间隔的onTick()(包括第一个和最后一个)。以下是该类 -

public abstract class CountDownTimer {

    private final long mMillisInFuture;
    private final long mCountdownInterval;
    private long mStopTimeInFuture;

    private boolean mCancelled = false;

    public CountDownTimer(long millisInFuture, long countDownInterval) {
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countDownInterval;
    }

    public synchronized final void cancel() {
        mCancelled = true;
        mHandler.removeMessages(MSG);
    }

    public synchronized final CountDownTimer start() {
        mCancelled = false;
        if (mMillisInFuture <= 0) {
            onFinish();
            return this;
        }
        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
        onTick(mMillisInFuture);
        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG), mCountdownInterval);
        return this;
    }

    public abstract void onTick(long millisUntilFinished);

    public abstract void onFinish();

    private static final int MSG = 1;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            synchronized (CountDownTimer.this) {
                if (mCancelled)
                    return;
                final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
                if (millisLeft <= 0) {
                    onFinish();
                } else {
                    onTick(millisLeft);
                    sendMessageDelayed(obtainMessage(MSG), mCountdownInterval);
                }
            }
        }
    };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接