Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
package com.oasisfeng.greenify.utils;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
* Use preferable handler to schedule a task and alarm to keep it as timely as possible in case of device sleep.
* Created by Oasis on 2015/11/6.
public class HybridTimer {
private static final int KMaxHandlerDelay = 30_000;
private static final int KExtraDelayForAlarm = 5_000; // To help more efficient handler to trigger first.
private static final String KActionTimerTaskPrefix = "com.oasisfeng.greenify.TIMER-";
private static final boolean DEBUG = Boolean.TRUE;
public abstract class TimerTask implements Runnable {
private boolean run = false;
private long mScheduledElapsed;
private PendingIntent mAlarmIntent;
public long getScheduledElapsed() {
return mScheduledElapsed;
private final Runnable mHandlerTask = new Runnable() { @Override public void run() {
final long now = SystemClock.elapsedRealtime();
if (now < mScheduledElapsed) { // Continue the spin timer
if (DEBUG) Log.v(TAG, "Continue to spin for remaining " + (mScheduledElapsed - now) / 1000 + "s");
mTimerHandler.postDelayed(this, Math.min(mScheduledElapsed - now, KMaxHandlerDelay));
} else {
try { mContext.unregisterReceiver(mAlarmReceiver); } catch (final RuntimeException ignored) {}
if (run) {
Log.e(TAG, "Handler is not canceled for task already run.");
run = true;
if (DEBUG) Log.v(TAG, "Handler to run " + TimerTask.this);
private final BroadcastReceiver mAlarmReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) {
if (DEBUG) Log.d(TAG, "Alarm fired for " + TimerTask.this);
if (run) {
Log.e(TAG, "Alarm is not canceled for task already run.");
run = true;
public HybridTimer(final Context context) {
this(context, new Handler());
public HybridTimer(final Context context, final Handler task_handler) {
mContext = context;
mTimerHandler = new Handler(Looper.getMainLooper()); // Use main looper to avoid concurrent issues
mTaskHandler = task_handler;
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
* Like {@link Handler#postDelayed(Runnable, long)}, but use {@link SystemClock#elapsedRealtime()} instead.
* No wake-up, no additional delay in sleep.
public void runDelayed(final long delay_ms, final TimerTask task) {
if (task == null) throw new IllegalArgumentException("task is null");
final long handler_delay = Math.min(delay_ms, KMaxHandlerDelay);
task.mScheduledElapsed = SystemClock.elapsedRealtime() + delay_ms;
mTimerHandler.postDelayed(task.mHandlerTask, handler_delay);
final int hash = System.identityHashCode(task);
final IntentFilter filter = new IntentFilter(KActionTimerTaskPrefix + hash); // TODO: Restrict source?
mContext.registerReceiver(task.mAlarmReceiver, filter);
if (task.mAlarmIntent == null) {
final Intent intent = new Intent(KActionTimerTaskPrefix + hash).setPackage(mContext.getPackageName());
task.mAlarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
final long scheduled_millis = System.currentTimeMillis() + delay_ms + KExtraDelayForAlarm;
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, scheduled_millis, task.mAlarmIntent); = false;
public void cancel(final TimerTask task) {
private final Context mContext;
private final AlarmManager mAlarmManager;
private final Handler mTimerHandler;
private final Handler mTaskHandler;
private static final String TAG = "HybridTimer";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment