Creating battery friendly background tasks on Android

widget

Concurrency on Android implies running heavy tasks on a separate thread (as opposed to the main thread) to make a user’s experience of your app better.

Background tasks are different from other conversations about concurrency. Some applications may require certain tasks to be run when the app is running in the background or not running (some of these tasks include downloading updates or syncing data with the one on the server). Solutions like Alarm Manager + Broadcast receivers, JobSchedulers, and Firebase JobDispatcher already exist and have their own unique use cases. In this article, I will try to explain WorkManager, which is part of Android Jetpack. In my opinion, it is an easier solution.

Complexity running background tasks before WorkManager

As mentioned earlier, there are APIs for doing background work. However, using these APIs incorrectly can have a negative effect on our user’s battery and cause them to, eventually, uninstall our app. To help with this problem, Android introduced battery-saving features (i.e., battery saver and doze mode) to properly manage the power usage of apps.

For developers, this solution has introduced more problems because we would need to work with the APIs that do background work and the battery saving features while trying to maintain backward compatibility in the apps we are building. Our code could become very complex which could be a problem in a few months when a new team member tries to go through the codebase. Thankfully, WorkManager came to save the day.

WorkManager

WorkManager provides a generic solution for background work by automatically taking battery and API levels into consideration for us. Other important things to note about WorkManager are:

  • Compatible with API levels 14+
  • Manages deferrable background work
  • Manages guaranteed background work
  • Not useful for tasks that should run at a precise time – Use Alarm manager instead

With WorkManager, you might also choose to run tasks under certain constraints. For example, you might want a particular task to run when the device is idle, charging, and there is internet connectivity.

Deferrable & Guaranteed work

Deferrable

“Must it run now?” Deferrable work is a task that can be run later and still be useful. For example, fetching data from the server so that a user gets fresh content the next time they open the app is deferrable work. However, sending an instant message is not deferrable work because the message has to be sent immediately.

Guaranteed

Guaranteed work is work that runs even if the android device is restarted.

Creating battery-friendly tasks

When creating battery-friendly background tasks, there are three things that should ultimately be considered.

  • Make sure the work isn’t using excessive CPU or disk.
  • Try to optimize your code to properly use these resources. Try to schedule the longest possible delay for repeating tasks.
  • Be sure it’s important to use the API before implementing it. The most battery friendly background work is the kind that doesn’t exist.

Using WorkManager

To use the WorkManager API, define your Worker class, implement Worker, and write the code for the task in the doWork() function.

class FetchWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params){
override fun doWork(): Result{
val res = fetchAllData() // the actualt work to be done
if(res){
return Result.success()
}else{
return Result.failure()
}
}
}

To do this work, we need to build a WorkRequest. This helps us to configure when and how our tasks should be run. When doing this, we can also add some constraints like this:

val constraints = Constraints.Builder() //if you want to add constraints
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.build()
// create a OneTimeWorkRequest that uses the constraints we just built
// OneTimeWorkRequestBuilder is used for non-repeating work
val fetchDataRequest = OneTimeWorkRequestBuilder<FetchWorker>()
.setConstraints(constraints)
.build()

After creating the WorkRequest, the final step would be to enqueue the request like this:

WorkManager.getInstance().enqueue(fetchDataRequest)

That’s all we need to do. Now, we can be certain that the task will be performed in the future when all three specified constraints are met.

For further reading about the WorkManager API, you can go to the android documentation.

Attributions:
  1. undefined by undefined