【Android开发】基于时间广播的定时框架

 鉴于Android中的AlarmManager、WorkManager的执行时间不精确原因,便基于时间广播对定时事件管理进行了一个封装实现

以下是一款简单、实用的定时事件管理框架,此处实现为基于时间广播的“分钟”(最大误差为1分钟),此处仅提供了24小时内的定时事件管理

该定时框架支持24小时内的定时事件循环事件优先级事件推迟事件,例如可以做到:

  1. 18:18分执行一次ScheduleTask
  2. 每日19:00执行一次ScheduleTask(重复间隔为24 * 60分钟、重复次数为永久循环)
  3. 推迟55分钟执行一次ScheduleTask
  4. 推迟8分钟执行第一次ScheduleTask,随后每10分钟执行一次该Task
  5. 从当前时刻开始每3分钟执行ScheduleTask,无限循环
  6. 13:14分执行ScheduleTask,后续每隔20分钟执行,允许重复5次
  7. 允许同一时刻有多个任务依次按照优先级大小执行
  8. 允许同一时刻先执行的任务决定是否要继续执行后续的任务

注意:在本框架中,将重复次数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();
    }
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值