Background tasks on Android: Using WorkManager

WorkManager is for deferrable background task that needs a guaranteed execution, let’s get started an example to explan how to use it

Overview

One of the 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 you’d like to execute a tasks in background, 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 unless you use Foreground Service, which continues running when the user isn’t interacting with the app. 

Any other choices? Here is a flowchart to help you:

The JobScheduler would be my first option, but which had support only on Android 5.0 and above. AlarmManager? It supports the version before Android 5.0 but fails on Android 6.0 new Doze mode (What’s Doze mode? In a nutshell, android disables the network, WIFI, GPS, any syncs after the user turns off the screen and resumes until the user turns on again). How about JobDispatcher? It supports the version even lower than 5.0 but It requires Google Play Service, it will fail almost for hundreds of Chinese devices.

There are different situations for Android versions, having Google Play Service or not, 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, a wrapper for all above options, WorkManager will choose underlying implementation JobScheduler, AlarmManager or Firebase JobDispatcher components based on the capabilities the device has, you don’t need to determine and worry about the logic yourself 👏👏👏, let’s get started:

What is WorkManager?

WorkManager is for deferrable background task that needs a guaranteed execution, it will take care of the logic to start your task under different situations, even if you close the app or reboot the device…etc.. WorkManager provides flexible APIs and additional benefits over other job schedulers:

  • Support the required constraints, such as network, storage, charging status.
  • Support one time job or periodic tasks
  • Chaining of a serial tasks to run sequentially or parallelly.
  • Handle API level compatibility back to API level 14.
  • It works even without Google Play Service.

To start, consider the following diagram, which shows how WorkManager works and architecture, you follow the steps:

  1. You create Worker subclass that implement doWork() method for your job,
  2. Then create WorkRequest that specifies the Worker and all arguments.
  3. The last step is to enqueue your job by passing the WorkRequest to WorkManager instance.

Requirement

Our requirement is to publish a new video message in background, we have two APIs for message creation POST /message and media upload PUT /message/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

Get Started

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 doWork() method:

To start this worker, you need to create WorkRequest that specifies which Worker class should perform the task, with any input data, constraints, or backoff strategy.

Now you can see that there are some core features of how WorkManager components work here:

  1. 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. It chooses the best scheduler for you, stores all input data and update worker status.
  2. 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)
  3. You specify the following return value to notify the WorkManager:
    1. Result.SUCCESS indicates the worker finish normally.
    2. Result.RETRY tells WorkManager to try this task again later.
    3. Result.FAILURE: finish and don’t try again.
  4. For input data, you CAN’T not pass anything except for primitive type, i.e. you can not pass NewMessage object directly to Worker via the old way putSerializable() or putParcelable() . So what can we do is that we store the NewMessage object somewhere and pass jobId string as key to Worker for finding the NewMessage to send or you and serialize/deserialize json string to pass data.
  5. You can set up some constraints regard to when to execute your task under the certain criterias, such as requiring network connection here.
  6. When you return Result.RETRY, the WorkManager will follow the backoff strategy and criterias 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.

Return Result.FAILURE in chain tasks will terminate the entire 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 change the worker to return Result.SUCCESS instead of Result.FAILURE, find another way to notify the task fails (for UI to display error) and 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.

Final

In this post, you can see that the way we implement a background task by using 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.


Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑