[译] WorkManager 基础入门

在这篇博文中,我将介绍:

  • 将你的后台任务定义为工作
  • 定义特定的工作应该如何运行
  • 运行你的工作
  • 使用链进行存在依赖的工作
  • 监视你的工作的状态

我还将解释 WorkManager 幕后发生的事情,以便你可以就如何使用它做出明智的决定。

从一个例子开始

假设你有一个图片编辑应用,可让你给图像加上滤镜并将其上传到网络让全世界看到。你希望创建一系列后台任务,这些任务用于滤镜,压缩图像和之后的上传。在每个环节,都有一个需要检查的约束——给图像加滤镜时要有足够的电量,压缩图像时要有足够的存储空间,以及上传图像时要有网络连接。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个例子如上图所示

这个例子正是具有以下特点的任务:

  • 可延迟的,因为你不需要它立即执行,而且实际上可能希望等待某些约束被满足(例如等待网络连接)。
  • 需要确保能够运行,无论应用程序是否退出,因为如果加了滤镜后的图像永远没能与世界共享,你的用户会非常不满意!

这些特点使我们的图像加滤镜和上传任务成为 WorkManager 的完美用例。

添加 WorkManager 依赖

本文使用 Kotlin 书写代码,使用 KTX 库(KoTlin eXtensions)。KTX 版本的库提供了 扩展函数 为了更简洁和习惯的使用 Kotlin。你可以添加如下依赖来使用 KTX 版本的 WorkManager:

dependencies {
def work_version = “1.0.0-beta02”
implementation “android.arch.work:work-runtime-ktx:$work_version”
}

你可以在 这里](developer.android.com/topic/libra…) 到该库的最新版本。如果你想使用 Java 依赖,那就移除“-ktx”。

定义你的 work 做什么

在我们将多个任务连接在一起之前,让我们关注如何执行一项工作。我将会着重细说上传任务。首先,你需要创建自己的 Worker 实现类。我将会把我们的类命名为 UploadWorker,然后重写 doWork() 方法。

Workers:

  • 定义你的工作实际做了什么。
  • 接受输入并产生输出。输入和输出都以键值对表示。
  • 始终返回表示成功,失败或重试的值。

这是一个示例,展示了如何实现上传图像的 Worker

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

override fun doWork(): Result {
try {
// Get the input
val imageUriInput = inputData.getString(Constants.KEY_IMAGE_URI)

// Do the work
val response = upload(imageUriInput)

// Create the output of the work
val imageResponse = response.body()
val imgLink = imageResponse.data.link
// workDataOf (part of KTX) converts a list of pairs to a [Data] object.
val outputData = workDataOf(Constants.KEY_IMAGE_URI to imgLink)

return Result.success(outputData)

} catch (e: Exception) {
return Result.failure()
}
}

fun upload(imageUri: String): Response {
TODO(“Webservice request code here”)
// Webservice request code here; note this would need to be run
// synchronously for reasons explained below.
}

}

有两点需要注意:

  • 输入和输出是作为 Data 传递,它本质上是原始类型和数组的映射。Data 对象应该相当小 —— 实际上可以输入/输出的总大小有限制。这由 MAX_DATA_BYTES 设置。如果您需要将更多数据传入和传出 Worker,则应将数据放在其他地方,例如 Room database。作为一个例子,我传入上面图像的 URI,而不是图像本身。
  • 在代码中,我展示了两个返回示例:Result.success()Result.failure()。还有一个 Result.retry() 选项,它将在之后的时间再次重试你的工作。

定义您的 work 应该如何运行

一方面 Worker 定义工作的作用,另一方面 WorkRequest 定义应该如何以及何时运行工作

以下是为 UploadWorker 创建 OneTimeWorkRequest 的示例。也可以有重复性的 PeriodicWorkRequest

// workDataOf (part of KTX) converts a list of pairs to a [Data] object.
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)

val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setInputData(imageData)
.build()

WorkRequest 将输入 imageData: Data 对象,并尽快运行。

假设 UploadWork 并不总是应该立即运行 —— 它应该只在设备有网络连接时运行。你可以通过添加 Constraints 对象来完成此操作。你可以创建这样的约束:

val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

以下是其他受支持约束的示例:

val constraints = Constraints.Builder()
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresStorageNotLow(true)
.setRequiresDeviceIdle(true)
.build()

最后,还记得 Result.retry() 吗?我之前说过,如果 Worker 返回 Result.retry(),WorkManager 将重新计划工作。你可以在创建新的 WorkRequest 时自定义退避条件。这允许你定义何时应重试运行。

退避条件由两个属性定义:

  • BackoffPolicy,默认为指数性的,但是可以设置为线性。
  • 持续时间,默认为 30 秒。

用于对上传工作进行排队的组合代码如下,包括约束,输入和自定义退避策略:

// Create the Constraints
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

// Define the input
val imageData = workDataOf(Constants.KEY_IMAGE_URI to imageUriString)

// Bring it all together by creating the WorkRequest; this also sets the back off criteria
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setInputData(imageData)
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build()

运行 work

这些都很好,但你还没有真正调度好你的工作去运行。以下是告诉 WorkManager 调度工作所需的一行代码:

WorkManager.getInstance().enqueue(uploadWorkRequest)

你首先需要获取 WorkManager 的实例,这是一个负责执行你的工作的单例。调用 enqueue 来启动 WorkManager 跟踪和调度工作的整个过程。

在幕后 —— 工作是怎么运行的

那么,WorkManager 能为您做些什么呢?默认情况下,WorkManager 会:

  • 脱离主线程运行你的工作(假设你正在继承 Worker 类,如上面的 UploadWorker 所示)。
  • 保障 你的工作将会运行(即使你重启设备或应用程序退出,它也不会忘记运行你的工作)。
  • 根据用户 API 级别的最佳实践运行(如上一篇文章所述)。

让我们探讨一下 WorkManager 如何确保你的工作脱离主线程运行并保证执行。在幕后,WorkManager 包括以下部分:

  • 内部 TaskExecutor:一个单线程 Executor,处理所有排队工作的请求。如果您不熟悉 Executors,可以在这里阅读更多相关信息。

  • WorkManager 数据库:一个本地数据库,可跟踪所有工作的所有信息和状态。这包括工作的当前状态,工作的输入和输出以及对工作的任何约束限制。此数据库使 WorkManager 能够保证你的工作能够完成 —— 如果你的用户的设备重新启动并且工作中断,则可以从数据库中提取工作的所有详细信息,并在设备再次启动时重新启动工作。

  • WorkerFactory:一个默认工厂,用于创建 Worker 的实例。我们将在以后的博文中介绍为什么以及如何配置它。

  • Default Executor:一个默认的执行程序,运行你的工作,除非你另行指定。这确保在默认情况下,你的工作是同步运行的,并且在主线程之外运行。

  • 这些部分可以被重写以具有不同的行为。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自:Working with WorkManager Android 开发者大会展示 2018

当你安排 WorkRequest

  1. 内部 TaskExecutor 立即将你的 WorkRequest 信息保存到 WorkManager 数据库。
  2. 稍后,当满足 WorkRequestConstraints 时(可以立即发生),Internal TaskExecutor 会告诉 WorkerFactory 创建一个 Worker
  3. 之后,默认的 Executor 调用你的 WorkerdoWork() 方法脱离主线程

通过这种方式,默认情况下,你的工作都可以保证执行脱离主线程运行。

现在,如果你想使用除默认 Executor 之外的一些其他机制来运行你的工作,也是可以的!对协程(CoroutineWorker)和 RxJava(RxWorker)的开箱即用支持作为工作的手段。

或者,你可以使用 ListenableWorker 准确指定工作的执行方式。Worker 实际上是 ListenableWorker 的一个实现,它默认在默认的 Executor 上运行你的工作,因此是同步的。所以,如果你想要完全控制工作的线程策略或异步运行工作,你可以将 ListenableWorker 子类化(具体细节将在后面的文章中讨论)。

WorkManager 虽然将所有工作信息保存到数据库中有些麻烦,但它还是会做,这使得它成了非常适合需要保障执行的任务。这也是使得 WorkManager 轻松应对对于不需要保障且只需要在后台线程上执行的任务的的原因。例如,假设你已经下载了图像,并且希望根据该图像更改 UI 部分的颜色。这是应该脱离主线程运行的工作,但是,因为它与 UI 直接相关,所以如果关闭应用程序则不需要继续。所以在这样的情况下,不要使用 WorkManager —— 坚持使用像 Kotlin 协程那样轻量的东西或创建自己的 Executor

使用链进行依赖性工作

我们的滤镜示例包含的不仅仅是一个任务 —— 我们想要给多个图像加滤镜,然后压缩并上传。如果要一个接一个地或并行地运行一系列 WorkRequests,则可以使用 。示例图显示了一个链,其中有三个并行运行的滤镜任务,后面是压缩任务和上传任务,按顺序运行:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 WorkManager 非常简单。假设你已经用适当的约束创建了所有 WorkRequests,代码如下所示:

WorkManager.getInstance()
.beginWith(Arrays.asList(
filterImageOneWorkRequest,
filterImageTwoWorkRequest,
filterImageThreeWorkRequest))
.then(compressWorkRequest)
.then(uploadWorkRequest)
.enqueue()

三个图片滤镜 WorkRequests 并行执行。一旦完成所有滤镜 WorkRequests (并且只有完成所有三个),就会发生 compressWorkRequest,然后是 uploadWorkRequest

链的另一个优点是:一个 WorkRequest 的输出作为下一个 WorkRequest 的输入。因此,假设你正确设置了输入和输出数据,就像我上面的 UploadWorker 示例所做的那样,这些值将自动传递。

为了处理并行的三个滤镜工作请求的输出,可以使用 InputMerger,特别是 ArrayCreatingInputMerger。代码如下:

val compressWorkRequest = OneTimeWorkRequestBuilder()
.setInputMerger(ArrayCreatingInputMerger::class.java)
.setConstraints(constraints)
.build()

InputMerger,特别是 ArrayCreatingInputMerger。代码如下:

val compressWorkRequest = OneTimeWorkRequestBuilder()
.setInputMerger(ArrayCreatingInputMerger::class.java)
.setConstraints(constraints)
.build()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值