- 原文地址:WorkManager Basics
- 原文做者:Lyla Fujiwara
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:Rickon
- 校对者:Feximin
插图来自 Virginia Poltrackhtml
欢迎来到咱们 WorkManager 系列的第二篇文章。WorkManager 是一个 Android Jetpack 库,当知足工做的约束条件时,用来运行可延迟、须要保障的后台工做。对于许多类型的后台工做,WorkManager 是当前的最佳实践方案。在第一篇博文中,咱们讨论了 WorkManager 是什么以及什么时候使用 WorkManager。前端
在这篇博文中,我将介绍:java
我还将解释 WorkManager 幕后发生的事情,以便你能够就如何使用它作出明智的决定。android
假设你有一个图片编辑应用,可以让你给图像加上滤镜并将其上传到网络让全世界看到。你但愿建立一系列后台任务,这些任务用于滤镜,压缩图像和以后的上传。在每一个环节,都有一个须要检查的约束——给图像加滤镜时要有足够的电量,压缩图像时要有足够的存储空间,以及上传图像时要有网络链接。ios
这个例子如上图所示git
这个例子正是具备如下特色的任务:github
这些特色使咱们的图像加滤镜和上传任务成为 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”。数组
在咱们将多个任务链接在一块儿以前,让咱们关注如何执行一项工做。我将会着重细说上传任务。首先,你须要建立本身的 Worker
实现类。我将会把咱们的类命名为 UploadWorker
,而后重写 doWork()
方法。
Worker
s:
这是一个示例,展现了如何实现上传图像的 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()
选项,它将在以后的时间再次重试你的工做。一方面 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
时自定义退避条件。这容许你定义什么时候应重试运行。
退避条件由两个属性定义:
用于对上传工做进行排队的组合代码以下,包括约束,输入和自定义退避策略:
// 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()
复制代码
这些都很好,但你尚未真正调度好你的工做去运行。如下是告诉 WorkManager 调度工做所需的一行代码:
WorkManager.getInstance().enqueue(uploadWorkRequest)
复制代码
你首先须要获取 WorkManager
的实例,这是一个负责执行你的工做的单例。调用 enqueue
来启动 WorkManager
跟踪和调度工做的整个过程。
那么,WorkManager
能为您作些什么呢?默认状况下,WorkManager
会:
Worker
类,如上面的 UploadWorker
所示)。让咱们探讨一下 WorkManager 如何确保你的工做脱离主线程运行并保证执行。在幕后,WorkManager 包括如下部分:
内部 TaskExecutor:一个单线程 Executor
,处理全部排队工做的请求。若是您不熟悉 Executors
,能够在这里阅读更多相关信息。
WorkManager 数据库:一个本地数据库,可跟踪全部工做的全部信息和状态。这包括工做的当前状态,工做的输入和输出以及对工做的任何约束限制。此数据库使 WorkManager 可以保证你的工做可以完成 —— 若是你的用户的设备从新启动而且工做中断,则能够从数据库中提取工做的全部详细信息,并在设备再次启动时从新启动工做。
WorkerFactory:一个默认工厂,用于建立 Worker
的实例。咱们将在之后的博文中介绍为何以及如何配置它。
Default Executor:一个默认的执行程序,运行你的工做,除非你另行指定。这确保在默认状况下,你的工做是同步运行的,而且在主线程以外运行。
这些部分能够被重写以具备不一样的行为。
来自:Working with WorkManager Android 开发者大会展现 2018
当你安排 WorkRequest
:
WorkRequest
信息保存到 WorkManager 数据库。WorkRequest
的 Constraints
时(能够当即发生),Internal TaskExecutor 会告诉 WorkerFactory
建立一个 Worker
。Executor
调用你的 Worker
的 doWork()
方法脱离主线程。经过这种方式,默认状况下,你的工做均可以保证执行脱离主线程运行。
如今,若是你想使用除默认 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” 对。
监视工做的最简单方法是使用 LiveData
类。若是你不熟悉 LiveData,它是一个生命周期感知的可监视数据持有者 —— 这里 对此有更详细的描述。
调用 getWorkInfoByIdLiveData
返回一个 WorkInfo
的 LiveData
。WorkInfo
包含输出的数据和表示工做状态的枚举。当工做顺利完成后,它的 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” 以下:
BLOCKED
:只有当工做在链中而且不是链中的下一个工做时才会出现这种状态。ENQUEUED
:只要工做是工做链中的下一个而且有资格运行,工做就会进入这个状态。这项工做可能仍在等待 Constraint
被知足。RUNNING
:在这种状态时,工做正在运行。对于 Worker
,这意味着 doWork()
方法已经被调用。SUCCEEDED
:当 doWork()
返回 Result.success()
时,工做进入这种最终状态。如今,当工做处于 RUNNING
状态,你能够调用 Result.retry()
。这将会致使工做退回 ENQUEUED
状态。工做也可能随时被 CANCELLED
。
若是工做运行的结果是 Result.failure()
而不是成功。它的状态将会以 FAILED
结束,所以,状态的完整流程图以下所示:
(来自:Working with WorkManager Android 开发者峰会 2018)
想看精彩的视频讲解,请查看 WorkManager Android 开发者峰会演讲。
这就是 WorkManager API 的基础知识。使用咱们刚刚介绍的代码片断,你如今就能够:
Worker
。WorkRequest
、Constraint
、启动输入和退出策略配置 Worker
的运行方式。WorkRequest
。WorkManager
在线程和保障运行方面的幕后工做。WorkInfo
监视你的 WorkRequest
的状态。想亲自试试 WorkManager 吗?查看 codelab,包含 Kotlin 和 Java 代码。
随着咱们继续更新本系列,请继续关注有关 WorkManager 主题的更多博客文章。 有什么问题或者你但愿咱们写到的东西吗?请在评论区告诉咱们!
感谢 Pietro Maggi
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。