本文最后更新于:1 个月前

使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务。

文档

将 WorkManager 添加到 Android 项目中

要将 WorkManager 库导入到 Android 项目中,请将以下依赖项添加到应用的 build.gradle 文件:

dependencies {
  def work_version = "2.3.4"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
  }

创建后台任务

任务是使用 Worker 类定义的。doWork() 方法在 WorkManager 提供的后台线程上同步运行。

doWork() 返回的 Result 会通知 WorkManager 任务:

  • 已成功完成:Result.success()
  • 已失败:Result.failure()
  • 需要稍后重试:Result.retry()

配置运行任务的方式和时间

Worker 定义工作单元,WorkRequest 则定义工作的运行方式和时间。对于一次性 WorkRequest,可以使用 OneTimeWorkRequest,对于周期性工作,可以使用 PeriodicWorkRequest

工作约束

Constraints 指明工作何时可以运行。具体约束详见约束的完整列表

初始延迟

setInitialDelay() 初始延迟,加入队列后至少经过多长时间再运行。

重试和退避政策

如果想要让 WorkManager 重新尝试执行任务,可以从工作器返回 Result.retry()

退避延迟时间指定重试工作前的最短等待时间。退避政策定义了再后续重试过程中,退避延迟时间随时间以怎样的方式增长,默认情况下按 BackoffPolicy.EXPONENTIAL 延长。

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
        .setBackoffCriteria(
                BackoffPolicy.LINEAR,
                OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                TimeUnit.MILLISECONDS)
        .build()

退避政策

  • BackoffPolicy.EXPONENTIAL 指数方式增长
  • BackoffPolicy.LINEAR 线性增长

定义任务的输入/输出

输入和输出值以键值对的形式存储在 Data 对象中。

输入

// 设置输入数据
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)
// 访问输入参数
val imageUriInput = getInputData().getString(Constants.KEY_IMAGE_URI)

输出

Data 也可用于输出返回值,将其添加到 Result.success()Result.failure() 时的 Result() 中。

class UploadWorker(appContext: Context, workerParams: WorkerParameters)
    : Worker(appContext, workerParams) {

    override fun doWork(): Result {

            // Get the input
            val imageUriInput = getInputData().getString(Constants.KEY_IMAGE_URI)
            // TODO: validate inputs.
            // Do the work
            val response = uploadFile(imageUriInput)

            // Create the output of the work
            val outputData = workDataOf(Constants.KEY_IMAGE_URL to response.imageUrl)

            // Return the output
            return Result.success(outputData)

    }
}

标记工作

WorkRequest.Builder.addTag(String) 为任意 WorkRequest 对象分配标识字符串,按逻辑对任务进行分组,就可以对使用特定标记的所有任务执行操作。

工作状态和观察工作

工作状态

  • BLOCKED 有尚未完成的前提性工作
  • ENQUEUED 工作能够在满足 Constraints 和时机条件后立即执行
  • RUNNING 工作器在活跃运行
  • 终止 State,只有 OneTimeWorkRequest 可以进入这种 State
    • SUCCEEDED 工作器返回 Result.success()
    • FAILED 工作器返回 Result.failure()所有依赖工作也会被标记为 FAILED,并且不会运行
  • CANCELLED 明确取消尚未终止的 WorkRequest 时。所有依赖工作也会被标记为 CANCELLED,并且不会运行。

观察工作

将工作加入队列后,可以通过 WorkManager 检查其状态,相关信息在 WorkInfo 对象中提供,包括工作的 id、标签、当前 State 和任何输出数据。

  • WorkManager.getWorkInfoById(UUID) or WorkManager.getWorkInfoByIdLiveData(UUID)
  • WorkManager.getWorkInfosByTag(String) or WorkManager.getWorkInfosByTagLiveData(String)
  • WorkManager.getWorkInfosForUniqueWork(String) or WorkManager.getWorkInfosForUniqueWorkLiveData(String)

利用每个方法的 LiveData 变量,可以通过注册监听器观察 WorkInfo 的变化。

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(uploadWorkRequest.id)
        .observe(lifecycleOwner, Observer { workInfo ->
            if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
                displayMessage("Work finished!")
            }
        })

观察工作器的中间进度

ListenableWorker 支持 setProgressAsync() API,此类 API 可以保留中间进度。

只有在运行时才能观察到和更新进度信息。

更新进度

对于 ListenableWorkerWorkersetProgressAsync() 会返回 ListenableFuture<Void>;更新进度是异步过程,因为更新过程包括将进度信息存储在数据库中。在 Kotlin 中,可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息。

class ProgressWorker(context: Context, parameters: WorkerParameters) :
    CoroutineWorker(context, parameters) {

    companion object {
        const val Progress = "Progress"
        private const val delayDuration = 1L
    }

    override suspend fun doWork(): Result {
        val firstUpdate = workDataOf(Progress to 0)
        val lastUpdate = workDataOf(Progress to 100)
        setProgress(firstUpdate)
        delay(delayDuration)
        setProgress(lastUpdate)
        return Result.success()
    }
}

观察进度

使用观察工作中的 getWorkInfoBy...()getWorkInfoBy...LiveData() 方法,并引用 WorkInfo

WorkManager.getInstance(applicationContext)
    // requestId is the WorkRequest id
    .getWorkInfoByIdLiveData(requestId)
    .observe(observer, Observer { workInfo: WorkInfo? ->
            if (workInfo != null) {
                val progress = workInfo.progress
                val value = progress.getInt(Progress, 0)
                // Do something with progress information
            }
    })

链接工作

  1. WorkManager.beginWith(OneTimeWorkRequest/List<OneTimeWorkRequest>) 返回 WorkContinuation 实例。
  2. WorkContinuation.then(OneTimeWorkRequest/List<OneTimeWorkRequest>),每次调用 then() 都会返回一个新的 WorkContinuation 实例。
  3. WorkContinuation.enqueue()WorkContinuation 链排队。

如果添加了 OneTimeWorkRequestList,这些请求可能会并行运行。

WorkManager.getInstance(myContext)
    // Candidates to run in parallel
    .beginWith(listOf(filter1, filter2, filter3))
    // Dependent work (only runs after all previous work in chain)
    .then(compress)
    .then(upload)
    // Don't forget to enqueue()
    .enqueue()

Input Merger

为了管理来自多个父级 OneTimeWorkRequest 的输入,WorkManager 使用 OneTimeWorkRequestBuilder.setInputMerger()

  • OverwritingInputMerger 会尝试将所有输入中的所有键添加到输出中。如果发生冲突,它会覆盖先前设置的键。
  • ArrayCreatingInputMerger 会尝试合并输入,并在必要时创建数组。

取消和停止工作

WorkManager.cancelWorkById(UUID)

重复性工作

PeriodicWorkRequest 无法链接。

val constraints = Constraints.Builder()
        .setRequiresCharging(true)
        .build()

val saveRequest =
PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(myContext)
    .enqueue(saveRequest)

唯一工作

调用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) 创建唯一工作序列。

第二个参数为冲突解决策略,指定如果已存在具有该唯一名称的未完成工作链,应如何处理。

  • REPLACE 取消现有工作链,并将其替换为新工作链。
  • KEEP,保持现有序列并忽略您的新请求。
  • APPEND 在现有序列的最后一个任务完成后运行新序列的第一个任务。

创建唯一工作链 WorkManager.beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest)