WorkManager
Scheduling tasks with work manager is part of Android Jetpack. The WorkManager API makes it easy to specify deferrable, asynchronous tasks and when they should run. These APIs let you create a task and hand it off to WorkManager to run immediately or at an appropriate time.
WorkManager chooses the appropriate way to run your task based on such factors as the device API level and the app state. If WorkManager executes one of your tasks while the app is running, WorkManager can run your task in a new thread in your app's process. If your app is not running, WorkManager chooses an appropriate way to schedule a background task--depending on the device API level and included dependencies, WorkManager might use JobScheduler
, Firebase JobDispatcher
, or AlarmManager
. You don't need to write device logic to figure out what capabilities the device has and choose an appropriate API; instead, you can just hand your task off to WorkManager and let it choose the best option. WorkManager is intended for tasks that require a guarantee that the system will run them even if the app exits, like uploading app data to a server. It is not intended for in-process background work that can safely be terminated if the app process goes away
Classes and concepts:
The WorkManager API uses several different classes. In some cases, you'll need to subclass one of the API classes. These are the most important WorkManager classes:
Worker: specifies what task you need to perform. The WorkManager APIs include an abstract Worker class. You extend this class and perform the work here.
WorkRequest: represents an individual task. At a minimum, a WorkRequest object specifies which Worker class should perform the task. However, you can also add details to the WorkRequest object, specifying things like the circumstances under which the task should run. Every WorkRequest has an autogenerated unique ID; you can use the ID to do things like cancel a queued task or get the task's state. WorkRequest is an abstract class; in your code, you'll be using one of the direct subclasses, OneTimeWorkRequest or PeriodicWorkRequest.
- WorkRequest.Builder: a helper class for creating WorkRequest objects. Again, you'd be using one of the subclasses, OneTimeWorkRequest.Builder or PeriodicWorkRequest.Builder.
- Constraints: specifies restrictions on when the task should run (for example, "only when connected to the network"). You create the Constraints object with Constraints.Builder, and pass the Constraints to the WorkRequest.Builder before creating the WorkRequest.
WorkManager: enqueues and manages the work requests. You pass your WorkRequest object to WorkManager to enqueue the task. WorkManager schedules the task in such a way as to spread out the load on system resources, while honoring the constraints you specify.
WorkInfo: contains information about a particular task. The WorkManager provides a LiveData for each WorkRequest object. The LiveData holds a WorkInfo object; by observing that LiveData, you can determine the current status of the task, and get any returned values after the task finishes.
Typical workflow:
Suppose that you're writing an app that needs to periodically compress its stored images. You want to use the WorkManager APIs to schedule the image compression. In this case, you don't particularly care when the compression happens; you want to set up the task and forget about it.
First, you would define your Worker class, and override its doWork() method. Your worker class specifies how to perform the operation, but doesn't have any information about when the task should run.
class CompressWorker(context : Context, params : WorkerParameters)
: Worker(context, params) {
override fun doWork(): Result {
// Do the work here--in this case, compress the stored images.
// In this example no parameters are passed; the task is
// assumed to be "compress the whole library."
myCompress()
// Indicate success or failure with your return value:
return Result.success()
// (Returning Result.retry() tells WorkManager to try this task again
// later; Result.failure() says not to try again.)
}
}
Next, you create a OneTimeWorkRequest object based on that Worker, then enqueue the task with WorkManager:
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build()
WorkManager.getInstance().enqueue(compressionWork)
WorkManager chooses an appropriate time to run the task, balancing such considerations as the load on the system, whether the device is plugged in, and so on. In most cases, if you don't specify any constraints, WorkManager runs your task right away. If you need to check on the task status, you can get a WorkInfo object by getting a handle to the appropriate LiveData
WorkManager.getInstance().getWorkInfoByIdLiveData(compressionWork.id)
.observe(lifecycleOwner, Observer { workInfo ->
// Do something with the status
if (workInfo != null && workInfo.state.isFinished) {
// ...
}
})
Task constraints:
If you wish, you can specify constraints on when the task should run. For example, you might want to specify that the task should only run when the device is idle, and connected to power. In this case, you'd need to create a OneTimeWorkRequest.Builder object, and use that builder to create the actual OneTimeWorkRequest:
// Create a Constraints object that defines when the task should run
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
// Many other constraints are available, see the
// Constraints.Builder reference
.build()
// ...then create a OneTimeWorkRequest that uses those constraints
val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
.setConstraints(myConstraints)
.build()
Then pass the new OneTimeWorkRequest
object to WorkManager.enqueue()
, as before. WorkManager considers your constraints when finding a time to run the task.
Canceling a Task:
You can cancel a task after you enqueue it. To cancel the task, you need its work ID, which you can get from the WorkRequest object. For example, the following code cancels the compressionWork request from the previous section:
val compressionWorkId:UUID = compressionWork.getId()
WorkManager.getInstance().cancelWorkById(compressionWorkId)
WorkManager makes its best effort to cancel the task, but this is inherently uncertain--the task may already be running or finished when you attempt to cancel it. WorkManager also provides methods to cancel all tasks in a unique work sequence, or all tasks with a specified tag, also on a best-effort basis.