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


非即时任务的诉求,将会使我们现有 后台任务库 的 复杂度呈指数级提升 ,但这是 必要 的,原因如下:
首先,上文中 定期任务 也属于非即时任务的范畴,虽然该任务是立即执行并等待的,但实际上其真正的业务逻辑,仍是未来的某个时间点触发;其次,也是最重要的一点,作为一个健壮的后台任务库,和 即时任务 相比,对 非即时任务 提供支持的优先级要高得多 。
——这似乎违反直觉,在日常开发中, 即时任务 似乎才是主流,但我们忽视了一个事实,资源并非无限 。
在文章的开始,我们构建了基本的线程调度能力,并创建了一个数量为 4 的线程池 。但随着业务复杂度的提升,线程池可能会同时执行多个任务,这意味着部分晚入列、或优先级低的任务,会经常性等待前面的任务执行完毕 。
严格意义上讲,此时,即时任务都转化为了非即时任务,再进一步抽象,所有即时任务都是非即时任务 。
万物皆可异步,是异步编程的一个经典概念,该思想在 、 或 协程 中都有体现 。
即时任务被延时执行是合理的吗?对于后台任务而言,是非常合理的,如果开发者有明确的诉求, 必须 且 立即 执行某段业务逻辑,那么就不应该用 后台任务库,而是直接在内存中调用这块代码 。
2.持久化存储
当后台任务可能被延时执行,思考下一个问题,如何保证任务执行的可靠性?
终极解决方案必然是 后台任务持久化,通过本地文件或者数据库存储后,即使进程被用户杀掉或系统回收,在合适的时机,APP总是能够将任务恢复并重建 。
考虑到安全性, 最终选择使用了 Room 数据库,并且设计维护了一个非常复杂的 ,简单罗列下核心的表:
@Entity(indices = [Index(value = http://www.kingceram.com/post/["schedule_requested_at"]), Index(value = http://www.kingceram.com/post/["last_enqueue_time"])])data class WorkSpec(// 1.任务执行的状态,ENQUEUED/RUNNING/SUCCEEDED/FAILED/CANCELLED@JvmField@ColumnInfo(name = "state")var state: WorkInfo.State = WorkInfo.State.ENQUEUED,// 2.Worker的类名,便于反射和日志打印@JvmField@ColumnInfo(name = "worker_class_name")var workerClassName: String,// 3.输入参数@JvmField@ColumnInfo(name = "input")var input: Data = http://www.kingceram.com/post/Data.EMPTY,// 4.输出参数@JvmField@ColumnInfo(name ="output")var output: Data = http://www.kingceram.com/post/Data.EMPTY,// 5.定时任务@JvmField@ColumnInfo(name ="initial_delay")var initialDelay: Long = 0,// 6.定期任务@JvmField@ColumnInfo(name = "interval_duration")var intervalDuration: Long = 0,// 7.约束关系@JvmField@Embeddedvar constraints: Constraints = Constraints.NONE,// ...)
设计好字段后,接下来我们设计其操作类:
@Daointerface WorkSpecDao {// ...@Insert(onConflict = OnConflictStrategy.IGNORE)fun insertWorkSpec(workSpec: WorkSpec)@Query("SELECT * FROM workspec WHERE id=:id")fun getWorkSpec(id: String): WorkSpec?@Query("SELECT id FROM workspec")fun getAllWorkSpecIds(): List@Query("UPDATE workspec SET state=:state WHERE id=:id")fun setState(state: WorkInfo.State, id: String): Int@Query("SELECT state FROM workspec WHERE id=:id")fun getState(id: String): WorkInfo.State?// ...}
细心的读者会发现, 的设计中除了声明常规的 、Query、 等,并没有类型的操作 。
读者经过认真考虑后,可得该设计是合理的——由于有 () 可更新任务的状态,已完成或取消的工作无需删除,而是通过 SQL 语句,灵活分类按需获取,如:
@Daointerface WorkSpecDao {// ...// 获取全部执行中的任务@Query("SELECT * FROM workspec WHERE state=RUNNING")fun getRunningWork(): List