Running background tasks on Android: Using WorkManager
One of our core features in our app are publishing a new video message, and we’d like to do all the stuffs in background mode once the users fill in the message text, record the video and publish this message, what component can we use to achieve that? On android if you’d like to execute a tasks in background on Android, no problem, the easiest way is to use
Service, however, running tasks in the background might run out a device’s limited resources, the Android OS introduces several limitations on background tasks in the recent releases, so the
Service component might not fit our requirement. Any other choices? Here is a flowchart to help you:
JobScheduler would be my first option, but which had support only on Android 5.0 and above. How about
AlarmManager? It supports the version before Android 5.0 but fails on Android 6.0 new Doze mode. If you have more complex requirements, it will become much harder for you to decide 😣.
Finally, Google introduced WorkManager for background execution as part of Jetpack, WorkManager will choose underlying implementation
Firebase JobScheduler components based on the capabilities the device has, you don’t need to determine and worry about the logic yourself 👏👏👏, let’s get started:
Our requirement is to publish a new video message in background, we have two APIs for message creation
POST /message and media upload
PUT /messgage/media, respectively. And we need to retry if the API fails, limit only one media upload task is executed and all media upload tasks will be executed sequentially (only starting the next task once the previous one finishes).
The flow will be
Let’s see how WorkManager works for us. We define the POJO for the new message first:
Now you create the worker class for message creation (for sending POST /message request), that will be subclass of Worker and implement
To start this worker, you need to create WorkRequest that specifies which Worker class should perform the task.
There are some core features of how WorkManager works here:
- For Worker, you just need to define how to perform your tasks, you don’t need to specify when the task should run. WorkManager will handle this for you.
- All the operations in Worker are performed in background, so you can send any API requests or database query in sync mode. (For sure, you still can perform those operations in async mode)
- You specify the following return value to notify the WorkManager: a) Result.SUCCESS indicates the worker finish normally. b) Result.RETRY tells WorkManager to try this task again later. c) Result.FAILURE: finish and don’t try again.
- For input data, you CAN’T not pass anything except for primitive type, i.e. you can not pass
NewMessageobject directly to Worker via the old way f
putParcelable(). So what can we do is that we store the
NewMessageobject somewhere and we pass
jobIdstring as key to Worker for finding the
- You can setup some constraints regard to when to execute your task under the certain criteria, such as requiring network connection here.
- When you return
Result.RETRY, the WorkManager will follow the backoff strategy and criteria you specify, there are two modes for back off strategy: exponential or linear, and you also might specify the retry backoff delay.
Media file upload
For media upload, we create another Worker for this task since we need different scheduling sequence.
For media upload task we need to limit the execution of the tasks to be only one at a time, that is we upload the file one by one, even if we can send several new message simultaneously. How can we do that? WorkManager supports chained tasks, you can enqueued those tasks must be run in order. For our case, we use unique work sequences, that is chained tasks with a given name, the WorkManager permits only one work sequence with that given name at a time. Unique work sequence is for the case that you have a task which shouldn’t be enqueued multiple times.
For chained tasks in WorkManager, if one of the tasks fails (i.e. returning
Result.FAILURE in Worker), then all the sequence ends since that it regards as a chain.
But it’s not supposed to be in our case, if the upload task fails and should not retry due to any kind of reasons (such as file doesn’t exist), we need a way to skip the current task and execute the next task in the sequence rather than failing all tasks in this chain. So we need the worker to return
Result.SUCCESS instead of
Result.FAILURE, and find another way to notify the task fails (for UI to display error) so that keep the sequence running the remaining tasks without terminating the entire sequence.
Behaviors in different scenarios
I’ve tested the above implementation (
OneTimeWorkRequest) on my HTC U11 device and the behaviors are the following:
- App in background: all tasks will be processed as in foreground.
- App kill, force stop app, app crash and never open again: all tasks will be processed as in background.
- For the above cases and reopen the app again: it’s still the same, it won’t be affected whether you reopen the app or don’t.
- Reboot the device: The WorkManager reschedule the tasks once the device booted.
In this post, you can see that the way we implement a background task by WorkManager is very easy and straightforward despite it’s still in alpha right now (at the time I wrote this article), it provides a useful way to execute the tasks that require guaranteed execution and are deferrable.