Reputation: 13551
I looked at ScheduledExecutorService only loops once
But it doesn't seem to address my issue.
I have a custom timer and when I hit start
it is supposed to fire a callback every second:
/**
* Starts the timer. If the timer was already running, this call is ignored.
*/
public void start()
{
if (_isRunning)
{
return;
}
_isRunning = true;
// Schedules repeated task that fires each time at the interval given
Log.d("Timer", "Starting execution");
_future = _execService.scheduleWithFixedDelay(new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Fire");
_elapsedTime += PausableTimer.this._interval;
// If time has exceeded duration, stop timer
if (_duration > 0 && _elapsedTime >= _duration)
{
Log.d("Timer", "Finish");
onFinish();
_future.cancel(false);
}
}
}, 0, _interval, TimeUnit.MILLISECONDS);
}
Here is how I invoke the timer:
_timer = new PausableTimer(1000, PausableTimer.DURATION_INFINITY);
_timer.start();
This is creating a timer that fires in 1000 millisecond intervals for inifity.
However my logs only show it firing once. The "Finish" log is not appearing so I know it hasn't ended.
Do you know why this is only firing once?
Update
/**
* Timer that can play and pause.
*/
public class PausableTimer extends Timer
{
public static final int DURATION_INFINITY = -1;
private Callbacks.VoidCallback _onTick;
private Callbacks.VoidCallback _onFinish;
private volatile boolean _isRunning = false;
private long _interval;
private long _elapsedTime;
private long _duration;
private ScheduledExecutorService _execService = Executors.newSingleThreadScheduledExecutor();
private Future<?> _future = null;
/**
* Creates a pausable timer.
* @param interval The time gap between each tick in millis.
* @param duration The period in millis for which the timer should run.
* Set it to {@code Timer#DURATION_INFINITY} if the timer has to run indefinitely.
*/
public PausableTimer(long interval, long duration)
{
_interval = interval;
_duration = duration;
_elapsedTime = 0;
_isRunning = false;
}
/// LIFE CYCLE
/**
* Starts the timer. If the timer was already running, this call is ignored.
*/
public void start()
{
if (_isRunning)
{
Log.d("Timer", "already started running");
return;
}
_isRunning = true;
// Schedules repeated task that fires each time at the interval given
Log.d("Timer", "Starting execution");
_future = _execService.scheduleWithFixedDelay(new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Fire");
onTick();
_elapsedTime += PausableTimer.this._interval;
// If time has exceeded duration, stop timer
if (_duration > 0 && _elapsedTime >= _duration)
{
Log.d("Timer", "Finish");
onFinish();
_future.cancel(false);
}
}
}, 0, PausableTimer.this._interval, TimeUnit.MILLISECONDS);
}
/**
* Pauses the timer.
*/
public void pause()
{
if(!_isRunning)
{
return;
}
_future.cancel(false);
_isRunning = false;
}
/**
* Resumes the timer if it was paused, else starts the timer.
*/
public void resume()
{
start();
}
/**
* Called periodically with the _interval set as the delay between subsequent calls.
* Fires tick callback if set.
*/
private void onTick()
{
if (_onTick != null)
{
_onTick.callback();
}
}
/**
* Called once the timer has run for the specified _duration.
* If the _duration was set as infinity, then this method is never called.
* Fires finished callback if set.
*/
protected void onFinish()
{
if (_onFinish != null)
{
_onFinish.callback();
}
_isRunning = false;
}
/**
* Stops the timer. If the timer is not running, then this call does nothing.
*/
public void cancel()
{
pause();
_elapsedTime = 0;
}
/// GETTERS
/**
* @return the elapsed time (in millis) since the start of the timer.
*/
public long getElapsedTime()
{
return _elapsedTime;
}
/**
* @return the time remaining (in millis) for the timer to stop.
* If the _duration was set to {@code Timer#DURATION_INFINITY}, then -1 is returned.
*/
public long getRemainingTime()
{
if (_duration <= PausableTimer.DURATION_INFINITY)
{
return PausableTimer.DURATION_INFINITY;
}
return _duration - _elapsedTime;
}
/// BINDERS
/**
* @return true if the timer is currently running, and false otherwise.
*/
public boolean isRunning()
{
return _isRunning;
}
/**
* Binds onTick callback.
*/
public void bindOnTick(Callbacks.VoidCallback callback)
{
_onTick = callback;
}
/**
* Binds onFinish callback.
* @param callback
*/
public void bindOnFinish(Callbacks.VoidCallback callback)
{
_onFinish = callback;
}
}
Here is a typical log. Basically what is happening is I start it up, wait 10-15 seconds, then start it again. It is supposed to fire every second. However it fires once or twice, then doesn't fire until its restarted.
D/Timer: Reset timer
D/Timer: Starting execution
D/Timer: Fire
D/Timer: Reset timer
D/Timer: Starting execution
D/Timer: Fire
D/Timer: Fire
D/Timer: Reset timer
D/Timer: Starting execution
To be extra clear, here is some invoking code I use:
private void restartTimer()
{
if (_timer != null)
{
_timer.cancel();
}
Log.d("Timer", "Reset timer");
_timer = new PausableTimer(1000, PausableTimer.DURATION_INFINITY);
_timer.bindOnTick(new Callbacks.VoidCallback()
{
@Override
public void callback()
{
decrementTimeRemaining();
}
});
_timer.start();
}
SOLUTION
After figuring out that the onTick()
call in my run()
was causing the thread to get stopped, I solved this by dispatching the onTick()
call to the main thread:
_future = _execService.scheduleWithFixedDelay(new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Run begin");
Runnable task = new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Main thread tick");
PausableTimer.this.onTick();
}
};
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(task);
_elapsedTime += PausableTimer.this._interval;
Log.d("Timer", "Run middle");
// If time has exceeded duration, stop timer
if (_duration > 0 && _elapsedTime >= _duration)
{
Log.d("Timer", "Finish");
onFinish();
_future.cancel(false);
}
}
}, 0, PausableTimer.this._interval, TimeUnit.MILLISECONDS);
Upvotes: 2
Views: 752
Reputation: 13551
Very interesting cause of the problem (to me at least).
This issue was my onTick()
callback that I was firing:
I noticed something very odd with my logs. The run()
logs before the onTick()
were firing, and the ones below it where not:
Log.d("Timer", "Starting execution");
_future = _execService.scheduleWithFixedDelay(new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Run begin"); // fires
onTick(); // when I remove this, all below logs fire!
_elapsedTime += PausableTimer.this._interval;
Log.d("Timer", "Run middle"); // didn't fire
Log.d("Timer", "Elapsed time " + _elapsedTime); // didn't fire
Log.d("Timer", "Duration " + _duration); // didn't fire
// If time has exceeded duration, stop timer
if (_duration > 0 && _elapsedTime >= _duration)
{
Log.d("Timer", "Finish"); // didn't fire
onFinish();
_future.cancel(false);
}
Log.d("Timer", "Run End"); // didn't fire
}
}, 0, PausableTimer.this._interval, TimeUnit.MILLISECONDS);
When I removed the onTick()
all the logs fired.
I suspect something gets mucked up when I try to go on the main thread from here with the onTick()
.
I'm not exactly sure yet, but this is the reason that the timer was only firing once, the onTick()
call mucks it up.
I will continue to investigate further and am open to input you might have on this.
Solution Dispatched the callback on the main thread:
_future = _execService.scheduleWithFixedDelay(new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Run begin");
Runnable task = new Runnable()
{
@Override
public void run()
{
Log.d("Timer", "Main thread tick");
PausableTimer.this.onTick();
}
};
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(task);
_elapsedTime += PausableTimer.this._interval;
Log.d("Timer", "Run middle");
// If time has exceeded duration, stop timer
if (_duration > 0 && _elapsedTime >= _duration)
{
Log.d("Timer", "Finish");
onFinish();
_future.cancel(false);
}
}
}, 0, PausableTimer.this._interval, TimeUnit.MILLISECONDS);
Upvotes: 1
Reputation: 5443
1) You set _isRunning = true;
and never reset this to false
;
2) Where are you setting _duration
? If this is 0
, your timer will never finish.
3) You're using _interval
and PausableTimer.this._interval
: do you mean to do that?
Upvotes: 0