Android Jetpack 之 WorkManager

1. 前言

最近在 API 30 中准备使用 IntentService,结果发现在 API 30 中,IntentService 已经是 deprecated 状态了。关于 IntentService 的使用和原理可以参考我之前写的:IntentService 的使用和源码分析。Google 建议使用 WorkManager 或者 JobIntentService 来替代 IntentService。

使用 WorkManager API 可以轻松地调度即使在应用退出或设备重启时仍应运行的可延迟异步任务,这是 Google 对 WorkManager 的概括。WorkManager 旨在用于可延迟运行(即不需要立即运行)并且在应用退出或设备重启时必须能够可靠运行的任务。例如:向后端服务发送日志或分析数据、定期将应用数据与服务器同步。

2. WorkManager 的使用

目前 WorkManager 的最新版本是 2.3.4 ,以下 Demo 将使用 Kotlin ,所以添加如下依赖:

// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:2.3.4"

2.1 WorkManager 的基本使用

使用 WorkManager 创建工作任务,任务是使用 Worker 类定义的。doWork() 方法在 WorkManager 提供的后台线程上同步运行。要创建后台任务,需要继承 Worker 类并重写 doWork() 方法:

class UploadWorker (context: Context, workerParams: WorkerParameters): Worker(context, workerParams) {
    override fun doWork(): Result {
        Log.e("zzw", "UploadWorker  doWork()    start")
        Thread.sleep(6000)
        Log.e("zzw", "UploadWorker  doWork()    end")
        return Result.success()
    }
}

WorkManager 为协程提供了支持,不要扩展 Worker,而应扩展 CoroutineWorker:

class DownLoadWorker(context: Context, workerParameters: WorkerParameters) :
    CoroutineWorker(context, workerParameters) {
    override suspend fun doWork(): Result {
        Log.e("zzw", "DownLoadWorker doWork()    start")
        delay(10000)
        Log.e("zzw", "DownLoadWorker doWork()    end")
        return Result.success()
    }
}

从 doWork() 返回的 Result 会通知 WorkManager 任务:

Result.success():任务执行成功,如果有下一个任务会接着执行下一个任务
Result.failure():已失败,整个任务链就此中断
Result.retry():需要稍后重试,通过 WorkManager 根据重试策略尝试执行该任务

执行:

WorkManager.getInstance(this).enqueue(uploadWorkRequest)
WorkManager.getInstance(this).enqueue(downloadWorkRequest)

2.2 WorkRequest

添加到 WorkManager 中的就是 WorkRequest对象,通过 WorkRequest 对象可以给 Worker 附加额外的功能。

2.2.1 约束条件

向工作添加 Constraints,以指明工作何时可以运行。例如,您可以指定工作应仅在设备空闲且接通电源时运行:

val constraints = Constraints.Builder()
                .setRequiresDeviceIdle(true)
                .setRequiresCharging(true)
                .build()
val downloadWorkRequest =
                OneTimeWorkRequestBuilder().setConstraints(constraints).build()

有关所支持约束的完整列表,请参阅 Constraints.Builder 。

如果指定了多个约束,任务将仅在满足所有约束时才会运行。如果在任务运行期间某个约束不再得到满足,则 WorkManager 将停止工作器。当约束继续得到满足时,系统将重新尝试执行该任务。

2.2.2 初始延迟

如果工作没有约束,或者工作加入队列时所有约束均已得到满足,则系统可能会选择立即运行任务。如果不希望任务立即运行,则可以将工作指定为在经过一段最短初始延迟时间后再启动。

val downloadWorkRequest =
                OneTimeWorkRequestBuilder()
                    .setInitialDelay(10, TimeUnit.SECONDS).build()

2.2.3 重试和退避政策

如果您需要让 WorkManager 重新尝试执行的任务,可以从工作器返回 Result.retry()。然后,系统会根据默认的退避延迟时间和政策重新调度您的工作。退避延迟时间指定了重试工作前的最短等待时间。退避政策定义了在后续重试过程中,退避延迟时间随时间以怎样的方式增长;默认情况下按 EXPONENTIAL 延长。

val downloadWorkRequest =
                OneTimeWorkRequestBuilder()
                    .setBackoffCriteria(
                        BackoffPolicy.LINEAR,
                        OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                        TimeUnit.MILLISECONDS
                    ).build()

2.2.4 任务的输入/输出

任务可能需要数据以输入参数的形式传入,或者将数据返回为结果。输入和输出值以键值对的形式存储在 Data 对象中。

val workData: Data = Data.Builder() // Data大小需要控制在10k内
                .putString("name", "zzw") // 参数可以为任意类型
                .putInt("age", 101)
                .build()
val downloadWorkRequest =
                OneTimeWorkRequestBuilder()
                    .setInputData(workData).build()

doWork 方法中:

    override suspend fun doWork(): Result {
        Log.e("zzw", "DownLoadWorker doWork()    start")
        var name = inputData.getString("name")
        var age = inputData.getInt("age", 0)
        Log.e("zzw", "DownLoadWorker doWork()    name=$name age=$age")
        delay(10000)
        Log.e("zzw", "DownLoadWorker doWork()    end")
        var resultData = Data.Builder().putString("name", "zzw") // 参数可以为任意类型
            .putInt("age", 101)
            .build()
        return Result.success(resultData)
    }

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

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

2.3 工作状态和观察工作

2.3.1 工作状态

在工作的整个生命周期内,它会经历多个不同的 State:

a.如果有尚未完成的前提性工作,工作处于 BLOCKED State。
b.如果工作能够在满足 Constraints 和时机条件后立即运行,将被视为处于 ENQUEUED。
c.当工作器在活跃执行时,其处于 RUNNING State。
d.返回 Result.success() 的工作器会被视为 SUCCEEDED。这是一种终止 State;只有 OneTimeWorkRequest 可以进入这种 State。
相反,返回 Result.failure() 的工作器会被视为 FAILED。这也是一种终止 State;只有 OneTimeWorkRequest 可以进入这种 State。所有依赖工作也会被标记为 FAILED,并且不会运行。
当您明确取消尚未终止的 WorkRequest 时,它会进入 CANCELLED State。所有依赖工作也会被标记为 CANCELLED,并且不会运行。

2.3.2 观察工作

将工作加入队列后,您可以通过 WorkManager 检查其状态。相关信息在 WorkInfo 对象中提供,包括工作的 id、标签、当前 State 和任何输出数据。可以通过以下三种方式之一来获取 WorkInfo:

a. 对于特定的 WorkRequest,可以利用 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 来通过 WorkRequest id 检索其 WorkInfo;
b. 对于指定的标记,可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象;
c. 对于唯一工作名称,可以利用 WorkManager.getWorkInfosForUniqueWork(String) 或 WorkManager.getWorkInfosForUniqueWorkLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象。

WorkManager.getInstance(this).enqueue(downloadWorkRequest)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(downloadWorkRequest.id)
                .observe(this,
                    Observer {
                        if (it != null && it.state == WorkInfo.State.SUCCEEDED) {
                            Log.e("zzw", "MainActivity   Work finished!")
                        }
                    })

2.4 链接工作

可以使用 WorkManager 创建工作链并为其排队。工作链用于指定多个关联任务并定义这些任务的运行顺序。当需要以特定的顺序运行多个任务时,这尤其有用。为了创建工作链,您可以使用 WorkManager.beginWith(OneTimeWorkRequest) 或 WorkManager.beginWith(List),这会返回 WorkContinuation 实例。然后,可以使用 WorkContinuation 通过 WorkContinuation.then(OneTimeWorkRequest) 或 WorkContinuation.then(List) 添加从属 OneTimeWorkRequest。每次调用 WorkContinuation.then(...) 都会返回一个新的 WorkContinuation 实例。如果添加了 OneTimeWorkRequest 的 List,这些请求可能会并行运行。最后,可以使用 WorkContinuation.enqueue() 方法为 WorkContinuation 链排队。

WorkManager.getInstance(this).beginWith(downloadWorkRequest).then(uploadWorkRequest)
                .enqueue()

创建 OneTimeWorkRequest 链时,需要注意以下几点:

从属 OneTimeWorkRequest 仅在其所有父级 OneTimeWorkRequest 都成功完成(即返回 Result.success())时才会解除阻塞(变为 ENQUEUED 状态)。如果有任何父级 OneTimeWorkRequest 失败(返回 Result.failure()),则所有从属 OneTimeWorkRequest 也会被标记为 FAILED。如果有任何父级 OneTimeWorkRequest 被取消,则所有从属 OneTimeWorkRequest 也会被标记为 CANCELLED。

2.5 重复性工作

应用有时可能需要定期运行某些任务。例如,可能要定期备份数据、下载应用中的新鲜内容,或者上传日志到服务器。将 PeriodicWorkRequest 用于这种需要定期执行的任务。PeriodicWorkRequest 无法链接。如果您的任务需要链接任务,请考虑 OneTimeWorkRequest。

            val downloadWorkRequest =
                PeriodicWorkRequestBuilder(1, TimeUnit.HOURS)
                    .build()

            WorkManager.getInstance(this)
                .enqueue(downloadWorkRequest)

重复间隔定义为重复之间的最短时间。工作器的确切执行时间取决于在工作请求中使用的约束,也取决于系统进行的优化。

注意:可以定义的最短重复间隔是 15 分钟。

2.6 自定义 WorkManager 配置和初始化

默认情况下,当应用启动时,WorkManager 使用适合大多数应用的合理选项自动进行配置。如果需要进一步控制 WorkManager 管理和调度工作的方式,可以通过自己初始化 WorkManager 自定义 WorkManager 配置。

从下面代码可以看出,WorkManager 默认采用 ContentProvider 的方法进行初始化:

Android Jetpack 之 WorkManager_第1张图片

如果移除默认初始化程序 ,则执行以下操作:

去除 WorkManager 在应用启动的时候进行初始化,可以减少应用的启动时间,按照需要进行初始化即可,或者结合 Jetpack 的 App Startup 进行优化。关于 App Startup 可以查看另外一篇文章:Android Jetpack 之 App Startup。

你可能感兴趣的:(Android)