使用WorkManager调度任务

WorkManager是一个库, 用以将工作入队, 当该工作的约束条件得到满足之后, WorkManager保证它的执行. WorkManager允许观测工作的状态, 并且拥有能力创建复杂的工作链.

WorkManager使用基础的作业分发服务, 仅当如下条件可用时:

  • 当API 23+时, 使用JobScheduler;
  • 当API 14~22时, 如果应用使用了Firebase JobDispatcher和其它的Firebase依赖时, 使用Firebase JobDispatcher; 否则的话, 则使用自定义的AlarmManager + BroadcastReceiver实现.

所有的工作必须有相应的Worker来实现计算. 工作在后台线程执行. WorkManager支持两种类型的工作: OneTimeWorkRequest和PeriodicWorkRequest.

默认情况下, WorkManager在后台线程执行它的操作. 如果你已经在后台线程运行且需要同步调用WorkManager, 使用synchronous()方法访问这些方法.

WorkManager API可以轻易的指定可暂缓的异步任务, 以及这些任务何时运行. 这些API能够让你创建一个任务并把该任务推给WorkManager即刻或者在恰当的时刻去运行. 比如, 某个应用可能时不时地需要从网络下载最新的资源. 使用这些类, 你可以设置一个任务, 选择该任务运行时的恰当条件(例如"只有在设备充电且联网的时候"), 然后把任务推给WorkManager, 当指定的条件满足时, WorkManager便会运行该任务. 即使你的应用被强制退出或者设备进行了重启, 这些任务依然会被保障执行.

备注: 例如, 向服务器上传应用数据, 即使应用退出, 系统依然保障运行这些任务. WorkManager即是为这些任务而设计的. 而对于进程内的后台工作, 在应用的进程结束的时候, 则会被安全地终止, 这些任务并不是WorkManager设计的目的. 而这些这种情况, 推荐使用ThreadPools.

WorkManager基于设备的API和应用的状态来选择恰当的方式来执行任务. 如果WorkManager在应用正在运行的时候执行了你的某条任务, 那么WorkManager会在你的应用进程中开启新的线程来执行这条任务. 而如果你的应用没有在运行, WorkManager则会选择恰当的方式来调度后台任务-根据设备API水平和依赖库, WorkManager可能会选择JobScheduler, Firebase JobDispatcher或者AlarmManager. 你不必自己写逻辑找出设备有哪些能力以找出恰当的API; 正相反, 你只需把任务递交给WorkManager让它选择最佳选项.

此外, WorkManager还提供了些许高级特性. 比如, 你可以设置任务链; 在一些任务完成之后, WorkManager将下一个任务添加进队列以形成链. 你也可以通过观测任务的LiveData来查看它的状态和返回值. 如果你想通过展示UI来显示任务状态, 这也许会有用.

同时, WorkManager已经存在于androidx.arch包中, 但它依然依赖于Android Support Library 27.1并与Arch构件的版本有关联. 拥有AndroidX依赖的WorkManager版本将会在未来发布.

WorkManager的依赖添加方式如下:

 dependencies {
def work_version = "1.0.0-alpha04" implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin // optional - Firebase JobDispatcher support
implementation "android.arch.work:work-firebase:$work_version" // optional - Test helpers
androidTestImplementation "android.arch.work:work-testing:$work_version"
}

相关类和概念

WorkManager API使用了几个不同的类. 某些情况下, 你需要继承某些类.

重要的一些类有:

  • Worker: 指定了你需要执行什么样的任务. WorkManager包含了抽象类Worker. 你需要继承它并在里面执行工作.
  • WorkRequest: 表示一个单独的任务. 最少, WorkRequest对象指定了哪个Worker执行该任务. 然后, 你也可以向WorkRequest添加一些细节, 指定诸如任务在什么条件下执行等这些事情. 每一个WorkRequest都拥有一个自动生成的独一无二的ID. 你可以使用该ID来做一些事情, 例如, 取消已经入队的任务, 或者, 获取任务的状态. WorkRequest是个抽象类, 使用时, 应该使用它的直接子类, OneTimeWorkReqeust或者PeriodicWorkRequest.
  • WorkRequest.Builder: 帮助类, 用来创建WorkRequest对象. 再说一遍, 你需要使用它的子类, OneTimeWorkRequest.Builder或者PeriodicWorkRequest.Builder.
  • Constraints: 指定任务运行时机的约束条件(例如"只有在连接到网络的时候"). 你可以使用Constraints.Builder来创建Constraints对象, 并且在创建WorkRequest对象之前, 把Constraints传递给WorkerRequest.Builder.
  • WorkerManager: 入队并管理工作请求. 你把WorkRequest对象传递给WorkManager以入队任务.  WorkManager以这种方式调度任务来分散系统资源的加载, 同时尊重了你指定的约束条件.
  • WorkStatus: 包含了特定任务的信息. WorkManager为每一个WorkRequest对象提供了LiveData. LiveData持有WorkStatus对象; 通过观测LiveData, 你能够判定任务的当前状态, 以及在任务结束后, 获取返回值.

经典工作流

假设你正在写一个图片库应用, 该应用需要定期压缩存储的图片. 你想要使用WorkManager API来执行图片压缩. 在这种情况下, 你不必特别关注压缩在什么时候执行; 你想要设置好任务, 然后不再关注它.

首先, 你需要定义自己的Worker类, 然后覆盖doWork方法. 你的Worker类详述了操作该怎么执行, 却没有任何信息表明任务何时运行.

 class CompressWorker : Worker()  {

     override fun doWork(): Result {

         // Do the work here--in this case, compress the stored images.
// In this example no parameters are passed; the task is
// assumed to be "compress the whole library."
myCompress() // Indicate success or failure with your return value:
return Result.SUCCESS // (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.) } }

然后, 基于上述Worker, 你创建了OneTimeWorkRequest对象, 然后通过WorkManager将该任务入队.

val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build() WorkManager.getInstance().enqueue(compressionWork)

WorkManager选择恰当的时刻运行该任务, 在平衡了诸如系统加载, 设备插拔等等, 这些考虑之后. 在多数情况下, 你不需要指定任务约束, WorkManager即刻运行你的任务. 如果你需要检测任务状态, 可能通过对正确的LiveData<WorkStatus>的处理, 获取WorkStatus对象. 比如, 你想知道任务是否已经完成, 你可以使用这样的代码:

 WorkManager.getInstance().getStatusById(compressionWork.id)
.observe(lifecycleOwner, Observer { workStatus ->
// Do something with the status
if (workStatus != null && workStatus.state.isFinished) {
// ...
}
})

任务约束

如果你想, 你可以指定任务何时应该运行的约束条件. 比如, 你可以指定任务只有在设备空闲且连接充电时才可以运行. 在这种情况下, 你需要创建一个OneTimeWorkReqeust.Builder对象, 然后可以使用这个Builder创建一个真正的OneTimeWorkRequest.

 // Create a Constraints that defines when the task should run
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Many other constraints are available, see the
// Constraints.Builder reference
.build() val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
.setConstraints(myConstraints)
.build()

然后, 像之前一样, 将这个OneTimeWorkRequest对象传递给WorkManager.enqueue(). WorkManager会考虑你的约束条件, 直到一个时刻可以运行你的任务.

取消任务

在一个任务入队后, 你依然可以取消它. 要取消一个任务, 你需要它的工作ID, 这个ID可以从WorkRequest对象中获取到. 比如, 下面的代码取消了之前的compressionWork请求:

val compressionWorkId:UUID = compressionWork.getId() WorkManager.getInstance().cancelWorkById(compressionWorkId)

WorkManager会尽最大努力来取消该任务, 但这是天性不确定的--因为在你尝试取消该任务时, 它可能已经在运行或者已经结束了. WorkManager也提供了方法取消在一个单独工作序列中的所有任务, 或者拥有特定tag的所有任务, 当然, 也是尽力而为.

高级功能

WorkManager API的核心功能合使你能够创建简单且"即发即弃"的任务. 除此之外, API提供了高级功能让你能够设置更多复杂的任务.

任务重现

你也许拥有一个需要反复执行的任务. 比如, 照片管理应用不会只执行一次压缩图片. 更可能的情况是, 它会想非常频繁地检查共享的照片, 来看是否有新的或者修改过的照片需要压缩. 这个再现任务能够压缩它找到的照片, 或者, 它可以接二连三地新建"压缩该图片"任务.

要创建一个重现任务, 使用PeriodicWorkRequest.Builder类创建一个PeriodicWorkRequest对象, 然后用入队OneTimeWorkRequest对象一样的方式, 入队PeriodicWorkRequest对象. 比如, 假设我们定义了PhotoCheckWorker类来识别需要压缩的图片. 如果我们想每12小时运行一次这个清单任务, 我们需要像如下一样创建一个PeriodicWorkRequest对象:

 val photoCheckBuilder =
PeriodicWorkRequestBuilder<PhotoCheckWorker>(12, TimeUnit.HOURS)
// ...if you want, you can apply constraints to the builder here... // Create the actual work object:
val photoCheckWork = photoCheckBuilder.build()
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(photoCheckWork)

WorkManager尝试运行你的任务, 在你请求的间隔, 并且服从你设置的约束条件和其它需求.

任务入链

你的应用可能需要以特定的顺序执行几个任务. WorkManager允许你创建并入队一个工作序列, 这个工作序列指定了多个任务, 以及它们运行的顺序.

比如, 假设你的应用有三个OneTimeWorkRequest对象: workA, workB和workC. 这些任务必须以A->B->C的顺序执行. 要反三个任务入队, 使用WorkManager.beginWith(), 以第一个OneTimeWorkRequest对象workA为参数, 创建工作序列. 这个方法返回了WorkContinuation对象, 该对象定义了一个任务序列. 然后, 按顺序, 用WorkContinuation.then方法添加剩下的OneTimeWorkRequest对象. 最后用WorkContinuation.enqueue方法将该工作序列入队.

 WorkManager.getInstance()
.beginWith(workA)
// Note: WorkManager.beginWith() returns a
// WorkContinuation object; the following calls are
// to WorkContinuation methods
.then(workB) // FYI, then() returns a new WorkContinuation instance
.then(workC)
.enqueue()

WorkManager用请求的顺序, 根据每一个任务特定的约束, 运行任务. 如果任何一个任务返回了Worker.Result.FAILURE, 则整个序列终止.

你也可以传递多个OneTimeWorkRequest对象给beginWith()/then()方法, 如果你对单个方法调用传递了多个OneTimeWorkRequest对象, WorkManager将会并行运行该方法里面的所有任务, 在该方法里面的所有任务都完成之后, 才会执行方法序列的余下部分. 比如:

 WorkManager.getInstance()
// First, run all the A tasks (in parallel):
.beginWith(workA1, workA2, workA3)
// ...when all A tasks are finished, run the single B task:
.then(workB)
// ...then run the C tasks (in any order):
.then(workC1, workC2)
.enqueue()

你可以通过WorkContinuation.combine()方法将多个链联合在一起, 来创建更加复杂的任务链. 比如你想创建如下一个任务链:

要创建这个序列, 需要创建两个单独的序列, 然后把它们结合在一起生成第三个:

 val chain1 = WorkManager.getInstance()
.beginWith(workA)
.then(workB)
val chain2 = WorkManager.getInstance()
.beginWith(workC)
.then(workD)
val chain3 = WorkContinuation
.combine(chain1, chain2)
.then(workE)
chain3.enqueue()

在这个实例下, WorkManager先运行workA, 然后运行workB; 也是先运行workC, 然后运行workD. 在workB和workD都完成之后, 再运行workE.

备注: 尽管在每一个子链中WorkManager有序运行任务, 但却无法保证chains1中的任务如何与chains2中的任务重叠. 比如上面例子中的workB可以在workD之前或者之后运行, 也可以同时运行. 唯一的承诺是每一个子链中的多个任务有序运行; 也就是, workB直到workA运行结束都会开始执行.

有大量的WorkContinuation方法的变体提供了特定场景下的速记. 比如WorkContinuation.combine(OneTimeWorkRequest, WorkContinuation...)命令WorkManager先完成所有指定的WorkContinuation链, 然后以执行完OneTimeWorkRequest结束.

独立工作序列(Unique work sequences)

通过调用beginUniqueWork()而非beginWith()来开始一个序列, 就能够创建一个独立工作序列. 每一个独立工作序列都有一个名字; 一个名字, WorkManager一次只允许开启一个工作序列. 当你创建一个新的独立工作序列时, 如果已经存在了一个未完成的同名工作序列时, 你应该详述WorkManager此时应该做什么:

  • 取消已存在序列并用新的序列替代它;
  • 保留已存在序列并忽略新序列;
  • 将新的序列附加到已存在序列末尾, 当已存在序列最后一个任务完成后, 执行新序列中的第一个任务.

当你拥有一个任务不该入队多次时, 独立工作序列会非常有用. 比如, 你的应用需要同步数据到网络时, 你可能会入队一个名叫"sync"的独立工作序列, 并且指明如果已经存在一个"sync"序列时, 新的任务应该被忽略.

当你需要逐步构建一个长的任务链时, 独立工作序列也会非常有用. 比如照片编辑应用可能会让用户撤销一长链的操作, 每一个操作的撤销可能花费很短的时间, 但是它们必须以正确的顺序执行. 在这种情形下, 应用可以创建一个"撤销"链并将每个撤销操作按需添加到链中.

工作打标

对于每一个WorkRequest对象, 你都可以给它赋值一个tag字符串, 然后从逻辑上将任务分组. 要设置tag的话, 调用WorkRequest.Builder.addTag(), 如下:

 val cacheCleanupTask =
OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
.setConstraints(myConstraints)
.addTag("cleanup")
.build()

类WorkManager提供了几个工具方法, 允许你用特定的tag操作所有的任务. 比如, WorkManager.cancelAllWorkByTag()使用特定的tag取消所有的任务; WorkManager.getStatusesByTag()返回特定tag标记任务的WorkStatus列表.

输入参数和返回值

为了获取更大的灵活性, 你可以给任务传递参数, 也可以使任务返回结果. 被传递和返回的值是键-值对. 要给任务传参数, 在创建WorkRequest对象之前调用WorkRequest.Builder.setInputData(). 该方法的参数是Data.Builder创建的Data对象. Worker类可以通过Worker.getInputData()方法访问传入的参数. 要输出返回值, 任务调用Worker.setOutputData(). 该方法的参数也是个Data对象. 你可能通过观测任务的LiveData<WorkStatus>获取输出.

比如, 假设你有一个Worker类执行耗时的计算. 下面是这个Worker的示例:

 // Define the parameter keys:
const val KEY_X_ARG = "X"
const val KEY_Y_ARG = "Y"
const val KEY_Z_ARG = "Z" // ...and the result key:
const val KEY_RESULT = "result" // Define the Worker class:
class MathWorker : Worker() { override fun doWork(): Result {
val x = inputData.getInt(KEY_X_ARG, 0)
val y = inputData.getInt(KEY_Y_ARG, 0)
val z = inputData.getInt(KEY_Z_ARG, 0) // ...do the math...
val result = myCrazyMathFunction(x, y, z); //...set the output, and we're done!
val output: Data = mapOf(KEY_RESULT to result).toWorkData()
setOutputData(output) return Result.SUCCESS
}
}

要创建这个工作且传参, 代码应该如下:

 val myData: Data = mapOf("KEY_X_ARG" to 42,
"KEY_Y_ARG" to 421,
"KEY_Z_ARG" to 8675309)
.toWorkData() // ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
.setInputData(myData)
.build()
WorkManager.getInstance().enqueue(mathWork)

返回值将会在任务的WorkStatus中可用:

 WorkManager.getInstance().getStatusById(mathWork.id)
.observe(this, Observer { status ->
if (status != null && status.state.isFinished) {
val myResult = status.outputData.getInt(KEY_RESULT,
myDefaultValue)
// ... do something with the result ...
}
})

如果你将任务入链, 一个任务的输出是可以作为链中下一个任务的输入的. 如果是个很简单的链, 单OneTimeWorkRequest紧接着另一个单OneTimeWorkRequest, 第一个任务通过调用setOutputData返回的值, 在第二个任务中通过调用getInputData可以获取得到. 如果工作链比较复杂, 比如多个任务都发送输出到单个任务--你可以在OneTimeWorkRequest.Builder中定义一个InputMerger, 以指定当不同任务返回的输出有相同的键时, 应该怎么处理.

Android Support WorkManager使用详解的更多相关文章

  1. 转:android Support 兼容包详解

    本文转自stormzhang的ANDROID SUPPORT兼容包详解 背景 来自于知乎上邀请回答的一个问题Android中AppCompat和Holo的一个问题?, 看来很多人还是对这些兼容包搞不清 ...

  2. Android Support Palette使用详解

    使用Palette API选择颜色 良好的视觉设计是app成功所必不可少的, 而色彩设计体系是设计的基础构成. Palette包是支持包, 能够从图片中解析出突出的颜色, 从而帮助你创建出视觉迷人的应 ...

  3. 33、Android Support兼容包详解(转载)

    原文转自:微信分享 2015-03-31 22:11 背景 来自于知乎上邀请回答的一个问题Android中AppCompat和Holo的一个问题?, 看来很多人还是对这些兼容包搞不清楚,那么干脆写篇博 ...

  4. Android Support兼容包详解

    原文:http://www.open-open.com/lib/view/open1427852683115.html

  5. Android Design Support Library使用详解

    Android Design Support Library使用详解 Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的And ...

  6. Android SDK中的Support兼容包详解

    这篇文章主要介绍了Android SDK中的Support兼容包详解,本文详细区分了Support Library的版本区别.各种Theme的概念和使用注意事项等内容,需要的朋友可以参考下 背景 来自 ...

  7. ANDROID L——Material Design详解(UI控件)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...

  8. 【转】Linux下Android ADB驱动安装详解

    原文网址:http://blog.csdn.net/zhenwenxian/article/details/5901350 Linux下Android ADB驱动安装详解 概述 最近由于内置的合作商比 ...

  9. Xamarin android CardView的使用详解

    android 5.0新增加的一个控件CardView,在support v7兼容包中,意思就是卡片View,虽然可以设置阴影,圆角等等样式,但是我们也可以自己写出来,谷歌工程师之所以出这个,肯定是帮 ...

随机推荐

  1. Unity中使用C#实现UDP广播

    没有系统的学习过网络,想做联机游戏还真是费劲,想做在局域网内实现自动搜索服务器的功能,然后就想到了使用UDP进行广播,把服务器的信息广播给每一个玩家. Socket udpSocket = new S ...

  2. js数组的比较

    如果两个数组元素个数都相等,但排序不同,那么它两个相等吗?结果肯定是否定的.但如果先调用sort()方法进行排序,结果就是true了. console.log(a.sort().toString()= ...

  3. 我用Python远程探查室友的网页浏览记录,他不愧是成年人!

    过程: 利用Python制作远程查看别人电脑的操作记录,与其它教程类似,都是通过邮件返回. 利用程序得到目标电脑浏览器当中的访问记录,生产一个文本并发送到你自己的邮箱,当然这个整个过程除了你把pyth ...

  4. Codeblocks自动代码格式化快捷键(自带)

    代码区域右击 点format use AStyle 估计也就是考试竞赛逼着用这个

  5. last命令详解

    基础命令学习目录首页 原文链接:https://blog.csdn.net/jerry_1126/article/details/54427119 [root@xiaoma /root] test!# ...

  6. MathExam6317

    自己取一个大气又可爱的标题 小学二年级的还没来得及写,大部分时间还是花在巩固和查阅新旧知识上了,通过看学习视频,查资料,看博客园的博客...下次完善好了交上. 一.预估与实际 PSP2.1 Perso ...

  7. GITHUB随笔 15-5月 junit

    junit 是用来做单元测试的一个工具  测试是一个持续的过程.也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式的开发过程. @ignore:   该元数据标记的测试方法在测试中会被忽 ...

  8. 项目报错“JavaServer Faces 2.2 can not be installed : One or more constraints”等一系列问题

    在做springmvc+maven项目时,经常遇到如下错误: 解决办法(这里以jdk1.8,web3.0为例): 一:保证build path的jre版本 remove掉旧版本的,add新版本 二:保 ...

  9. C语言中的strstr函数

    转自:http://www.cnblogs.com/xy-kidult/archive/2012/12/25/2832460.html 早上翻<C和指针>,碰见一个子串查找问题,这个问题在 ...

  10. vue2.0 keep-alive 最佳实战(转载)

    1.基本用法 vue2.0提供了一个keep-alive组件用来缓存组件,避免多次加载相应的组件,减少性能消耗 <keep-alive> <component> <!-- ...