注册

关于WorkManager需要知道的一切

背景

WorkManager 是google为Android推出Jetpack开发套件的成员之一,是一个持续化工作的推荐解决方案。 我们在开发过程中通常会遇到一些需要持续化的操作需求,例如,上传文件到服务端,Token过期刷新,定期更新服务端下发的配置等。

主要的用途是:

立即开始执行一个任务

有的任务必须立即开始执行,但这里可能会有个疑问,为什么立即开始执行的任务不直接写代码开始执行,要利用WorkManager呢?这主要归功于WorkManager的一些特性,例如根据约束安排任务,链式化任务等,让你代码写得更简洁,更好的扩展性。

需要长时间定期运行的任务

任务需要长时间定期运行的的情况,例如我们我需要每一个小时上传一次用户日志.

延迟任务

有的任务需要延后一段时间执行.


再继续讲解它如何使用之前先来说说它的几个特性.

特性

约束

提供一些约束条件去执行任务,当约束条件满足后才开始执行,例如,当连接上Wifi, 当设备有足够的电量等条件

可靠性

安排的任务保证能够顺利执行,因为WorkManager内部以SqLite存储任务的执行的情况,为执行的和执行失败的都会重新尝试.

加急任务

可能你会给WorkManager安排很多Task, 某些Task也许优先级比较高,需要立即执行,WorkManager提供加急的特性,可以尽早执行这类Task.

链式任务

某些Task可能需要顺序执行,也可能需要并行执行,WorkManager同样提供这类API满足需求

val continuation = WorkManager.getInstance(context)
    .beginUniqueWork(
        Constants.IMAGE_MANIPULATION_WORK_NAME,
        ExistingWorkPolicy.REPLACE,
        OneTimeWorkRequest.from(CleanupWorker::class.java)
    ).then(OneTimeWorkRequest.from(WaterColorFilterWorker::class.java))
    .then(OneTimeWorkRequest.from(GrayScaleFilterWorker::class.java))
    .then(OneTimeWorkRequest.from(BlurEffectFilterWorker::class.java))
    .then(
        if (save) {
            workRequest<SaveImageToGalleryWorker>(tag = Constants.TAG_OUTPUT)
        } else /* upload */ {
            workRequest<UploadWorker>(tag = Constants.TAG_OUTPUT)
        }
    )

线程的互操作性

无缝继承了Coroutines和RxJava的异步特性,在WorkManager也能使用这类异步的API


实战

说了这么多,下面我们来看看如何使用WorkManager

第一步,添加依赖

dependencies {
    def work_version = "2.8.1"

    // (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"

    // optional - Multiprocess support
    implementation "androidx.work:work-multiprocess:$work_version"
}

第二步,自定义Worker

这里我们需要定义,这个Task需要做什么,创建一个自定义Worker类,这里我们创建一个UploadWorker用于做一些后台上传的操作

class UploadWorker(appContext: Context, workerParams: WorkerParameters):
       Worker(appContext, workerParams) {
   override fun doWork(): Result {
       // 做一些上传操作
       uploadImages()
       // 代表任务执行成功
       return Result.success()
   }
}

在doWork中我们可以写上任务需要执行的代码,当任务结束后需要返回一个Result,这个Result有三个值

Result.success() 任务执行成功

Result.failure() 任务执行失败

Result.retry() 任务需要重试

第三步, 创建一个WorkRequest

当定义完需要做什么后我们需要创建一个WorkRequest去启动这个任务的执行。WorkManager提供了很多灵活的API用于定义任务的启动逻辑,例如是否执行一次还是周期性执行,它的约束条件是什么等。这里演示我们使用OneTimeWorkRequest.

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<UploadWorker>()
       .build()

第四步, 提交WorkRequest

当创建完成WorkRequest,我们需要把它交给WorkManager去执行

WorkManager
    .getInstance(myContext)
    .enqueue(uploadWorkRequest)

进阶

一次性任务

创建一个简单的一次性任务

val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)

如果需要增加一些配置如约束等可以使用builder

val uploadWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       // 添加额外配置
       .build()
加急工作

WorkManager 执行重要的工作,同时让系统更好地控制对资源的访问。

加急工作具有以下特点:

重要性:加急工作适合对用户重要或由用户启动的任务。

速度:加急工作最适合立即开始并在几分钟内完成的短任务。

配额:限制前台执行时间的系统级配额决定加急作业是否可以启动。

电源管理:电源管理限制(例如省电模式和打瞌睡模式)不太可能影响加急工作。

启动加急工作的方式也非常简单,可以直接调用setExpedited()设置该WorkRequest为一个加急任务

val request = OneTimeWorkRequestBuilder()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()

WorkManager.getInstance(context)
    .enqueue(request)

这里setExpedited会有一个参数OutOfQuotaPolicy,代表系统配额不足时候,把该任务作为一个普通任务对待.

周期性任务

我们有一些需求例如,备份应用数据,上传日志,下载一些应用配置,需要周期性进行,我们可以定义PeriodicWorkRequest去创建周期性的任务.


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

上面这段代码每一小时执行一次任务.

但是这个时间约束也不是固定的,这里定义的时间实际上是最小间隔时间,系统会根据当前系统的情况进行适当调整。

我们还可以定义flexInterval让间隔提前一点


val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
       1, TimeUnit.HOURS, // repeatInterval
       15, TimeUnit.MINUTES) // flexInterval
    .build()

这样我们执行任务的时间是repeatInterval - flexInterval,上面代码的任务会在1小时-15分钟的时候执行.

image.png

周期性任务遇上约束条件

当周期性任务遇到一些约束条件不满足的时候将会延迟执行,直到约束条件满足.

关于约束

WorkManager提供了下列的一些约束条件.

NetworkType 网络条件约束,例如只能在连接WIFI的情况下执行.

BatteryNotLow 非电量低约束,在有充足的电量的时候执行.

RequiresCharging 需要充电的时候执行约束

DeviceIdle 在设备无状态时候运行,这样不会对设备的效率产生影响.

StorageNotLow 当设备有有足够的存储空间时候运行

创建一个约束使用Contraints.Builder()并赋值给WorkRequest.Builder().

下面代码展示创建一个约束该任务只会在wifi并且在充电的时候执行.

val constraints = Constraints.Builder()
   .setRequiredNetworkType(NetworkType.UNMETERED)
   .setRequiresCharging(true)
   .build()

val myWorkRequest: WorkRequest =
   OneTimeWorkRequestBuilder<MyWork>()
       .setConstraints(constraints)
       .build()

延迟任务

如果你指定的任务没有约束或者约束已经满足,那么它会立即开始执行,如果想让它有个最少的延迟,可以指定一个最小的延迟执行时间.

下面这个例子展示设置最小10分钟后开始加入队列.

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .setInitialDelay(10, TimeUnit.MINUTES)
   .build()

上面展示的针对OneTimeWorkRequestBuilder同样也适用于PeriodicWorkRequest.

退避策略

如果一个任务失败返回Result.retry(), 你的任务可以在稍等进行重试,这种退避策略可以自定义,这里连个自定义的属性

退避延迟:退避延迟执行下次尝试任务的最少时间,通常我们自定义最少不能低于[MIN_BACKOFF_MILLIS]

退避策略:退避策略可以指定两种一个是LINEAR(线性)和EXPONENTIAL(幂等)

实际上每一个任务都有一个默认的退避策略,缺省的退避策略是EXPONENTIAL和30s的延迟,但是你可以自定义,下面是一个自定义的例子。

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

Tag任务

每一个任务都可以附加上一个标签,稍后可以使用这个标签找到该任务,取消它或者查看的执行进度. 如果你有一组任务,可以添加同一个标签,可以统一的操作它们。例如使用WorkManager.cancelAllWorkByTag(String)取消所有的任务,使用WorkManager.getWorkInfosByTag(String)返回一个任务信息列表查看当前任务的状态.

下面是一个展示给任务赋值一个标签

val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
   .addTag("cleanup")
   .build()

链式任务

WorkManager还提供了一种定义顺序执行任务或者并发执行任务的方式。

使用这种方式创建任务通过

WorkManager.beginWith(OneTimeWorkRequest)
WorkManager.beginWith(List<OneTimeWorkRequest>)

以上两个方式都会返回一个WorkContinuation.

WorkContinuation随后可以继续调用

then(OneTimeWorkRequest)
then(List<OneTimeWorkRequest>)

执行链式任务

最后可以调用enqueue()去执行你的工作链. 举个例子

WorkManager.getInstance(myContext)
   .beginWith(listOf(plantName1, plantName2, plantName3))
   .then(cache)
   .then(upload)
   .enqueue()

Input Mergers

一个父任务的执行结果可以传递给子任务,例如上面plantName1, plantName2, plantName3执行的结果可以传递

给cache任务,WorkManager使用InputMerger去管理这些多个父任务的输出结果到子任务.

这里有两种不同类型的的InputMerger

  • OverwritingInputMerger 从输入到输出增加所有的key,遇到冲突的情况,覆盖之前的key的值

  • ArrayCreatingInputMerger 从输入到输出增加所有的key,遇到冲突的情况进行创建数组

工作链的状态

当前面的任务阻塞住的时候后面的任务同样也是阻塞状态.

image.png

当前面的任务执行成功后,后面的任务才能继续开始执行

image.png

当一个任务失败进行重试的时候并不会影响并发的任务

image.png

一个任务失败,后面的任务也会是失败状态

image.png

对于取消也是这样

image.png

结语

总的来说WorkManager是一个非常好用的组件,它解决了一些曾经实现起来比较繁琐的功能,例如它的约束执行,我们可以等待有网络时候执行任务。我们利用周期性执行任务功能能够很方便的执行一些诸如刷新token, 定期日志上传等功能.


作者:屹森
链接:https://juejin.cn/post/7247404852945944635
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册