鉴于Android中的AlarmManager、WorkManager的执行时间不精确原因,便基于时间广播对定时事件管理进行了一个封装实现
以下是一款简单、实用的定时事件管理框架,此处实现为基于时间广播的“分钟”(最大误差为1分钟),此处仅提供了24小时内的定时事件管理
该定时框架支持24小时内的定时事件、循环事件、优先级事件、推迟事件,例如可以做到:
- 18:18分执行一次ScheduleTask
- 每日19:00执行一次ScheduleTask(重复间隔为24 * 60分钟、重复次数为永久循环)
- 推迟55分钟执行一次ScheduleTask
- 推迟8分钟执行第一次ScheduleTask,随后每10分钟执行一次该Task
- 从当前时刻开始每3分钟执行ScheduleTask,无限循环
- 13:14分执行ScheduleTask,后续每隔20分钟执行,允许重复5次
- 允许同一时刻有多个任务依次按照优先级大小执行
- 允许同一时刻先执行的任务决定是否要继续执行后续的任务
注意:在本框架中,将重复次数0定义为“永久循环重复”
您也可以在此基础上随意扩展、修改,最好加上相应的多线程情况下的处理
若需要使用该框架,您只需要做以下几点:
您需要自主实现一个接收时间广播的广播接收器,并可以配置相应的处理服务,在服务中调用该框架的ScheduleEventManager#startWork()
若您需要创建定时任务,您只需要调用ScheduleEventManager#addScheduleEvent()
若您需要移除定时任务,您只需要调用ScheduleEventManager#removeScheduleEvent()
/**
* All user's scheduled task should implements this interface.
* All scheduled task must added to the {@link ScheduleEventManager}.
*
* @author zyk
*/
@FunctionalInterface
public interface ScheduleTask {
/**
* Execute scheduled task.
*
* @return if true, the next scheduled task can be executed; otherwise, all tasks
* after this task will not be executed.
*/
boolean execute();
}
/**
* This manager will manage every scheduled task's id.
*
* @author zyk
*/
/* package */ class IdManager {
private static volatile IdManager sInstance;
// key:id,value:targetTime
private final Map<Long, Integer> mAllId = new HashMap<>(16);
private IdManager() {
}
public static IdManager getInstance() {
if (sInstance == null) {
synchronized (IdManager.class) {
if (sInstance == null) {
sInstance = new IdManager();
}
}
}
return sInstance;
}
public Long createId(int targetTime) {
Random random = new Random(System.currentTimeMillis());
long id = random.nextLong();
// Make sure the id is unique.
while (mAllId.containsKey(id) || id == ScheduleEventConstant.ILLEGAL_ID) {
id = random.nextLong();
}
mAllId.put(id, targetTime);
return id;
}
public Integer deleteId(long id) {
return mAllId.remove(id);
}
public void refreshId(long id, int targetTime) {
mAllId.put(id, targetTime);
}
}
/**
* Event entity.
*
* @author zyk
*/
/* package */ class ScheduleEvent {
private final ScheduleTask task;
public int targetTime; // Time for event to execute.
public int frequency; // Here 0 means FOREVER, others means repeat times.
public int interval; // Interval between this scheduled event.
public int priority; // The higher the priority, the more priority execution.
public long id; // Unique id will be managed by IdManager. The repeat event will reuse their own id.
public ScheduleEvent(@IntRange(from = 0, to = 24 * 60) int targetTime,
@IntRange(from = 0, to = 24 * 60) int frequency,
@IntRange(from = 0, to = 24 * 60) int interval,
@IntRange(from = 0) int priority,
@NonNull ScheduleTask task) {
this.targetTime = targetTime;
this.frequency = frequency;
this.interval = interval;
this.priority = priority;
this.task = task;
// Id will be created once.
this.id = IdManager.getInstance().createId(targetTime);
}
private ScheduleEvent(int targetTime, int frequency, int interval, int priority, ScheduleTask task, long id) {
this.targetTime = targetTime;
this.frequency = frequency;
this.interval = interval;
this.priority = priority;
this.task = task;
// ID will be reused when the event is executed again.
this.id = id;
}
public boolean work() {
return task.execute();
}
public ScheduleEvent next() {
int nextTargetTime = (targetTime + interval) % (24 * 60);
// FOREVER(frequency = 0)events do not need to reduce the frequency value,
// only(frequency = 2,3,4...)events need.
if (frequency != ScheduleEventConstant.FREQUENCY_FOREVER) {
frequency--;
}
IdManager.getInstance().refreshId(id, nextTargetTime);
return new ScheduleEvent(nextTargetTime, frequency, interval, priority, task, id);
}
@NonNull
@Override
public String toString() {
return "targetTime: " + targetTime +
" frequency: " + frequency +
" interval: " + interval +
" priority: " + priority +
" taskClass: " + task.getClass().getSimpleName();
}
}
/**
* Constant for user to use schedule event.
*
* @author zyk
*/
public class ScheduleEventConstant {
// Scheduled event's priority should greater than this value.
public static final int DEFAULT_PRIORITY_LEVEL = 0;
// Scheduled event's frequency(0 means forever. 1 means one time.).
public static final int FREQUENCY_FOREVER = 0;
public static final int FREQUENCY_ONCE = 1;
// Scheduled event's illegal id define.
public static final int ILLEGAL_ID = 0;
}
/**
* The manager will reasonably add or remove related scheduled tasks.
*
* @author zyk
*/
public class ScheduleEventManager {
private static volatile ScheduleEventManager sInstance;
private final Map<Integer, List<ScheduleEvent>> mAllScheduleWork = new HashMap<>(8);
private final IdManager mIdManager = IdManager.getInstance();
private ScheduleEventManager() {
}
public static ScheduleEventManager getInstance() {
if (sInstance == null) {
synchronized (ScheduleEventManager.class) {
if (sInstance == null) {
sInstance = new ScheduleEventManager();
}
}
}
return sInstance;
}
/**
* Add events that should be executed on the exact time point.
* Such as 18:35(targetTime = 18 * 60 + 35).
*
* @param targetTime Exact time point.
* @param frequency The times of this scheduled task can be executed.
* @param interval The interval time at which a scheduled task can be executed.
* @param priority High priority tasks will be executed first.
* @param task Scheduled task.
* @return The id of this scheduled event.
*/
public long addScheduledEvent(int targetTime, int frequency, int interval, int priority, ScheduleTask task) {
return addScheduleEvent(new ScheduleEvent(targetTime, frequency, interval, priority, task));
}
/**
* Add events that should be executed after the specified delay time.
* Such as delay 15 minutes.
*
* @param frequency The times of this scheduled task can be executed.
* @param interval The interval time at which a scheduled task can be executed.
* @param priority High priority tasks will be executed first.
* @param task Scheduled task.
* @return The id of this scheduled event.
*/
public long addDelayedEvent(int delay, int frequency, int interval, int priority, ScheduleTask task) {
Calendar calendar = Calendar.getInstance();
int targetTime = (calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE) + delay) % (24 * 60);
return addScheduleEvent(new ScheduleEvent(targetTime, frequency, interval, priority, task));
}
private long addScheduleEvent(ScheduleEvent work) {
int key = work.targetTime;
if (mAllScheduleWork.containsKey(key)) {
List<ScheduleEvent> target = mAllScheduleWork.get(key);
if (target != null) {
target.add(work);
// Descending order based on priority.
Collections.sort(target, (o1, o2) -> o2.priority - o1.priority);
}
} else {
List<ScheduleEvent> target = new ArrayList<>();
target.add(work);
mAllScheduleWork.put(key, target);
}
return work.id;
}
/**
* Remove the scheduled event by id.
*
* @param id The unique id of the scheduled event.
*/
public void removeScheduleEvent(long id) {
Integer targetTime = mIdManager.deleteId(id);
if (targetTime == null) {
return;
}
List<ScheduleEvent> target = mAllScheduleWork.get(targetTime);
Iterator<ScheduleEvent> iterator = target.iterator();
while (iterator.hasNext()) {
ScheduleEvent event = iterator.next();
if (event.id == id) {
iterator.remove();
break;
}
}
if (target.isEmpty()) {
mAllScheduleWork.remove(targetTime);
}
}
/**
* Start to execute scheduled events.
*
* @param currentTime For trigger the scheduled events which should be executed now.
*/
public void startWork(int currentTime) {
if (!mAllScheduleWork.containsKey(currentTime)) {
return;
}
List<ScheduleEvent> currentEvents = mAllScheduleWork.get(currentTime);
if (currentEvents == null) {
return;
}
// For repeated event.
List<ScheduleEvent> forRepeat = new ArrayList<>();
boolean canContinue = true;
Iterator<ScheduleEvent> iterator = currentEvents.iterator();
while (iterator.hasNext()) {
ScheduleEvent event = iterator.next();
if (canContinue) {
canContinue = event.work();
}
iterator.remove();
mIdManager.deleteId(event.id);
// Pass the scheduled task to the next time point.
if (event.frequency != ScheduleEventConstant.FREQUENCY_ONCE) {
forRepeat.add(event.next());
}
}
for (ScheduleEvent event : forRepeat) {
addScheduleEvent(event);
}
if (currentEvents.isEmpty()) {
mAllScheduleWork.remove(currentTime);
}
}
/**
* Provide information about all scheduled events.
*
* @return All scheduled events' information.
*/
public String information() {
Set<Integer> keys = mAllScheduleWork.keySet();
StringBuilder builder = new StringBuilder();
builder.append("Time work list: \n");
for (Integer key : keys) {
List<ScheduleEvent> list = mAllScheduleWork.get(key);
if (list != null) {
builder.append("time: ").append(key).append(", ");
for (ScheduleEvent event : list) {
builder.append(event.toString()).append('\n');
}
}
}
return builder.toString();
}
}