Skip to content

Instantly share code, notes, and snippets.

@yukuku
Last active October 29, 2015 08:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yukuku/7a3361fb016e07dd60a5 to your computer and use it in GitHub Desktop.
Save yukuku/7a3361fb016e07dd60a5 to your computer and use it in GitHub Desktop.
Debouncer for Android: Delays processing of a payload, and if another payload is submitted afterwards, the earlier ones are not processed nor delivered any more.
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Delays processing of a payload, and if another payload is submitted
* afterwards, the earlier ones are not processed nor delivered any more.
* @param <RequestType>
* @param <ResultType>
*/
public abstract class Debouncer<RequestType, ResultType> {
static final String TAG = Debouncer.class.getSimpleName();
public static final int MSG_ON_RESULT = 1;
private final DebounceHandler handler;
private final long defaultDelay;
final ScheduledExecutorService sched = Executors.newSingleThreadScheduledExecutor();
final AtomicInteger serial = new AtomicInteger();
static class DebounceHandler<RequestType, ResultType> extends Handler {
private final WeakReference<Debouncer<RequestType, ResultType>> ref;
public DebounceHandler(final Debouncer<RequestType, ResultType> debouncer) {
this.ref = new WeakReference<>(debouncer);
}
@Override
public void handleMessage(final Message msg) {
final Debouncer<RequestType, ResultType> debouncer = this.ref.get();
if (debouncer == null) return;
// check again one more time
if (isOutdated(3, msg.arg1, debouncer.serial.get())) return;
//noinspection unchecked
debouncer.onResult((ResultType) msg.obj);
}
}
static abstract class Task implements Runnable {
public int id;
public Task(final int id) {
this.id = id;
}
}
/**
* Call this on the main thread.
* @param defaultDelay The default delay in ms before {@link #process(RequestType)} is performed.
*/
public Debouncer(final long defaultDelay) {
this.defaultDelay = defaultDelay;
this.handler = new DebounceHandler<>(this);
}
/**
* Schedule processing after the default delay.
* @param payload Payload to be sent to the {@link #process(RequestType)} method.
*/
public void submit(final RequestType payload) {
submit(payload, defaultDelay);
}
/**
* Schedule processing after the specified delay.
* @param payload Payload to be sent to the {@link #process(RequestType)} method.
*/
public void submit(final RequestType payload, final long delay) {
final Task t = new Task(serial.incrementAndGet()) {
@Override
public void run() {
// check if this is still needed
if (isOutdated(1, this.id, serial.get())) return;
final ResultType result = process(payload);
// check again if this is still needed
if (isOutdated(2, this.id, serial.get())) return;
// we are okay, deliver result in main thread
final Message msg = Message.obtain();
msg.what = MSG_ON_RESULT;
msg.arg1 = this.id;
msg.obj = result;
handler.sendMessage(msg);
}
};
sched.schedule(t, delay, TimeUnit.MILLISECONDS);
}
/**
* Called in non-UI thread.
* Override this to process the payload submitted in {@link #submit(RequestType)}.
*/
public abstract ResultType process(final RequestType payload);
/**
* Called in the UI thread.
* Override this to receive the process result and e.g. update UI.
*/
public abstract void onResult(final ResultType result);
static boolean isOutdated(int phase, int this_id, int current_id) {
if (this_id == current_id) return false;
switch (phase) {
case 1:
Log.d(TAG, "outdated task (" + this_id + " < " + current_id + ") found before process");
break;
case 2:
Log.d(TAG, "outdated task (" + this_id + " < " + current_id + ") found after process");
break;
case 3:
Log.d(TAG, "outdated task (" + this_id + " < " + current_id + ") found before onResult");
break;
}
return true;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment