Skip to content

Instantly share code, notes, and snippets.

@pcqpcq
Created February 20, 2016 08:47
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 pcqpcq/a9a710b6bbcf7181a307 to your computer and use it in GitHub Desktop.
Save pcqpcq/a9a710b6bbcf7181a307 to your computer and use it in GitHub Desktop.
package com.oasisfeng.greenify.utils;
import android.app.AlarmManager;
import android.app.PendingIntent;
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) {}
mAlarmManager.cancel(mAlarmIntent);
if (run) {
Log.e(TAG, "Handler is not canceled for task already run.");
return;
}
run = true;
if (DEBUG) Log.v(TAG, "Handler to run " + TimerTask.this);
mTaskHandler.removeCallbacks(TimerTask.this);
mTaskHandler.post(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);
mContext.unregisterReceiver(mAlarmReceiver);
if (run) {
Log.e(TAG, "Alarm is not canceled for task already run.");
return;
}
run = true;
mTaskHandler.removeCallbacks(TimerTask.this);
mTaskHandler.post(TimerTask.this);
}};
}
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.removeCallbacks(task.mHandlerTask);
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);
task.run = false;
}
public void cancel(final TimerTask task) {
mAlarmManager.cancel(task.mAlarmIntent);
mTimerHandler.removeCallbacks(task.mHandlerTask);
}
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