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

frustration post ....
I just stumbled into the "CountDownTimer - last onTick not called" problem many have reported here.
Simple demo showing the problem
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);
}
}
}
The logcat output from a test run ...
As you can see the last call onTick is happening with 1963ms millisUntilFinished, then the next call is onFinished nearly 2 seconds later. Surely a buggy behavior. I found many posts on this yet no clean solution yet. One I included in the source code, if you set the iDontWantThis field to 100 it works.
I dont mind workarounds in minor fields yet this seems such a core functionality that i cant fathom it wasnt fixed yet. What are you people doing to have a clean solution for this?
Thanks a lot
martin
UPDATE:
A very useful modification of the CountDownTimer by Sam which does not surpresses the last tick due to internal ms delay and also prevents the accumulation of ms delay with each tick over time can be found here

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.

I think the frustration comes from an incorrect expectation of what a tick should be. As the other answer noted, this behavior is intentional. Another possible way of handling this is to simply specify a smaller interval. If you were implementing some sort of countdown clock for example, it wouldn't hurt to change the interval to 500. If it's important that some work is only done when the seconds change, then you can do that too by storing the result of getSeconds() and only doing that work when that value changes.
If CountdownTimer were changed to always fire that last tick even if the remaining time is less than the interval, I'm sure StackOverflow would have a bunch of questions like "why do I not have enough time in the last tick of CountdownTimer?"

I don't understand why you say that it is intentional behaviour, the API says exactly:
"Schedule a countdown until a time in the future, with regular notifications on intervals along the way."
new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
}
public void onFinish() {
mTextField.setText("done!");
}
}.start();
if you set the time to 30 seconds, and the countDownInterval to 1000, as the API says regular, it should be fired exactly 30 times.
I think it's not an intentional behaviour but a wrong implementation.
The solution should be the one proposed by Sam here:
android CountDownTimer - additional milliseconds delay between ticks

Android's CountDownTimer calls onTick() for the first time as soon as (without delay) the timer is started (as can be seen on line number 93)
Post this, onTick() is called based upon the time remaining until the timer is completed.
If the time remaining until timer completed is less than the time interval specified, then onTick() is not called (line number 136). This is the reason your last onTick() is not being called.
Modified CountDownTimer class
I have modified the timer to call onTick() all intervals (including first and last) after specified delay. Here is the class -
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);
}
}
}
};
}

Related

Running for loop periodically

I want to run a for loop every 5 minutes for an Android application. Is there a better method than this where less processing is done
for(;;) {
// code
final long NANOSEC_PER_SEC = 1000 * 1000 * 1000;
long startTime = System.nanoTime();
while ((System.nanoTime() - startTime) < 5 * 60 * NANOSEC_PER_SEC) {
}
}
CountDownTimer is a good class for this kind of work. It looks like this:
long duration = 12345;
long ticksInterval = 5000; // 5 second in millis
new CountDownTimer(duration, ticksInterval){
public void onTick(long remaining) {
// Do something each ticksInterval millis
}
public void onFinish() {
// Do something after duration millis
}
}.start();
That said, I don't think you can use this for an infinite amount of time since it requires an ending time. Though you can hack to restart it once it's finished.
You should go for set Repeat Alert for every 5 Minute and Run your code inside that .
Also you can go for use of Handler like below code .
final int REPEAT_CALL=2;
final int mFIVE_MINUTE=5*60*1000;
private Handler mHandler=new Handler(){
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch(msg.what){
case REPEAT_CALL:
//This will get execute by every 5 minute so write you for loop code here.
mHandler.sendEmptyMessageDelayed(REPEAT_CALL,mFIVE_MINUTE);
break;
}
}
};
You can start it by calling this :
mHandler.sendEmptyMessageDelayed(REPEAT_CALL,10);
Then after it will call by every 5 minute .
You can cancel this call back by calling below method:
mHandler.removeMessages(REPEAT_CALL);

How can I control a timer in android?

I want to make an application about mini game.
Detail : In 2 seconds you must to answer a question if you don't answer or the answer is wrong -> Game Over . But if your answer is true the Timer will reset become 0 and countdown again with diffirent question.
I have already seen many code about timer in website but I don't understand clearly about it :(
So I want to ask : How can i set up a timer run only 2 seconds and how can i reset it and continue with a new question ?
Please help me.
you can use CountDownTimer in android like this:
public class Myclass {
myTimer timer =new myTimer(2000,1000);
public void creatQuestion(){
timer.start();
//method you init question and show it to user
}
public void getUserAnswer(/*evry thing you expected*/)
{
//if answer is true call timer.start()
//else call timer.onFinish(); to run onfinish in timer
}
public class myTimer extends CountDownTimer {
public myTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override
public void onTick(long millisUntilFinished) {
// you can update ui here
}
#Override
public void onFinish() {
this.cancel();
//fire game over event
}
}
}
i hope it make you satisfy
I've done something similar using Thread/Runnable.
Thread t = new Thread(new Runnable() {
public void run() {
final long startTime = getTime();
final long maxEndTime = startTime + 2000L;
try {
while (shouldContinueWaiting()) {
if (getTime() > maxEndTime) {
throw new TimeoutException();
}
sleep();
}
} catch (InterruptedException e) {
handleInterrupt();
} catch (TimeoutException e) {
handleTimeout();
}
}
boolean shouldContinueWaiting() {
// Has the user already answered?
}
void handleInterrupt() {
// The user has answered. Dispose of this thread.
}
void handleTimeout() {
// User didn't answer in time
}
void sleep() throws InterruptedException {
Thread.sleep(SLEEP_DURATION_IN_MILLIS);
}
void getTime() {
return System.currentTimeMillis();
}
then you can start/restart the thread by:
t = new Thread(same as above...);
t.start();
and stop by:
t.interrupt();
We want to use the Timer class.
private Timer timer;
When you're ready for the timer to start counting -- let's say it's after you press a certain button -- do this to start it:
timer = new Timer();
timer.scheduleAtFixedRate(incrementTime(), 0, 100);
The first line is us creating a new Timer. Pretty standard. The second line, however, is the one I wanted you to see.
incrementTime() is a method that is called at the end of every "tick" of the clock. This method can be called whatever you want, but it has to return an instance of TimerTask. You could even make an anonymous interface if you want, but I prefer moving it off into its own section of code.
The 0 is our starting location. We start counting from here. Simple.
The 100 is how large a "tick" of the clock is (in milliseconds). Here, it's every 100 milliseconds, or every 1/10 of a second. I used this value at the time of writing this code because I was making a stopwatch application and I wanted my clock to change every 0.1 seconds.
As for your project, I'd suggest making the timer's task be your question switch method. Make it happen every 2000 milliseconds, or 2 seconds.
You can use a Handler.
Handler h = new Handler();
h.postDelayed(new Runnable() {
#Override
public void run() {
//this will happen after 2000 ms
}
}, 2000);
Maybe this can help you:
Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
// FIRE GAME OVER
handler.postDelayed(this, 2000); // set time here to refresh textView
}
});
You can fire your game over after 2000 milliseconds.
If you get the question correct -> remove callback from handler and reset it when the next question starts.

Why is my CountDownTimer counting faster than given parameters?

I'm having an issue with my CountDownTimer. Here's the situation:
Working on a game and I am not using a framework (who needs one, right? coughs) and I have a CountDownTimer running. This CountDownTimer is used to calculate how long a player has to complete a level, and also update the screen per 100ms tick.
In the first level this works fine, but in the second level it ticks per 50ms, and in the third per 60/90/50ms ticks (yes, in that order repeatedly) making the game behave odly and messing up collision.
I have no idea what's going on here.
I've ruled out if it's dependant on the level by loading later levels first, and upon solving those the problem arrises all the same.
I have, however, pinpointed where it's going wrong: During the loading for a new level, and only then; even reloading a current level doesn't bring this bug up.
I have a suspicion that somehwere in this code, something's not going as it should be:
public void prepareNewLevel(){
RelativeLayout rl = (RelativeLayout)findViewById(R.id.game);
level.clear();
movables.clear();
img_move.clear();
img_noMove.clear();
timerCount.cancel();
totalSolved = 0;
solvable = 0;
levelWidth = 0;
levelHeight = 0;
allCollided = false;
firstDraw = true;
rl.removeAllViewsInLayout();
}
level, movables, img_move and img_noMove are List items.
This my timer:
MyCount update = new MyCount(15*1000, 100);
This is MyCount:
public class MyCount extends CountDownTimer{
public MyCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override
public void onTick(long millisUntilFinished) {
remaining_time = millisUntilFinished;
if(!loadingNewLevel)
MoveObjects();
}
#Override
public void onFinish() {
resetLevel = true;
upMotion = false;
downMotion = false;
leftMotion = false;
rightMotion = false;
update.start();
}
}
And then I also call update.start() at the end of my loadLevel() function. I first thought I had two timers running at once, but writing to the LogCat showed me that was false, concidering I got a neat 'reset' message every 15 seconds, instead of at multiple moments smaller than 15s.
And that's basically the issue here. Now that I know where it's being caused and what the issue is, how do I solve it?
-Zubaja
Try use Timer instead CountDownTimer:
// start timer inside your method
if( timer != null) timer.cancel();
timer = new Timer();
timer.scheduleAtFixedRate( new TimerTask()
{
#Override
public void run()
{
handler.obtainMessage( yourInegerMessage).sendToTarget();
}
}, 100, 15000);
// and define you handler
public Handler handler = new Handler( new Handler.Callback()
{
#Override
public boolean handleMessage( Message message)
{
// use 'message.what' as yourInegerMessage
// ...
return true;
}
});
// and stop your timer after first time
May be this help you.
May be you start more one CountDownTimer? Try stop existing CountDownTimer before start new CountDownTimer.

Starting Two (or More) Functions Simultaneously in Android

I'm in the process of designing a chronometer / countdown timer app for Android 2.2 and would like one button press to start both the chronometer and the timer simultaneously. So, ideally, I'd like the seconds (time) on both the chronometer and timer to change at the same instance. (The timer will be counting down even as the chronometer is counting up). Since I'm using the chronometer and timer functionality provided by Android, I wrote the following piece of code when the user presses the 'Start' button
private boolean mStartPressedOnce = false;
long mTimeWhenStopped = 0;
Chronometer mChronometer;
MyCounter mCounter;
...
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartButton:
// Perform some initialization for the chronometer depending
// on the button press
if (mStartPressedOnce == false) {
mChronometer.setBase(SystemClock.elapsedRealtime());
} else {
mChronometer.setBase(SystemClock.elapsedRealtime() + mTimeWhenStopped);
}
// Perform the initialization for the timer
mCounter = new MyCount(45000, 1000);
// Fire up the chronometer
mChronometer.start();
// Fire up the timer
mCounter.start();
break;
case R.id.ResetButton:
// Reset the chronometer
mChronometer.setBase(SystemClock.elapsedRealtime());
mTimeWhenStopped = 0;
break;
case case R.id.StopButton:
mStartPressedOnce = true;
// Stop the chronometer
mTimeWhenStopped = mChronometer.getBase() - SystemClock.elapsedRealtime();
mChronometer.stop();
break;
}
...
public class MyCounter extends CountDownTimer {
#Override
public MyCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override
public void onFinish() {
// Nothing to do here
}
#Override
public void onTick(long millisUntilFinished) {
long seconds = (long) (millisUntilFinished / 1000);
long minutes = (long) ((millisUntilFinished / 1000) / 60);
long hours = (long) (((millisUntilFinished / 1000) / 60) / 60);
// Do some formatting to make seconds, minutes and hours look pretty
// Update the timer TextView
(TextView) findViewById(R.id.CountDownTimerTextView))
.setText(hours + ":" + minutes + ":" + seconds);
}
}
Though it looks like the seconds on the chronometer and timer are in sync initially, after a short time, they seem to go off and the second updates for both occur at different times.
Was wondering what I could do to fix this. I did come across - and read this thread
Running multiple AsyncTasks at the same time -- not possible?
I realize that there may be a design change needed but I'm not sure exactly what needs to be done.
Edit: Included types for chronometer and timer and method for calculating time using Chronometer - per jolivier and njzk2's suggestions
You can retrieve the current time with System.currentTimeMillis(), store it into a variable and forward it to both mChronometer and mCounter, so that they use the same time reference although their task started at different time.
Edit: with the given types, the android documentation about Chronometer will tell you that you can use elapsedRealTime to achieve what I said.
CountDownTimer does not have this and its start method is final so you may want to use another implementation, a better view of your use case might help us.
Basically, wanting two threads to perform an action at the same millisecond is never a good idea, one of them will serve as the clock and the other one must be a slave and listen to the clock.
So, after mulling over this for some time and going off of the suggestion jolivier so generously shared with us, I realized that there exists a method called onChronometerTick which is called every time there is chronometer tick (every second, in this case). So, I thought of subtracting 1000 milliseconds from the counter every time the method is called and update the timer display accordingly. I got rid of the Android timer piece (CountDownTimer) completely. I figured this would be a nice way to have both displays update at the same time. It's also a simple implementation of a timer.
I'm happy to report that it seems to work well. Both the timer and chronometer displays indeed update at the same time. So, the original question looks like it's answered. Unfortunately, I ran into an off-by-two error on the timer front that I fixed with an ugly hack. I'm posting what I have so far. Any suggestions on how to fix the hack or improve the code are welcome. Note that I have commented the code to try to make it easy to understand what's been done.
Edit for bug: One more thing I noticed is that after around 10 minutes or so the chronometer and timer are off by one second. More precisely, the timer is behind the chronometer by one second. Not yet sure why this happens.
private boolean mStartPressedOnce = false;
long mTimeWhenStopped = 0;
Chronometer mChronometer;
long millisUntilFinished = 0;
boolean firstPassOver = false;
int counter = 0;
...
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartButton:
// Perform some initialization for the chronometer depending
// on the button press
if (mStartPressedOnce == false) {
mChronometer.setBase(SystemClock.elapsedRealtime());
} else {
mChronometer.setBase(SystemClock.elapsedRealtime() + mTimeWhenStopped);
}
// Fire up the chronometer
mChronometer.start();
break;
case R.id.ResetButton:
// Reset the chronometer
mChronometer.setBase(SystemClock.elapsedRealtime());
mTimeWhenStopped = 0;
break;
case case R.id.StopButton:
mStartPressedOnce = true;
// Stop the chronometer
mTimeWhenStopped = mChronometer.getBase() - SystemClock.elapsedRealtime();
mChronometer.stop();
break;
}
...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.stop_watch);
mChronometer = (Chronometer) findViewById(R.id.StopWatchTextView);
// Initialize the number of milliseconds before the timer expires (
// set the timer) - in this case to 46 seconds
millisUntilFinished = 46000;
// Display how many seconds remain before the timer expires
((TextView) findViewById(R.id.CountDownTimerTextView)).setText(hours
+ ":" + minutes + ":" + millisUntilFinished / 1000);
// In line with the suggestion provided by jolivier - make the timer
// the slave and update its display every time the chronometer
// ticks
mChronometer
.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() {
#Override
public void onChronometerTick(Chronometer chronometer) {
// Update the display for the chronometer
CharSequence text = chronometer.getText();
chronometer.setText(text);
// Update the display for the timer
// !!! BUG !!!
// Looks like onChronometerTick is called at the 0th second
// and this causes an off by two error if a count down timer
// is being implemented. Fixed it with this hack. There's gotta
// be a more elegant solution, though.
if(counter >= 2) {
millisUntilFinished1 = millisUntilFinished1 - 1000;
counter = 2;
}
counter++;
if (millisUntilFinished >= 0) {
long seconds = (long) (millisUntilFinished / 1000);
long minutes = (long) ((millisUntilFinished / 1000) / 60);
long hours = (long) (((millisUntilFinished / 1000) / 60) / 60);
// Do some formatting to make seconds, minutes and hours look pretty
// Update the timer TextView
((TextView) findViewById(R.id.CountDownTimerTextView))
.setText(hours + ":" + minutes + ":"
+ seconds);
}
}
});
// Other code
...
}

Android Countdown Timer crash and wrong values

I have an app that tells me how many hours to go at school ( :P ). I have a textview with the text Time: ( Hours to go ). This should be 6.2 but it is 6 at the moment, then when I click start it says School finished instantly and then crashes. Oh, I want to show the time in hours, like this ( 6.2 = 6 hours 20 minutes ).
What could be the problem?
Code startActivity:
package com.nieeli.timer;
import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class startActivity extends Activity implements OnClickListener {
private static final String tag = "Main";
private MalibuCountDownTimer countDownTimer;
private long timeElapsed;
private boolean timerHasStarted = false;
private Button startB;
private TextView text;
private TextView timeElapsedView;
private final long startTime = 22500000/3600000;
private final long interval = 1/100;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
startB = (Button) this.findViewById(R.id.button);
startB.setOnClickListener(this);
text = (TextView) this.findViewById(R.id.timer);
timeElapsedView = (TextView) this.findViewById(R.id.timeElapsed);
countDownTimer = new MalibuCountDownTimer(startTime, interval);
text.setText(text.getText() + String.valueOf(startTime));
}
public void onClick(View v) {
if (!timerHasStarted) {
countDownTimer.start();
timerHasStarted = true;
startB.setText("Start");
} else {
countDownTimer.cancel();
timerHasStarted = false;
startB.setText("Stop and Reset");
}
}
// CountDownTimer class
public class MalibuCountDownTimer extends CountDownTimer {
public MalibuCountDownTimer(long startTime, long interval) {
super(startTime, interval);
}
#Override
public void onFinish() {
text.setText("School Finished!");
timeElapsedView.setText("Time Passed: "
+ String.valueOf(startTime));
}
#Override
public void onTick(long millisUntilFinished) {
text.setText("Time remain:" + millisUntilFinished);
timeElapsed = startTime - millisUntilFinished;
timeElapsedView.setText("Time Left: "
+ String.valueOf(timeElapsed));
}
}
}
Haven't made any changes in manifest or layout.
Thanks :D
First, remember that long is an integer type.
private final long interval = 1/100;
Is not equivalent to 0.01; I believe it would work out to zero. Also, this is how often you want your onTick to be called; I believe you're trying to say you want to be called every 0.01 milliseconds, which seems excessive. Perhaps you meant every 100 milliseconds (ten times a second)?
You may also want to swap the startB.setText lines so that when it is stopped it shows "Start" and when it is started it shows "stopped"
You want to convert milliseconds to hours and minutes? You could try something like this:
long totalMinutes = millis / 1000 / 60;
long hours = millis / 60;
long minutes = totalMinutes - (hours * 60);
EDIT:
There are two main approaches if you want your countdown number (6.2 representing 6 hours and 20 minutes) to decrease by .1 every ten minutes.
The first option is to set "interval" to 10 * 60 * 1000 (the number of milliseconds in ten minutes). Then in onTick, take a float number (10.0 or whatever) and decrease by 0.1. That may be a quick and dirty method, if it even works, but it's not ideal.
The second is to do what I had described above. Then when you have the number of hours and number of minutes:
float timeRemaining = (float) hours + (float) minutes / 100
use String.format to display it in the textview. Something like
String.format("%.1f%n", timeRemaining)
to show it with only one decimal point.
I didn't actually try the code--but off the top of my head, this should give you some tips to steer you in the right direction.

Categories

Resources