Using Android WorkManager to Schedule Reliable Background Tasks
Learn how to defer work that survives app restarts and respects battery constraints with a concrete Worker example and verification steps.
18 Dec 2025, 20:22 UTC

The problem: work that must survive app restarts
Imagine you need to upload a log file to a server whenever the device is plugged in and connected to Wi‑Fi. If you start the upload from an Activity or Service, the task can be killed when the user leaves the app, the system enters Doze mode, or the device reboots. Writing your own retry logic and persisting state quickly becomes error‑prone.
How WorkManager solves it
WorkManager is a Jetpack library that abstracts the best‑available background scheduler on the device. On API 23+ it uses JobScheduler; on older devices with Google Play services it falls back to Firebase JobDispatcher; otherwise it uses an AlarmManager‑based implementation. The library persists work definitions in an internal SQLite database, so they survive process kills and reboots. You define a unit of work by extending Worker and overriding doWork(). You then enqueue a OneTimeWorkRequest (or PeriodicWorkRequest) with constraints such as required network type or charging state. WorkManager observes those constraints and executes the work when they are met.
Key guarantees
- Execution is guaranteed even if the app is force‑stopped or the device reboots.
- Built‑in exponential back‑off and retry policies.
- Support for chaining: you can make
WorkBrun only afterWorkAsucceeds. - Observable results via
LiveData,Kotlin Flow, orListenableFuture.
Worked example: a charging‑and‑Wi‑Fi logger
The following Kotlin code defines a simple worker that writes a timestamp to the app’s internal files directory when the device is charging and connected to an unmetered network.
class LoggerWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val timestamp = System.currentTimeMillis()
val file = File(applicationContext.filesDir, "upload_log.txt")
file.appendText("Upload attempt at $timestamp\n")
// Indicate success; WorkManager will consider the work finished.
return Result.success()
}
}
To enqueue the work with the required constraints:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // Wi‑Fi only
.setRequiresCharging(true)
.build()
val uploadRequest = OneTimeWorkRequestBuilder<LoggerWorker>
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
Place the enqueue call somewhere early in your app’s lifecycle, for example in onCreate() of your Application subclass or a startup Activity.
Verifying that the work ran
After launching the app, you can confirm execution without needing any special permissions beyond ADB access:
- Connect a device or start an emulator.
- Run
adb logcatand filter for your app’s tag (if you add aLog.dinsidedoWork()) or simply watch for file creation. - Plug the device in and connect to a Wi‑Fi network.
- Wait a few moments; WorkManager will schedule the work as soon as the constraints are satisfied.
- Check the log file:
adb shell run-as cat files/upload_log.txt. You should see a timestamp line. - Alternatively, inspect the persisted work:
adb shell dumpsys jobscheduler(API 23+) or open the Device File Explorer in Android Studio, navigate todata/data//files/and view the WorkManager database underdatabases/androidx-work.
To test the library’s respect for system health constraints, toggle Battery Saver (adb shell cmd battery saver enable) or force Doze (adb shell dumpsys deviceidle force-idle) and observe that the work still runs once the constraints are met and the system exits those states.
Trade‑offs and limitations
While WorkManager provides a robust abstraction, there are a few practical limits to keep in mind:
- On devices running Android 6.0 (API 23) or lower **without** Google Play services, WorkManager defaults to an AlarmManager‑based scheduler. This fallback is less precise and may fire later than the exact constraint satisfaction time.
- Work intended to run longer than ~10 minutes should be marked as foreground via
setForegroundAsync(); otherwise the system may kill the worker to preserve battery. - The library adds a small method count (~60 KB) and introduces a SQLite dependency; for extremely simple one‑off tasks a plain
AlarmManagermight be lighter, but you lose the guaranteed persistence and constraint handling.
Actionable closing
If your app needs to defer work that must survive app restarts, respect battery‑saving policies, and retry automatically, WorkManager is the recommended starting point. Define your unit of work in a Worker, apply constraints that reflect the real‑world conditions (charging, network, storage), enqueue it with WorkManager.getInstance().enqueue(), and verify execution via logcat or the persisted database. Begin with the example above, adjust the constraints to match your use case, and monitor the fallback behavior on older devices to ensure the timing precision meets your product requirements.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.