Skip to content

Instantly share code, notes, and snippets.

@Bramengton
Created November 3, 2021 20:30
Show Gist options
  • Save Bramengton/ed853d9aebb20820430128d6e975c4b7 to your computer and use it in GitHub Desktop.
Save Bramengton/ed853d9aebb20820430128d6e975c4b7 to your computer and use it in GitHub Desktop.
CountDownTimer - a different look at the obvious!
public Ticker startTimerCheckFrameDelay(){
return new Ticker("TimerCheckFrameDelay") {
@Override
public void onFinish() {
if(BuildConfig.DEBUG) Timber.tag(TAG).i("finally we did it!");
}
@Override
public void onTick(long millisLeft) {
if(BuildConfig.DEBUG) Timber.tag(TAG).i("Ticker left - %s", millisLeft);
}
};
}
mTicker.reStart(TimeUnit.SECONDS.toMillis(15), TimeUnit.SECONDS.toMillis(1)); - every 15 seconds, count down 1 second before calling onFinish.
mTicker.onPause() - Pause
mTicker.onResume() - Resume
mTicker.onStop() - Stop
mTicker.close() - Close
mTicker.isActive() - check is active.
package eld.trackensure.utilities;
import android.os.SystemClock;
import java.util.concurrent.TimeUnit;
import eld.trackensure.BuildConfig;
import timber.log.Timber;
public abstract class Ticker implements Runnable, AutoCloseable {
private static final String TAG = Ticker.class.getSimpleName();
public abstract void onFinish();
public abstract void onTick(long millisLeft);
// private volatile boolean stop = false;
private final Thread mThread;
private final Object mLock;
private volatile boolean mPause;
private volatile boolean mStop;
private volatile boolean mFinish;
private final String mName;
/**
* Millis since epoch when alarm should stop.
*/
private volatile long mMillisInFuture;
/**
* The interval in millis that the user receives callbacks
*/
private volatile long mCountdownInterval;
private volatile long mStopTimeInFuture;
public Ticker() {
mName = "CustomTickerTimer";
this.mLock = new Object();
mThread = new Thread(this, String.format("%s-%s", mName, System.currentTimeMillis()));
}
public Ticker(String tag) {
mName = tag;
this.mLock = new Object();
mThread = new Thread(this, String.format("%s-%s", mName, System.currentTimeMillis()));
}
// public final Ticker start() {
//// stop = false;
//
// mPause = false;
// mFinish = false;
// mStop = false;
//
// if (mMillisInFuture <= 0) {
// onFinish();
// }else {
// mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
// if(mThread!=null) mThread.start();
// }
// return this;
// }
public final Ticker start(long millisInFuture, long countDownInterval) {
log(String.format("START for interval: %s with step: %s", millisInFuture, countDownInterval));
// stop = false;
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
mPause = false;
mFinish = false;
mStop = false;
if (mMillisInFuture <= 0) {
onFinish();
}else {
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
if(mThread!=null) mThread.start();
}
return this;
}
@Override
public void run() {
try {
while (!mStop || !Thread.interrupted()) {
final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
if (millisLeft <= 0) {
onFinish();
return;
} else {
long lastTickStart = SystemClock.elapsedRealtime();
if(!mStop)onTick(millisLeft);
// take into account user's onTick taking time to execute
long lastTickDuration = SystemClock.elapsedRealtime() - lastTickStart;
long delay;
if (millisLeft < mCountdownInterval) {
// just delay until done
delay = millisLeft - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, trigger onFinish without delay
if (delay < 0) delay = 0;
} else {
delay = mCountdownInterval - lastTickDuration;
// special case: user's onTick took more than interval to
// complete, skip to next interval
while (delay < 0) delay += mCountdownInterval;
}
if(delay>0) TimeUnit.MILLISECONDS.sleep(delay);
else {
onFinish();
return;
}
}
synchronized (mLock) {
boolean inPause = false;
long lastTickStart = SystemClock.elapsedRealtime();
while (mPause) {
try {
mLock.wait();
}
catch (InterruptedException e) {
return;
}
inPause = true;
}
if(mStop)break;
if(inPause) mStopTimeInFuture += SystemClock.elapsedRealtime() - lastTickStart;
}
}
} catch (InterruptedException ignored) {
Timber.tag(TAG).e(ignored);
}
}
@Override
public void close() {
synchronized (mLock) {
log("close");
mPause = false;
mStop = true;
}
if(isActive()) mThread.interrupt();
}
/**
* Call this on pause.
*/
public void onPause() {
synchronized (mLock) {
log("onPause");
mPause = true;
mStop=false;
}
}
/**
* Just stop.
*/
public void onStop() {
synchronized (mLock) {
log("onStop");
mPause = false;
mStop = true;
}
}
/**
* Call this on resume.
*/
public void onResume() {
synchronized (mLock) {
log("onResume");
mPause = false;
mStop=false;
mFinish = false;
mLock.notifyAll();
}
}
/**
* Call this for restar - every N seconds, count down M second before calling onFinish.
*/
public void reStart(long millisInFuture, long countDownInterval) {
log(String.format("reStart for interval: %s with step: %s", millisInFuture, countDownInterval));
synchronized (mLock) {
// stop = false;
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
mPause = false;
mFinish = false;
mStop = false;
if (mMillisInFuture <= 0) {
onFinish();
}else {
mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
}
mLock.notifyAll();
}
}
public boolean isActive(){
return mThread!=null && !mThread.isInterrupted() && mThread.isAlive();
}
private void log(String text){
if(BuildConfig.DEBUG) Timber.tag(TAG).i("Ticker name[%s] - %s", mName!=null ? mName : "Name - Ticker", text);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment