Kotlin Android WorkManager使用
1. 简介
WorkManager 是 Android Jetpack 中的一个库,它旨在帮助开发者在 Android 应用中轻松管理异步任务,这些任务即使在应用退出或设备重启后也能可靠地执行。Kotlin 作为 Android 开发的首选语言之一,与 WorkManager 结合使用可以极大地简化异步任务管理的代码。
2. 依赖添加
要在 Kotlin 项目中使用 WorkManager,首先需要在 build.gradle
文件中添加依赖。
在项目级别的 build.gradle
中确保 google()
仓库被包含:
allprojects {
repositories {
google()
jcenter()
}
}
在模块级别的 build.gradle
中添加 WorkManager 依赖。目前最新版本可在 Google Maven 仓库查看,这里以 2.7.1
版本为例:
dependencies {
def work_version = "2.7.1"
implementation "androidx.work:work-runtime-ktx:$work_version"
// 如果需要使用测试相关功能
androidTestImplementation "androidx.work:work-testing:$work_version"
}
同步项目后,就可以在 Kotlin 代码中使用 WorkManager 相关的类了。
3. 创建任务
3.1 定义 Worker 类
Worker 类是 WorkManager 执行任务的核心组件,它继承自 androidx.work.Worker
类。在 Kotlin 中,定义一个简单的 Worker 类如下:
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 在这里编写任务逻辑
// 例如执行网络请求、文件处理等
return Result.success()
}
}
在 doWork()
方法中,编写实际要执行的任务代码。任务执行完成后,根据执行结果返回 Result.success()
表示任务成功,Result.failure()
表示任务失败,Result.retry()
表示任务应该重试。
3.2 使用 OneTimeWorkRequest
OneTimeWorkRequest
用于创建一次性执行的任务请求。例如,我们要创建一个任务,在后台生成一个随机数并打印。
import android.content.Context
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.util.concurrent.TimeUnit
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val randomNumber = (1..100).random()
println("Generated random number: $randomNumber")
return Result.success()
}
}
fun scheduleOneTimeWork(context: Context) {
val workRequest: WorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
.setInitialDelay(1, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
在上述代码中,通过 OneTimeWorkRequest.Builder
创建任务请求,设置了任务类为 MyWorker
,并且通过 setInitialDelay
方法设置任务在 1 分钟后开始执行。最后使用 WorkManager.getInstance(context).enqueue(workRequest)
将任务加入队列开始执行。
3.3 使用 PeriodicWorkRequest
PeriodicWorkRequest
用于创建周期性执行的任务请求。例如,我们希望每 15 分钟执行一次任务来检查更新。
import android.content.Context
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.util.concurrent.TimeUnit
class UpdateCheckWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 检查更新的逻辑
return Result.success()
}
}
fun schedulePeriodicWork(context: Context) {
val workRequest: WorkRequest = PeriodicWorkRequest.Builder(UpdateCheckWorker::class.java, 15, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
在这个例子中,PeriodicWorkRequest.Builder
创建了一个每 15 分钟执行一次的任务请求,任务类为 UpdateCheckWorker
。注意,最小周期间隔是 15 分钟,这是 Android 系统的限制,以避免过于频繁的后台任务影响系统性能。
4. 任务约束
WorkManager 允许设置任务执行的约束条件,只有当这些条件满足时任务才会执行。
4.1 设置网络约束
假设我们有一个任务需要在设备连接到网络时执行。
import android.content.Context
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.util.concurrent.TimeUnit
class NetworkDependentWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 依赖网络的任务逻辑
return Result.success()
}
}
fun scheduleNetworkDependentWork(context: Context) {
val workRequest: WorkRequest = OneTimeWorkRequest.Builder(NetworkDependentWorker::class.java)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
在上述代码中,通过 Constraints.Builder
设置了任务需要在设备连接到网络时执行,setRequiredNetworkType(NetworkType.CONNECTED)
表示要求网络连接类型为已连接状态。
4.2 设置电量约束
如果任务需要在设备电量充足时执行,可以设置电量约束。
import android.content.Context
import androidx.work.BatteryNotLow
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.util.concurrent.TimeUnit
class BatterySufficientWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 任务逻辑
return Result.success()
}
}
fun scheduleBatterySufficientWork(context: Context) {
val workRequest: WorkRequest = OneTimeWorkRequest.Builder(BatterySufficientWorker::class.java)
.setConstraints(
Constraints.Builder()
.setRequiresBatteryNotLow(true)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
这里使用 setRequiresBatteryNotLow(true)
设置任务需要在设备电量不低时执行,BatteryNotLow
约束表示设备电量高于某个系统定义的阈值。
4.3 设置存储约束
对于一些需要有足够存储空间的任务,可以设置存储约束。
import android.content.Context
import androidx.work.StorageNotLow
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.util.concurrent.TimeUnit
class StorageSufficientWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 任务逻辑
return Result.success()
}
}
fun scheduleStorageSufficientWork(context: Context) {
val workRequest: WorkRequest = OneTimeWorkRequest.Builder(StorageSufficientWorker::class.java)
.setConstraints(
Constraints.Builder()
.setRequiresStorageNotLow(true)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
setRequiresStorageNotLow(true)
表示任务需要在设备存储不低时执行,StorageNotLow
约束确保设备有足够的可用存储空间。
5. 任务链
WorkManager 支持任务链的概念,即一个任务执行完成后触发另一个任务,以此类推。
5.1 顺序任务链
假设我们有两个任务,第一个任务生成一个字符串,第二个任务将这个字符串打印出来。
import android.content.Context
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkContinuation
import androidx.work.WorkManager
import androidx.work.WorkRequest
class GenerateStringWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val outputData = workDataOf("generatedString" to "Hello, WorkManager!")
return Result.success(outputData)
}
}
class PrintStringWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val inputData = inputData
val stringToPrint = inputData.getString("generatedString")
stringToPrint?.let { println(it) }
return Result.success()
}
}
fun scheduleSequentialWork(context: Context) {
val generateRequest: WorkRequest = OneTimeWorkRequest.Builder(GenerateStringWorker::class.java).build()
val printRequest: WorkRequest = OneTimeWorkRequest.Builder(PrintStringWorker::class.java).build()
val continuation: WorkContinuation = WorkManager.getInstance(context)
.beginWith(generateRequest)
.then(printRequest)
continuation.enqueue()
}
在上述代码中,GenerateStringWorker
任务生成一个字符串并通过 Result.success(outputData)
返回数据。PrintStringWorker
通过 inputData
获取前一个任务传递的数据并打印。WorkManager.getInstance(context).beginWith(generateRequest).then(printRequest)
创建了一个顺序任务链,先执行 GenerateStringWorker
,完成后执行 PrintStringWorker
。
5.2 并行任务链
有时候我们可能需要多个任务并行执行,然后再执行一个汇总任务。例如,有两个任务分别计算两个数字的平方,最后汇总结果。
import android.content.Context
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkContinuation
import androidx.work.WorkManager
import androidx.work.WorkRequest
import java.util.concurrent.TimeUnit
class SquareNumber1Worker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val result = 5 * 5
val outputData = workDataOf("square1" to result)
return Result.success(outputData)
}
}
class SquareNumber2Worker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val result = 3 * 3
val outputData = workDataOf("square2" to result)
return Result.success(outputData)
}
}
class SumSquaresWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val inputData = inputData
val square1 = inputData.getInt("square1", 0)
val square2 = inputData.getInt("square2", 0)
val sum = square1 + square2
println("Sum of squares: $sum")
return Result.success()
}
}
fun scheduleParallelWork(context: Context) {
val square1Request: WorkRequest = OneTimeWorkRequest.Builder(SquareNumber1Worker::class.java).build()
val square2Request: WorkRequest = OneTimeWorkRequest.Builder(SquareNumber2Worker::class.java).build()
val sumRequest: WorkRequest = OneTimeWorkRequest.Builder(SumSquaresWorker::class.java).build()
val continuation: WorkContinuation = WorkManager.getInstance(context)
.beginWith(listOf(square1Request, square2Request))
.then(sumRequest)
continuation.enqueue()
}
这里 SquareNumber1Worker
和 SquareNumber2Worker
并行执行,分别计算两个数字的平方。SumSquaresWorker
作为汇总任务,在前面两个任务都完成后,获取它们的结果并计算总和。WorkManager.getInstance(context).beginWith(listOf(square1Request, square2Request)).then(sumRequest)
创建了一个先并行后顺序的任务链。
6. 任务监听
WorkManager 提供了监听任务执行状态的机制,以便开发者在任务完成、失败或重试时执行相应的操作。
6.1 使用 LiveData 监听任务状态
通过 WorkManager.getWorkInfoByIdLiveData(workId)
方法可以获取一个 LiveData<WorkInfo>
,用于监听任务状态。
import android.content.Context
import androidx.lifecycle.Observer
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 任务逻辑
return Result.success()
}
}
fun monitorTaskWithLiveData(context: Context) {
val workRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).build()
val workId = workRequest.id
WorkManager.getInstance(context).enqueue(workRequest)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId)
.observeForever(object : Observer<WorkInfo?> {
override fun onChanged(workInfo: WorkInfo?) {
workInfo?.let {
when (it.state) {
WorkInfo.State.RUNNING -> println("Task is running")
WorkInfo.State.SUCCEEDED -> println("Task succeeded")
WorkInfo.State.FAILED -> println("Task failed")
WorkInfo.State.CANCELLED -> println("Task cancelled")
WorkInfo.State.BLOCKED -> println("Task is blocked")
WorkInfo.State.ENQUEUED -> println("Task is enqueued")
}
}
}
})
}
在上述代码中,首先创建并启动任务,获取任务的 workId
。然后通过 WorkManager.getInstance(context).getWorkInfoByIdLiveData(workId)
获取 LiveData<WorkInfo>
,并使用 observeForever
方法注册一个观察者,在任务状态变化时打印相应的状态信息。
6.2 使用 WorkManager 的回调监听任务状态
除了 LiveData
方式,还可以使用 WorkManager.enqueueUniqueWork
方法结合 WorkRequest
的 addListener
回调来监听任务状态。
import android.content.Context
import androidx.work.Constraints
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 任务逻辑
return Result.success()
}
}
fun monitorTaskWithCallback(context: Context) {
val workRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
.setConstraints(Constraints.Builder().build())
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"my_unique_work",
WorkManager.UpdatePolicy.REPLACE,
workRequest
)
.addListener({ workInfo ->
when (workInfo.state) {
WorkInfo.State.RUNNING -> println("Task is running")
WorkInfo.State.SUCCEEDED -> println("Task succeeded")
WorkInfo.State.FAILED -> println("Task failed")
WorkInfo.State.CANCELLED -> println("Task cancelled")
WorkInfo.State.BLOCKED -> println("Task is blocked")
WorkInfo.State.ENQUEUED -> println("Task is enqueued")
}
}, ContextCompat.getMainExecutor(context))
}
这里通过 WorkManager.enqueueUniqueWork
方法添加任务,并使用 addListener
方法注册一个回调,在任务状态变化时执行相应的操作。ContextCompat.getMainExecutor(context)
确保回调在主线程执行,以便可以更新 UI 等操作。
7. 数据传递
WorkManager 支持在任务之间传递数据,以及向任务传递输入数据和从任务获取输出数据。
7.1 向任务传递输入数据
假设我们有一个任务需要根据传入的数字进行计算。
import android.content.Context
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
class CalculationWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val inputData = inputData
val number = inputData.getInt("number", 0)
val result = number * 2
val outputData = Data.Builder().putInt("result", result).build()
return Result.success(outputData)
}
}
fun passInputDataToTask(context: Context) {
val inputData = Data.Builder().putInt("number", 5).build()
val workRequest: WorkRequest = OneTimeWorkRequest.Builder(CalculationWorker::class.java)
.setInputData(inputData)
.build()
WorkManager.getInstance(context).enqueue(workRequest)
}
在上述代码中,CalculationWorker
通过 inputData.getInt("number", 0)
获取传入的数字,进行计算后通过 Result.success(outputData)
返回结果。在 passInputDataToTask
方法中,通过 Data.Builder().putInt("number", 5)
创建输入数据,并通过 setInputData(inputData)
将数据传递给任务。
7.2 在任务链中传递数据
在任务链中,前一个任务的输出数据可以作为后一个任务的输入数据。
import android.content.Context
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkContinuation
import androidx.work.WorkManager
import androidx.work.WorkRequest
class GenerateDataWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val outputData = Data.Builder().putString("message", "Hello from first task").build()
return Result.success(outputData)
}
}
class ProcessDataWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
val inputData = inputData
val message = inputData.getString("message")
message?.let { println("Processed message: $it") }
return Result.success()
}
}
fun passDataInTaskChain(context: Context) {
val generateRequest: WorkRequest = OneTimeWorkRequest.Builder(GenerateDataWorker::class.java).build()
val processRequest: WorkRequest = OneTimeWorkRequest.Builder(ProcessDataWorker::class.java)
.setInputMerger(WorkRequest.MergeInputMerger.APPEND)
.build()
val continuation: WorkContinuation = WorkManager.getInstance(context)
.beginWith(generateRequest)
.then(processRequest)
continuation.enqueue()
}
这里 GenerateDataWorker
生成数据并返回,ProcessDataWorker
通过 setInputMerger(WorkRequest.MergeInputMerger.APPEND)
方法将前一个任务的输出数据合并到自己的输入数据中,从而获取并处理前一个任务传递的消息。
8. 任务取消与清理
有时候需要取消正在执行或排队的任务,WorkManager 提供了相应的方法来实现。
8.1 取消任务
通过任务的 workId
可以取消任务。
import android.content.Context
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit
class MyWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
override fun doWork(): Result {
// 任务逻辑
return Result.success()
}
}
fun cancelTask(context: Context) {
val workRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java).build()
val workId = workRequest.id
WorkManager.getInstance(context).enqueue(workRequest)
// 稍后取消任务
WorkManager.getInstance(context).cancelWorkById(workId)
}
在上述代码中,首先创建并启动任务,获取任务的 workId
。然后通过 WorkManager.getInstance(context).cancelWorkById(workId)
方法取消任务。如果任务正在执行,WorkManager 会尝试停止任务;如果任务在排队,会将其从队列中移除。
8.2 清理任务
WorkManager 会在设备重启等情况下自动重新调度任务。如果希望在应用卸载等情况下清理所有任务,可以使用 WorkManager.cancelAllWork()
方法。
import android.content.Context
import androidx.work.WorkManager
fun cleanUpTasks(context: Context) {
WorkManager.getInstance(context).cancelAllWork()
}
这个方法会取消所有正在执行和排队的任务,并且会清除 WorkManager 内部存储的任务信息,确保在应用重新安装后不会重新调度之前的任务。
9. 高级用法
9.1 使用 WorkerFactory
在某些情况下,可能需要为 Worker
类提供依赖注入等功能,这时可以使用 WorkerFactory
。
首先定义一个自定义的 WorkerFactory
:
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.WorkerFactory
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
class MyWorkerFactory @AssistedInject constructor(
@Assisted private val context: Context,
@Assisted private val workerParameters: WorkerParameters
) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): Worker? {
return when (workerClassName) {
MyWorker::class.java.name -> MyWorker(context, workerParameters)
else -> null
}
}
}
然后在应用启动时设置这个 WorkerFactory
:
import android.app.Application
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: MyWorkerFactory
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
}
这样,当 WorkManager 创建 MyWorker
实例时,就会使用我们自定义的 WorkerFactory
,可以在 WorkerFactory
中进行依赖注入等操作。
9.2 使用 RxJava 与 WorkManager 结合
对于习惯使用 RxJava 的开发者,可以将 RxJava 与 WorkManager 结合使用。例如,通过 RxWorker
类将 Worker
转换为 RxJava 的 Observable
。
首先添加 RxJava 相关依赖:
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
implementation 'androidx.work:work-rxjava3:2.7.1'
然后定义一个 RxWorker
:
import android.content.Context
import androidx.work.WorkerParameters
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.schedulers.Schedulers
import androidx.work.RxWorker
class MyRxWorker(context: Context, workerParams: WorkerParameters) : RxWorker(context, workerParams) {
override fun createWork(): Observable<Result> {
return Observable.fromCallable {
// 任务逻辑
Result.success()
}.subscribeOn(Schedulers.io())
}
}
这样就可以像使用 RxJava 的 Observable
一样使用 MyRxWorker
,例如进行链式操作、订阅等。
10. 常见问题与解决
10.1 任务不执行
如果任务没有按照预期执行,首先检查任务的约束条件是否满足。例如,如果设置了网络约束,确保设备处于网络连接状态。同时,检查 Worker
类中的 doWork()
方法是否有异常抛出,异常可能导致任务失败并停止执行。可以在 doWork()
方法中添加日志打印来排查问题。
10.2 任务重复执行
在某些情况下,可能会发现任务被重复执行。这可能是因为没有正确设置任务的唯一性。如果希望任务只执行一次,可以使用 WorkManager.enqueueUniqueWork
方法,并根据需求设置 UpdatePolicy
,例如 WorkManager.UpdatePolicy.REPLACE
表示如果有相同的任务在队列中,替换它;WorkManager.UpdatePolicy.KEEP
表示保留现有任务,不添加新任务。
10.3 数据传递问题
如果在任务之间传递数据出现问题,检查数据的键值对是否正确设置和获取。确保在设置输入数据和获取输出数据时使用相同的键。同时,注意 setInputMerger
方法的使用,特别是在任务链中传递数据时,不同的合并策略可能会影响数据的获取。
通过以上对 Kotlin 中 Android WorkManager 的详细介绍和代码示例,开发者可以深入理解并熟练运用 WorkManager 来管理 Android 应用中的异步任务,提高应用的性能和用户体验。无论是简单的一次性任务,还是复杂的任务链和周期性任务,WorkManager 都提供了强大且灵活的解决方案。在实际开发中,根据应用的具体需求,合理设置任务约束、监听任务状态、处理数据传递等操作,能够让应用在后台任务管理方面更加高效和可靠。