WorkManager可以用来轻松地创建一个任务,然后放手让它在你指定的某种条件下运行。
在项目中引入WorkManager:
dependencies {
def work_version = "1.0.0-alpha11"
implementation "android.arch.work:work-runtime-ktx:$work_version"
}
1. 相关类和概念
- Worker: 定义需要执行的任务,继承此类并在此定义任务。
- WorkRequest: 代表一个单独的任务。
WorkRequest
对象指定执行任务的Worker
类。还可以给WorkRequest
添加更多的细节,指定任务的执行环境。每一个WorkRequest
都有一个自动生成的唯一ID
,使用该ID
可以用来获取任务的执行状态或者取消任务。WorkRequest
是一个抽象类,我们只能使用其两个子类 OneTimeWorkRequest (单次任务)或者 PeriodicWorkRequest (周期性任务)。 - WorkManager:将
WorkRequest
传给WorkManager
来入队任务。当条件合适时将执行任务。 - WorkInfo: 通过
WorkInfo
可以获取特定任务的执行状态,并在任务完成后获取其返回值。
2. 基本的使用
首先,继承Worker类,来定义任务:
class MyWorker(context : Context, params : WorkerParameters)
: Worker(context, params) {
override fun doWork(): Result {
// Do the work here
doTheWork()
// Indicate success or failure with your return value:
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
return Result.SUCCESS
}
}
然后,创建一个WorkRequest并通过WorkManager将任务入队:
val myWork = OneTimeWorkRequestBuilder<MyWorker>().build()
WorkManager.getInstance().enqueue(myWork)
WorkManager将会选择一个合适的时机来执行此任务。在大多数情况下,如果不给任务指定执行条件,WorkManager会立即执行这个任务。如果需要检查任务的执行状态,可以通过WorkInfo来动态监测:
WorkManager.getInstance().getWorkInfoByIdLiveData(myWork.id)
.observe(lifecycleOwner, Observer { workInfo ->
// Do something with the status
if (workInfo != null && workInfo.state.isFinished) {
// ...
}
})
2.1 添加任务执行条件
// Create a Constraints object that defines when the task should run
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true) //要求设备处于空闲状态
.setRequiresCharging(true) //要求设备正在充电
.setRequiresBatteryNotLow(true) //要求设备未处于低电量状态
.setRequiresStorageNotLow(true) //要求设备未处于低内存状态
.setRequiredNetworkType(NetworkType.METERED) //要求设备网络环境
.build()
// ...then create a OneTimeWorkRequest that uses those constraints
val myWork = OneTimeWorkRequestBuilder<MyWorker>()
.setConstraints(myConstraints)
.build()
这样,只有当设备的状态满足上述要求时,任务才有可能执行。
2.2 取消任务
val myWorkId: UUID = myWork.id
WorkManager.getInstance().cancelWorkById(myWorkId)
2.3 标记任务
val myWork = OneTimeWorkRequestBuilder<MyWork>()
.setConstraints(myConstraints)
.addTag("cleanup")
.build()
标记任务后,可以通过 WorkManager.cancelAllWorkByTag()
来取消该标记的所有任务。也可以通过WorkerManager.getWorkInfosByTagLiveData()
来获取该标记的所有任务的WorkInfo
集合。
2.4 周期性任务
val myBuilder =
PeriodicWorkRequestBuilder<MyWorker>(12, TimeUnit.HOURS)
// ...if you want, you can apply constraints to the builder here...
// Create the actual work object:
val myWork = myBuilder.build()
// Then enqueue the recurring task:
WorkManager.getInstance().enqueue(myWork)
上面的代码定义了一个每12小时执行一次的任务。其实就是使用PeriodicWorkRequest
类来创建WorkRequest
, 用法和单次任务一样。
3. 进阶用法
3.1 链式任务
你也许希望一次执行多个任务,或者多个任务按照特定的顺序执行。比如有三个OneTimeWorkRequest
对象:workA
,workB
和workC
,可以通过以下方式让它们顺序执行:
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
也可以给beginWith
方法和then
方法一次传多个OneTimeWorkRequest
,如下:
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()
还可以创建两个任务链,然后将它们合到一起。
如果想创建如上图内容的任务,可以这样:
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
。
3.2 唯一工作顺序
你可以使用beinUniqueWork
方法替代beginWith
方法来创建一个唯一工作顺序,该方法如下:
beginUniqueWork(String uniqueWorkName,
ExistingWorkPolicy existingWorkPolicy,
OneTimeWorkRequest... work)
beginUniqueWork
方法接收三个参数,第一个参数用于给该唯一工作顺序指定一个名称,WorkManager
一次只发射一个该名称的工作顺序。如果有两个唯一工作顺序拥有相同的名称,怎么办呢? 第二个参数用于解决这个问题,第二个参数是一个枚举变量,它拥有三个值,作用分别如下:
public enum ExistingWorkPolicy {
/**
* 如果已存在相同唯一名称的work,那么用当前的work替代它
*/
REPLACE,
/**
* 如果已存在相同唯一名称的work,忽略新创建的work
*/
KEEP,
/**
* 如果已存在相同唯一名称的work,把新的work加在原work后面,当原work执行完后再执行新work
*/
APPEND
}
唯一执行顺序可以用以处理出现某任务还没执行完成,又被重复提交的情况。
3.3 输入参数和接收返回值
为了更具灵活性,可以给任务传递参数并接收其结果值。传值使用 WorkRequest.Builder.setInputData()
方法,返回结果值使用 Worker.setOutputData()
方法,传值和返回值均使用键值对。
例如某一个任务,需要传入x
,y
,z
三个数值,计算结束后返回结果值。传值的代码如下:
val myData: Data = workDataOf("x" to 42,
"y" to 421,
"z" to 8675309)
// ...then create and enqueue a OneTimeWorkRequest that uses those arguments
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
.setInputData(myData)
.build()
WorkManager.getInstance().enqueue(mathWork)
接收传值参数并返回计算结果值的代码如下:
class MathWorker(context : Context, params : WorkerParameters)
: Worker(context, params) {
override fun doWork(): Result {
val x = inputData.getInt("x", 0)
val y = inputData.getInt("y", 0)
val z = inputData.getInt("z", 0)
// ...do the math...
val result = myCrazyMathFunction(x, y, z);
//...set the output, and we're done!
val output: Data = workDataOf("result" to result)
setOutputData(output)
return Result.SUCCESS
}
}
返回值可以通过监听任务的WorkInfo获取:
WorkManager.getInstance().getWorkInfoByIdLiveData(mathWork.id)
.observe(this, Observer { status ->
if (status != null && status.state.isFinished) {
val myResult = status.outputData.getInt("result",
myDefaultValue)
// ... do something with the result ...
}
})
对于链式任务,如果上一个任务在返回结果前调用了setOutputData()
方法,那么它的下一个任务可以通过getInputData()
方法获取上一个任务返回的结果值。