社区说|浅谈 WorkManager 的设计与实现:系统概述( 二 )


2.任务队列和串行化
接下来我们设计一个任务队列,保证可以不断接收新的任务,并及时分发给空闲的后台线程执行:
public class ArrayDeque extends AbstractCollection implements Deque {public boolean add(E e);public boolean offer(E e);public E remove();public E poll();}
【社区说|浅谈 WorkManager 的设计与实现:系统概述】很经典的一个队列接口, 也并未单独造轮子,而是借用了 Java 的类 。这是一个非常经典的实现类,在诸多大名鼎鼎的工具库中都有它的身影(比如),限于篇幅不展开,感兴趣的读者可自行查看源码 。
读者可预见到的是,后台任务的创建和添加的时机是无法控制的,加上设计之初都并未考虑线程同步,因此目前的设计将会有 线程安全问题。
因此,后台任务的入列和执行,必须借用一个新的角色保证串行,就像单线程一样 。
实现方案非常简单,通过代理和加锁,提供一个的包装类即可:
public class SerialExecutorImpl implements SerialExecutor {// 1. 任务队列private final ArrayDeque mTasks;// 2. 后台线程的Executor,即上文中数量为4的线程池private final Executor mBackgroundExecutor;// 3.锁对象@GuardedBy("mLock")private Runnable mActive;@Overridepublic void execute(@NonNull Runnable command) {// 不断地入列、出列和任务执行synchronized (mLock) {mTasks.add(new Task(this, command));if (mActive == null) {if ((mActive = mTasks.poll()) != null) {mExecutor.execute(mActive);}}}}}
3.任务状态、类型和结果回调
下一步,我们对所关注的任务状态进行一个罗列,不难得出,我们关注的状态大致有:任务开始()、任务执行中()、任务成功()、任务失败()、任务取消()等几种:
请注意,上文中的日志上报我们定义成了 一次性工作,即只执行一次,执行结束即永久结束 。
实际上,一次性工作 并不能涵盖所有的业务场景,举例来说,作为一个视频类的 APP,我们需 定期 对用户的播放进度进行一次记录或上报,保证用户即使杀掉APP,下次仍在最后记录的播放位置继续播放 。
这里我们引入了 定期任务 的概念,它只有一个终止状态。这是因为定期工作永远不会结束 。每次运行后,无论结果如何,系统都会重新对其进行调度 。
最后,我们将后台任务的执行抽象为一个接口,开发者实现接口,实现具体后台业务,如日志上报等,并返回具体的结果:
public abstract class Worker {// 后台任务的具体实现,`WorkerManager`内部进行了线程调度,执行在工作线程.@WorkerThreadpublic abstract @NonNull Result doWork();}public abstract static class Result {@NonNullpublic static Result success() {return new Success();}@NonNullpublic static Result retry() {return new Retry();}@NonNullpublic static Result failure() {return new Failure();}}
持久化
目前为止,我们实现了一个简化版、基于内存中队列的后台任务库,接下来我们将针对 后台任务持久化 的必要性,进行进一步的讨论 。
1.非即时任务
第一步我们需要引入 非即时任务 的概念 。
作为互联网的从业者,读者对类似的提示弹窗应该并不陌生:
您手机/PC的新版本已下载完毕:「立即安装」、「定时安装」、「稍后提醒我」
显然,这是一个常见的功能,用户选择后,应用或系统的后台需在未来的某个时间点,升级或提醒用户 。如果和前文中的任务类型进行区分,前者我们可以归纳为 即时任务,后者则可称之为 延时任务 或 非即时任务 。