Android使用 WorkManager 调度任务

WorkManager深入解析
本文全面解析WorkManager在Android开发中的应用,涵盖入门指南、进阶技巧、高级概念及线程处理,助您掌握任务调度核心。

一、前言

    WorkManager属于Android Jetpack的一部分,通过WorkManager API可以轻松第调度可延迟的任务,即使是那些在应用退出或者设备重启时仍需要运行的任务。

WorkManager的主要功能

  • 最高可向后兼容到 API 14
    • 在 API 23 及以上级别的设备上运行使用 JobScheduler实现
    • 在 API 14-22 的设备上运行结合使用 BroadcastReceiver 和 AlarmManager实现
  • 支持网络可用性或充电状态等工作约束
  • 支持调度一次性任务和周期行的任务
  • 支持监控和管理计划任务
  • 将任务链接起来
  • 确保任务执行,即使应用退出或者设备重启也会执行任务
  • 遵循低电耗模式等省电功能

WorkManager的主要用途
    WorkManager主要用于可延迟执行的任务,并且在应用退出或者设备重启时必须能可靠运行的任务。例如:

  • 向服务端发送日志或者分析数据
  • 定期同步应用数据到服务器

WorkManager的局限性
    WorkManager 不适用于应用进程结束时能够安全终止的运行中后台任务,也不适用于需要立即执行的任务。因此,也不要滥用WorkManager。

本文测试Demo源码下载

二、WorkManager入门指南

    WorkManager 的使用主要有以下几点操作:

  1. 将 WorkManager 添加到您的 Android 项目中(添加依赖)
  2. 创建后台任务
  3. 配置运行任务的方式和时间
  4. 将任务提交给系统

2.1 添加依赖

    WorkManager 是属于 Android Jetpack 的一部分,使用需要在应用模块下的build.gradle引入相关的依赖。

dependencies {
      def work_version = "2.3.4"

        // (仅Java使用,如果你的项目是基于Java开发的,引入这个)
        implementation "androidx.work:work-runtime:$work_version"

        // (Kotlin + coroutines,如果你的项目基于Kotlin开发,引入这个)
        implementation "androidx.work:work-runtime-ktx:$work_version"

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

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

        // 可选 - 测试支持
        androidTestImplementation "androidx.work:work-testing:$work_version"
      }

说明:关于最新的WorkManager依赖,请参考: WorkManager 库版本历史

2.2 创建后台任务

    任务使用 Worker 类定义,类内部的doWork() 方法在 WorkManager 提供的后台线程上同步运行。要创建后台任务,扩展 Worker 类并重写 doWork() 方法即可。

示例:

class CheckSystemWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
   
   

    override fun doWork(): Result {
   
   

        Log.e("TEST", "Checking system。。。。。。。。")
        Thread.sleep(3000)
        Log.e("TEST", "Checking system done.")
        return Result.success()
    }

}

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

使用 Worker 定义任务之后,使用 WorkRequest 定义任务的运行方式和时间。任务可以是一次性的,也可以是周期性的。对于一次性 WorkRequest,请使用 OneTimeWorkRequest,对于周期性工作,请使用 PeriodicWorkRequest

示例:

val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>().build()

2.4 将任务提交给系统

定义 WorkRequest 之后,现在可以通过 WorkManagerenqueue() 方法来调度它,将任务提交给系统进行管理和运行。

示例:

WorkManager.getInstance(applicationContext).enqueue(checkSystem)

三、WorkManager 进阶

3.1 设定任务请求

    前面介绍了如何创建简单的任务请求(WorkRequest)并将其放到系统队列中。在这个章节中,将会详细介绍任务和任务请求的各种设定。

3.1.1 任务约束

    可以通过向任务请求中添加 Constraints 指明任务运行的条件。Constraints通过 Constraints.Builder 创建,里面定义了设定不同约束的成员函数。例如电池非低电量、需要网络等等。

示例:

val constraints = Constraints.Builder()
    .setRequiresBatteryNotLow(true)
    .setRequiredNetworkType(NetworkType.CONNECTED) // 需要联网
    .build()

val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
    .setConstraints(constraints)
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)

上面的示例中,为任务添加了需要网络连接的约束,在联网状态下,任务放入队列之后将会安排执行,但是如果当前设备断开了网络,并不会立刻执行,而是在队列中等到网络恢复,网络恢复后,等待的任务会继续执行。

    如果为一个任务设定了多个约束,当所有约束都满足时任务才会执行。如果运行期间任务的约束不满足,将会停止任务的执行,等到约束满足时,系统将会尝试恢复执行任务。

3.1.2 初始延迟

    如果任务没有设定约束或者所有约束都满足,任务有可能会立即执行。如果不想任务立即执行,可以设定一个最短的延迟时间。需要设定最短初始延迟时间,需要向任务请求中调用setInitialDelay()接口设定。

说明:为什么说最短延迟时间呢?因为将任务提交给系统,本来就有可能出现等待延迟,同理,设定了初始延迟时间的任务即使到了延迟时间,任务也有可能需要等待资源。换句话说,任务开始执行的时间 >= 设定的初始延迟时间

val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
    .setInitialDelay(3000, TimeUnit.MILLISECONDS)
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)

上面的例子可以发现,当任务请求提交到系统时,并不会立即执行,而是等待一段时间才会开始执行。

3.1.3 重试和退避政策

    如果任务执行失败,需要让 WorkManager 重新尝试执行任务,可以从工作器(Worker)中返回 Result.retry(),然后,系统会根据退避延迟时间和政策重现调整调度任务。

    设置任务的退避政策通过调用 WorkerRequest.BuildersetBackoffCriteria()) 接口实现,设置的值包括退避延迟时间的增长方式重试任务最短等待时间重试等待时间的单位。退避延迟时间的增长方式在 BackoffPolicy 中定义,默认是 BackoffPolicy.EXPONENTIAL (指数增长)。

提示:退避延迟时间增长方式有两种

  • 线性增长(BackoffPolicy.LINEAR:线性函数增长,即 f ( n ) = t n f(n)=tn f(n)=tn,其中 t 为重试任务最短时间, n 为重试次数( n ≥ 1 n\geq1 n1)。
  • 指数增长(BackoffPolicy.EXPONENTIAL:2的指数倍增长,即 f ( n ) = t ∗ 2 n f(n)=t*2^n f(n)=t2n,其中 t 为重试任务最短时间, n 为重试次数( n ≥ 1 n\geq1 n1)。
// 定义Worker
var times: Int = 0
var lastTime = 0L

class CheckDiskWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
   
   
    override fun doWork(): Result {
   
   
        Log.e("TEST", "Checking disk。。。。。。。。")
        if(times > 0) {
   
   
            Log.w("TEST", "Retry ${
     
     times}, Delay is ${
     
     (System.currentTimeMillis() - lastTime) / 1000} s")
        }
        Thread.sleep(3000)
        lastTime = System.currentTimeMillis()
        if(times < 5) {
   
   
            Log.e("TEST", "Checking disk failure.")
            times++
            // 需要重试,比如操作失败了,返回Result.retry()
            return Result.retry()
        }
        Log.e("TEST", "Checking disk done.")
        times = 0
        // 返回成功时,将不会再重试
        return Result.success()
    }
}
// 设定任务请求的重试和回避策略
val checkDisk = OneTimeWorkRequestBuilder<CheckDiskWorker>()
    .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkDisk)

注意事项
1. OneTimeWorkRequest重试的最小时间间隔必须是OneTimeWorkRequest.MIN_BACKOFF_MILLISOneTimeWorkRequest.MAX_BACKOFF_MILLIS,超出这个范围的将会取临界值;
2. 因为任务提交给系统到真是执行并不是实时的,所以,真实的延长时间不一定是预期的(有可能会出现后一次重试时间间隔比前一次的还要短),但是一定是大于等于预期值。

3.1.4 定义任务的输入/输出

    将任务提交给系统,但是需要和任务进行交互,就需要实现任务的输入/输出。将需要的数据以参数的形式传入到任务(输入),任务执行完毕后返回结果(输出)。传入参数通过 WorkerReuqestsetInputData() 接口传入。输入和输出值以键值对的形式存储在 Data 对象中。在 Worker 中通过getInputData()接口获取输入参数。类似地,从 Worker 中返回数据(输出)也是通过 Data 对象,并通过Result 对应带有返回数据参数的方法返回结果,例如:Result.success(Data)Result.failure(Data)

说明:Data 对象可以通过原始的 Data.Builder 进行构建,也可以使用 worker 库提供的内联函数 workDataOf(vararg pairs: Pair<String, Any?>) 进行构建,原理一样

示例:

// 定义Worker
class CheckNetworkWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
   
   
    override fun doWork(): Result {
   
   
        Log.e("TEST", "Checking network。。。。。。。。")
        Log.d("TEST", "Checking network, params......")
        for((key, value) in inputData.keyValueMap) {
   
   
            Log.d("TEST", "$key ---- $value")
        }
        Thread.sleep(3000)
        Log.e("TEST", "Checking network done.")
        return Result.success(Data.Builder().let {
   
   
            it.putInt("code", 200)
            it.putString("msg", "Network is fine")
            it.build()
        })
    }
}

// 构建WorkerRequest,并提交系统
val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
    .setInputData(Data.Builder().let {
   
   
        // Data以Builder的方式进行构建,传入的是键值对
        it.putString("operator", "Owen")
        it.putString("description", "Check network state")
        it.build()
    })
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)

注意:按照设计要求,Data 对象应该很小,大小上限为 10KB,值可以是字符串、基元类型或数组变体。如果需要将更多数据传入和传出工作器,应该将数据放在其他位置,例如 存储或者数据库。

    向任务输入数据已经知道如何实现了,可是在程序中如何获取任务输出的数据呢?主要有以下几步:

  1. 通过 WorkManagergetWorkInfoByXXXLiveData() 获取 WorkRequestLiveData<WorkInfo> 对象
  2. LiveData<WorkInfo> 对象添加观察者
  3. 在观察者的 onChanged() 方法中获取返回的数据
  4. WorkRequest 提交到系统

获取LiveData<WorkInfo> 对象的接口有多个,详细请参考:WorkManager,使用合适的方法获取,在后续章节也会详细介绍。

那么,以上的例子可以改为:

val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
    .setInputData(Data.Builder().let {
   
   
        // Data以Builder的方式进行构建,传入的是键值对
        it.putString("operator", "Owen")
        it.putString("description", "Check network state")
        it.build()
    })
    .build()

WorkManager.getInstance(applicationContext)
    .getWorkInfoByIdLiveData(checkNetwork.id)
    // observe方法是添加观察者,这个方法有两个参数,第一个参数是LifecycleOwner,可以传入
    .observe(this, object : Observer<WorkInfo> {
   
   
    override fun onChanged(t: WorkInfo?) {
   
   
        // 任务执行完毕之后,会在这里获取到返回的结果
        if(t?.state == WorkInfo.State.SUCCEEDED) {
   
   
            for((key, value) in t.outputData!!.keyValueMap) {
   
   
                Log.d("TEST", "Out Data $key ---- $value")
            }
        }
    }
})
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)

参考资料:

  1. LifecycleOwner
  2. Observer

3.1.5 标记任务

    可以为任意 WorkRequest 对象分配标记字符串(tag),实现对任务按逻辑进行分组,这样就可以对使用特定标记的所有任务执行操作。例如,WorkManager.cancelAllWorkByTag(String) 会取消所有使用特定标记的任务,而 WorkManager.getWorkInfosByTagLiveData(String) 会返回具有该标记的所有任务的 LiveData 和状态列表。

    在构建 WorkRequest对象时,通过 WorkRequest.Builder.addTag(String) 向任务添加标记.

示例:

val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
    .setInputData(Data.Builder().let {
   
   
        // Data以Builder的方式进行构建,传入的是键值对
        it.putString("operator", "Owen")
        it.putString("description", "Check network state")
        it.build()
    })
    .addTag("networkWork"
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值