Created
February 20, 2016 08:47
-
-
Save pcqpcq/a9a710b6bbcf7181a307 to your computer and use it in GitHub Desktop.
here: http://pastebin.com/QppdBX3D, by Oasis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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