Android: Countdowntimer Skips Last Ontick()!

Android: CountDownTimer skips last onTick()!

I don't know why the last tick is not working but you can create your own timer with Runable , for example.

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

and to start it,

new MyCountDownTimer(10000, 2000).Start();

EDIT FOR GOOFY'S QUESTION

you should have a variable to hold counter status (boolean) . then you can write a Stop() method like Start().

EDIT-2 FOR GOOFY'S QUESTION

actually there is no bug on stopping counter but there is a bug on start again after stop(resume).

I'm writing a new updated full code that I had just tried and it's working. It's a basic counter that show time on screen with start and stop button.

class for counter

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

activity class

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>

CountDownTimer skipping one second when displaying on TextView

There are 2 issues:

  • The first issue is that the countdown timer doesn't execute until time has elapsed for the first tick, in this case, after 1000ms.

  • The second is that the elapsed time is only approximate. millisUntilFinished is not guaranteed to come in increments of the interval (you can see that if you don't divide by 1000, the first tick is slightly under 29000).

Another thing to keep in mind is that you're not guaranteed to receive a tick call. That is to say, if the device did not have enough time to complete a tick, it may skip it (generally only noticeable for faster intervals).

To solve issue 1, you can simply run the onTick code (or refactor it into its own method) and run it as you start the countdown timer.

For issue 2, you can simply round the number.

For example:

new CountDownTimer(30000, 1000)
{
@Override
public void onTick(long millisUntilFinished)
{
performTick(millisUntilFinished);
}
@Override
public void onFinish()
{
animatedPic.setClickable(true);
// reregister the proximity sensor
sm.registerListener(sensorListener,proxSensor,SensorManager.SENSOR_DELAY_NORMAL);
coolDownTimer.setText("GO!");
}
}.start();

performTick(30000);


void performTick(long millisUntilFinished) {
coolDownTimer.setText("Cool Down for: " + String.valueOf(Math.round(millisUntilFinished * 0.001f)));
}


If you want to make sure the appropriate value is updated with minimal latency, you may want to consider reducing the interval.

android CountDownTimer - last onTick not called - what clean solution to use?

The behavior you are experiencing is actually explicitly defined in the CountdownTimer code; have a look at the source.

Notice inside of handleMessage(), if the time remaining is less than the interval, it explicitly does not call onTick() and just delays until complete.

Notice, though, from the source that CountdownTimer is just a very thin wrapper on Handler, which is the real timing component of the Android framework. As a workaround, you could very easily create your own timer from this source (less than 150 lines) and remove this restriction to get your final tick callback.

CountDownTimer firing onTick at not excepted time point

All the documentation has to say about onTick() is "Callback fired on regular interval."

http://developer.android.com/reference/android/os/CountDownTimer.html#onTick(long)

Why are you assuming it shouldn't fire immediately?

If want to ignore the first callback to onTick() you could add a boolean flag like so:

countdown = new CountDownTimer(10000, 1000) {
private boolean first = true;
@Override
public void onTick(long millisUntilFinished) {
if (first) {
first = false;
return;
}
// Stuff here
}

@Override
public void onFinish() {
// Restart countdown
first = true;
countdown.start();
}
}.start();

After looking at your usage of CountDownTimer a bit more it seems like you could also use a Handler / Runnable combination.

Here's an example:

In your Activity you do:

private Handler timerHandler;
private Runnable timerRunnable;

// ...

@Override
public void onCreate() {
super.onCreate();
timerHandler = new Handler();
timerRunnable = new Runnable() {
@Override
public void run() {
// Stuff here
timerHandler.postDelayed(timerRunnable, 5000);
}
};
timerHandler.postDelayed(timerRunnable, 5000);
}

CountDownTimer flickers between seconds

Every time onStart fires (e.g. by going to the home screen then back into the app) if the current timer still has time left, you start another one through that startVisibleCountdown() call. You haven't shown you're stopping the old one anywhere - your code isn't even keeping a reference to it, it's just started anonymously, and runs until it completes (or the app is killed)

If you have multiple timers running, they all set timeLeft according to the value passed into their onTick, and then they call the function that displays that. Since their updates are all getting posted to the message queue, it's possible there's a slight timing discrepancy between them (e.g. one has millisUntilFinished = 6000 and one has 5999) and you're getting them out of order.

That would explain why it's changing at all (you have multiple timers setting the text on the TextView when there should only be one) and why it's going backwards (no hard guarantees about which message is at the front of the queue or even when it arrives exactly)

So you need to make sure you're only ever running one instance of your timer - there are a few ways to handle that, this is probably the safest approach (not thread-safe but that's not an issue with what you're doing here):

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
}

That way, there's only one place you're actually creating and starting a timer, and that code ensures only one instance is running at a time. That instance is only cleared when the timer successfully finishes, or when it's explicitly stopped. By centralising it like this (and putting all your defensive coding in one place) you can call the start function from multiple places (onStart, some button's onClick) and it will all be handled safely

Trying to get a CountDownTimer to restart after 15 seconds.

The onFinish method is being called from the interface thread, so any interface changes you make won't actually redraw until onFinish returns. Try something like this:

  cdt = new CountDownTimerWithPause(60000,1000,true) {
@Override
public void onTick(long millisUntiFinished) {
tvClock.setText("Remaining:" + millisUntiFinished/1000);

}

@Override
public void onFinish() {
tvClock.setText("");
bell.start();
if (bAutoPlayIsOn) {

tvClock.setText("waiting for next pose...");
(
new Thread() {
public void run() {
try {
//15 second pauses between poses
Thread.sleep(15000);
getActivity().runOnUiThread
(
new Runnable() {
public void run() {
//start over
listener.onForwardItemSelected();
ResetClocktoMaxTime(60000);
resume();
}
}
);
}
catch (InterruptedException e) {
Log.d("Sleep Interrupted", e.toString());
e.printStackTrace();
}
catch (Exception e) {
Log.d("Timer", e.toString());
}
}
}

).start();

}
else {
tvClock.setText("Done");
btnStart.setText("Start ");
bClockIsTicking = false;
btnStart.setCompoundDrawablesWithIntrinsicBounds(null, playBtn, null, null);
}

}
};


Related Topics



Leave a reply



Submit