Android:CountDownTimer跳过最后的onTick()!

时间:2012-01-13 21:40:00

标签: java android timer countdown countdowntimer

代码:

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分配了id tv ,文本设置为“”。

12 个答案:

答案 0 :(得分:49)

我检查了CountDownTimer的源代码。 “丢失的标记”来自CountDownTimer的一个特殊功能,我还没有看到其他地方记录:

在每次打勾开始时,在调用onTick()之前,计算倒计时结束前的剩余时间。如果此时间小于倒计时时间间隔,则onTick再次被 调用。而是仅安排下一个tick(将调用onFinish()方法)。

鉴于硬件时钟并不总是非常精确,后台可能还有其他进程延迟运行CountDownTimer的线程加上Android本身在调用CountDownTimer的消息处理程序时可能会产生一个小延迟在倒计时结束之前调用最后一个滴答的可能性至少会延迟一毫秒,因此不会调用onTick()。

对于我的应用程序,我只是通过使滴答间隔“稍微”更小(500毫秒)来解决这个问题

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

我可以保留我的代码。对于间隔时间长度至关重要的应用,此处发布的其他解决方案可能是最佳解决方案。

答案 1 :(得分:22)

我不知道为什么最后一个勾号不起作用,但您可以使用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();

编辑GOOFY的问题

你应该有一个变量来保持计数器状态(布尔值)。然后你可以编写一个Stop()方法,比如Start()。

EDIT-2对于GOOFY的问题

实际上停止计数器没有错误,但是在停止(恢复)后再次启动时会出现错误。

我正在编写一个新的更新的完整代码,我刚试过它并且它正在运行。这是一个基本的计数器,通过开始和停止按钮在屏幕上显示时间。

计数器类

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

活动类

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中

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:weightSum="1">
    <TextView android:textAppearance="?android:attr/textAppearanceLarge" 
              android:text="TextView" android:layout_height="wrap_content" 
              android:layout_width="wrap_content" 
              android:id="@+id/time">
    </TextView>
    <Button android:text="Start" 
            android:id="@+id/start" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StartTimer">
    </Button>
    <Button android:text="Stop" 
            android:id="@+id/stop" 
            android:layout_width="wrap_content" 
            android:layout_height="wrap_content" 
            android:onClick="StopTimer">
    </Button>
</LinearLayout>

答案 2 :(得分:4)

我提出的最简单的解决方案如下。请注意,它仅在您需要使用秒倒计时显示的简单屏幕时才有效。

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毫秒被调用一次,但在视觉上只显示几秒钟。

答案 3 :(得分:3)

我花了好几个小时试图找出这个问题,我很高兴向您展示一个很好的工作。不要等待onFinish()调用,只需将1(或任何间隔)添加到您的单位,然后在onTick()调用中添加if语句。只需在最后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();

答案 4 :(得分:3)

虽然上述解决方案有效,但可以进一步改进。它不必要地在另一个类中有一个runnable(它已经可以自己处理了)。所以只需创建一个扩展线程(或可运行)的类。

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

答案 5 :(得分:2)

我找到了简单的解决方案。我需要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);
    }

};

小嘀嗒诀窍:)

答案 6 :(得分:2)

所以我觉得我有点过了一遍,因为我的计时器在自己的线程中运行而不是使用postDelay处理程序,尽管它总是回发到它创建的线程。我也知道我只关心秒,所以它简化了这个想法。它还允许您取消它并重新启动它。我没有内置暂停因为那不符合我的需要。

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

}

答案 7 :(得分:2)

向计时器添加几毫秒,以便有时间处理代码。 我将+100添加到您的计时器长度,并Math.ceil()添加结果,而不是添加1.

另外......第一个刻度是 AFTER 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();
}

答案 8 :(得分:1)

扩展Nantoka的答案。这是我的代码,以确保视图正确更新:

16/11/21 09:39:03 INFO Client: Application report for application_1479668866076_0014 (state: ACCEPTED)
16/11/21 09:39:04 INFO Client: Application report for application_1479668866076_0014 (state: ACCEPTED)
16/11/21 09:39:05 INFO YarnSchedulerBackend$YarnSchedulerEndpoint: ApplicationMaster registered as NettyRpcEndpointRef(null)
16/11/21 09:39:05 INFO YarnClientSchedulerBackend: Add WebUI Filter. org.apache.hadoop.yarn.server.webproxy.amfilter.AmIpFilter, Map(PROXY_HOSTS -> sandbox.hortonworks.com, PROXY_URI_BASES -> http://sandbox.hortonworks.com:8088/proxy/application_1479668866076_0014), /proxy/application_1479668866076_0014
16/11/21 09:39:05 INFO JettyUtils: Adding filter: org.apache.hadoop.yarn.server.webproxy.amfilter.AmIpFilter
16/11/21 09:39:05 INFO Client: Application report for application_1479668866076_0014 (state: RUNNING)


16/11/23 10:10:38 INFO NettyUtil: Found Netty's native epoll transport in the classpath, using it
16/11/23 10:10:38 INFO Cluster: New Cassandra host /10.0.0.4:9042 added
16/11/23 10:10:38 INFO Cluster: New Cassandra host /10.0.0.5:9042 added
16/11/23 10:10:38 INFO CassandraConnector: Connected to Cassandra cluster: Test Cluster
16/11/23 10:10:39 INFO SparkContext: Starting job: fold at WordCount.scala:17
16/11/23 10:10:39 INFO DAGScheduler: Got job 0 (fold at WordCount.scala:17) with 2 output partitions
16/11/23 10:10:39 INFO DAGScheduler: Final stage: ResultStage 0 (fold at WordCount.scala:17)
16/11/23 10:10:39 INFO DAGScheduler: Parents of final stage: List()
16/11/23 10:10:39 INFO DAGScheduler: Missing parents: List()
16/11/23 10:10:39 INFO DAGScheduler: Submitting ResultStage 0 (CassandraTableScanRDD[1] at RDD at CassandraRDD.scala:15), which has no missing parents
16/11/23 10:10:39 INFO MemoryStore: Block broadcast_0 stored as values in memory (estimated size 7.1 KB, free 7.1 KB)
16/11/23 10:10:39 INFO MemoryStore: Block broadcast_0_piece0 stored as bytes in memory (estimated size 3.7 KB, free 10.9 KB)
16/11/23 10:10:39 INFO BlockManagerInfo: Added broadcast_0_piece0 in memory on 10.0.0.6:58236 (size: 3.7 KB, free: 143.6 MB)
16/11/23 10:10:39 INFO SparkContext: Created broadcast 0 from broadcast at DAGScheduler.scala:1006
16/11/23 10:10:39 INFO DAGScheduler: Submitting 2 missing tasks from ResultStage 0 (CassandraTableScanRDD[1] at RDD at CassandraRDD.scala:15)
16/11/23 10:10:39 INFO YarnScheduler: Adding task set 0.0 with 2 tasks
16/11/23 10:10:39 INFO TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, sandbox.hortonworks.com, partition 0,RACK_LOCAL, 29218 bytes)
16/11/23 10:10:40 INFO BlockManagerInfo: Added broadcast_0_piece0 in memory on sandbox.hortonworks.com:51013 (size: 3.7 KB, free: 143.6 MB)
16/11/23 10:10:43 INFO TaskSetManager: Starting task 1.0 in stage 0.0 (TID 1, sandbox.hortonworks.com, partition 1,RACK_LOCAL, 29156 bytes)
16/11/23 10:10:43 WARN TaskSetManager: Lost task 0.0 in stage 0.0 (TID 0, sandbox.hortonworks.com): java.io.IOException: Failed to open native connection to Cassandra at {10.0.0.4, 10.0.0.5}:9042

16/11/23 10:10:45 INFO TaskSetManager: Starting task 0.1 in stage 0.0 (TID 2, sandbox.hortonworks.com, partition 0,RACK_LOCAL, 29218 bytes)
16/11/23 10:10:45 INFO TaskSetManager: Lost task 1.0 in stage 0.0 (TID 1) on executor sandbox.hortonworks.com: java.io.IOException (Failed to open native connection to Cassandra at {10.0.0.4, 10.0.0.5}:9042) [duplicate 1]

答案 9 :(得分:1)

如果您的时间间隔超过4秒,则每次onTick()呼叫都不合适。因此,如果您想要精确的结果,那么保持间隔小于5秒。 Reseaon在每个滴答开始时,在调用onTick()之前,计算倒计时结束前的剩余时间,如果此时间小于倒计时时间间隔,则onTick()不会再叫了。而是仅安排下一个tick(将调用onFinish()方法)。

答案 10 :(得分:0)

我还遇到了与CountDownTimer相同的问题,我尝试了不同的方法。 所以最简单的方法之一就是@Nantoca提供的解决方案 - 他建议将频率从1000毫秒加倍到500毫秒。但我不喜欢这种解决方案,因为它会使更多的工作耗费一些额外的电池资源。

所以我决定使用@ ocanal的灵魂来编写我自己的简单CustomCountDownTimer。

但我在他的代码中发现了几个缺陷:

  1. 效率有点低(创建第二个处理程序来发布结果)

  2. 它开始以延迟发布第一个结果。 (在首次初始化期间,您需要执行post()方法而非postDelayed()

  3. 奇怪的看。大写字母,状态而不是经典的方法是取消布隆和其他一些。

  4. 所以我清理了一下,这是他的方法更常见的版本:

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

答案 11 :(得分:-1)

您正在计算错误的剩余时间。回调获得完成任务之前的毫秒数。

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

我自己从未使用过这个课程,但看起来你不会在它启动的瞬间得到回调,这就是为什么它看起来像你错过了一个条目。例如10000毫秒,每个刻度1000毫秒,您将获得总共9个更新回调,而不是10 - 9000,8000,7000,6000,5000,4000,3000,2000,1000,完成。