[译] WorkManager 基础入门

WorkManager 基础入门

插图来自 Virginia Poltrackhtml

欢迎来到咱们 WorkManager 系列的第二篇文章。WorkManager 是一个 Android Jetpack 库,当知足工做的约束条件时,用来运行可延迟、须要保障的后台工做。对于许多类型的后台工做,WorkManager 是当前的最佳实践方案。在第一篇博文中,咱们讨论了 WorkManager 是什么以及什么时候使用 WorkManager。前端

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

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

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

从一个例子开始

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

这个例子如上图所示git

这个例子正是具备如下特色的任务:github

  • 可延迟的,由于你不须要它当即执行,并且实际上可能但愿等待某些约束被知足(例如等待网络链接)。
  • 须要确保可以运行,不管应用程序是否退出,由于若是加了滤镜后的图像永远没能与世界共享,你的用户会很是不满意!

这些特色使咱们的图像加滤镜和上传任务成为 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<UploadWorker>()
        .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<UploadWorker>()
        .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<CompressWorker>()
        .setInputMerger(ArrayCreatingInputMerger::class.java)
        .setConstraints(constraints)
        .build()
复制代码

请注意,InputMerger 是添加到 compressWorkRequest 中的,而不是并行的三个滤镜请求中的。

假设每一个滤镜工做请求的输出是映射到图像 URI 的键 “KEY_IMAGE_URI”。添加 ArrayCreatingInputMerger 的做用是并行请求的输出,当这些输出具备匹配的时,它会建立一个包含全部输出值的数组,映射到单个键。可视化图表以下:

ArrayCreatingInputMerger 功能可视化

所以,compressWorkRequest 的输入将最终成为映射到滤镜图像 URI 数组的 “KEY_IMAGE_URI” 对。

观察你的 WorkRequest 状态

监视工做的最简单方法是使用 LiveData 类。若是你不熟悉 LiveData,它是一个生命周期感知的可监视数据持有者 —— 这里 对此有更详细的描述。

调用 getWorkInfoByIdLiveData 返回一个 WorkInfoLiveDataWorkInfo 包含输出的数据和表示工做状态的枚举。当工做顺利完成后,它的 State 就会是 SUCCEEDED。所以,例如,你能够经过编写一些监视代码来实现当工做完成时自动显示该图像:

// In your UI (activity, fragment, etc)
WorkManager.getInstance().getWorkInfoByIdLiveData(uploadWorkRequest.id)
        .observe(lifecycleOwner, Observer { workInfo ->
            // Check if the current work's state is "successfully finished" if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) { displayImage(workInfo.outputData.getString(KEY_IMAGE_URI)) } }) 复制代码

有几点须要注意:

  • 每一个 WorkRequest 都有一个惟一的 id,该惟一 id 是查找关联 WorkInfo 的一种方法。
  • WorkInfo 更改时进行监视并被通知的能力是 LiveData 提供的功能。

工做有一个由不一样 State 表明的生命周期。监视 LiveData<WorkInfo> 时,你会看到这些状态;例如,你可能会看到:

“happy path” 或工做状态

工做状态经历的 “happy path” 以下:

  1. BLOCKED:只有当工做在链中而且不是链中的下一个工做时才会出现这种状态。
  2. ENQUEUED:只要工做是工做链中的下一个而且有资格运行,工做就会进入这个状态。这项工做可能仍在等待 Constraint 被知足。
  3. RUNNING:在这种状态时,工做正在运行。对于 Worker,这意味着 doWork() 方法已经被调用。
  4. SUCCEEDED:当 doWork() 返回 Result.success() 时,工做进入这种最终状态。

如今,当工做处于 RUNNING 状态,你能够调用 Result.retry()。这将会致使工做退回 ENQUEUED 状态。工做也可能随时被 CANCELLED

若是工做运行的结果是 Result.failure() 而不是成功。它的状态将会以 FAILED 结束,所以,状态的完整流程图以下所示:

(来自:Working with WorkManager Android 开发者峰会 2018)

想看精彩的视频讲解,请查看 WorkManager Android 开发者峰会演讲

总结

这就是 WorkManager API 的基础知识。使用咱们刚刚介绍的代码片断,你如今就能够:

  • 建立包含输入/输出的 Worker
  • 使用 WorkRequestConstraint、启动输入和退出策略配置 Worker 的运行方式。
  • 调度 WorkRequest
  • 了解默认状况下 WorkManager 在线程和保障运行方面的幕后工做。
  • 建立复杂链式相互依赖的工做,能够顺序运行和并行运行。
  • 使用 WorkInfo 监视你的 WorkRequest 的状态。

想亲自试试 WorkManager 吗?查看 codelab,包含 KotlinJava 代码。

随着咱们继续更新本系列,请继续关注有关 WorkManager 主题的更多博客文章。 有什么问题或者你但愿咱们写到的东西吗?请在评论区告诉咱们!

感谢 Pietro Maggi

WorkManager 相关资源

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索