Scheduling Reliable Background Work on Android with WorkManager
Learn how to schedule deferrable background tasks on Android with WorkManager, including defining workers, applying constraints, observing results, and debugging with adb.
24 May 2026, 08:52 UTC

When an app needs to sync data, upload logs, or run maintenance, the work must survive app restarts, device reboots, and system‑initiated process kills. Relying on plain AsyncTask or Service can lead to lost work when the system reclaims resources. Android WorkManager provides a backward‑compatible, constraint‑aware API that automatically picks the best available scheduler (JobScheduler, Firebase JobDispatcher, or AlarmManager) based on the device’s API level and current state.
Core concepts: WorkRequest, Worker, and Constraints
A WorkRequest describes what should be done and when it may run. There are two types:
OneTimeWorkRequest– runs once.PeriodicWorkRequest– repeats with a defined interval.
The actual work lives in a Worker subclass. You override doWork() to perform the task and return Result.success(), Result.retry(), or Result.failure().
Constraints let you declare the conditions under which the work may execute, such as:
- Network type (e.g.,
NetworkType.UNMETERED) - Device charging state
- Storage availability
- Battery not low
If a constraint is not satisfied, WorkManager defers execution until it becomes true.
Defining one‑off and periodic work with unique naming
To avoid duplicate work, you can enqueue requests with a unique name and an existing work policy. The example below schedules a daily log‑upload task that will not be replaced if a similar request is already pending or running.
// LogUploadWorker.kt
class LogUploadWorker(appContext: Context, workerParams: WorkerParameters)
: Worker(appContext, workerParams) {
override fun doWork(): Result {
// Replace with real upload logic
val success = uploadLogs()
return if (success) Result.success() else Result.retry()
}
private fun uploadLogs(): Boolean {
// Simulate work; in practice use HttpURLConnection, OkHttp, etc.
Log.d("LogUploadWorker", "Attempting log upload")
// Return true on success
return true
}
}
Enqueue the worker:
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val uploadRequest = PeriodicWorkRequestBuilder<LogUploadWorker>(24, TimeUnit.HOURS)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
"daily_log_upload",
ExistingPeriodicWorkPolicy.KEEP,
uploadRequest
)
The ExistingPeriodicWorkPolicy.KEEP tells WorkManager to leave the existing request untouched if one with the same name is already present.
Observing results and chaining work
WorkManager exposes the status of work via LiveData or Kotlin Flow. You can observe outcomes to update the UI or trigger follow‑up actions.
WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(uploadRequest.id)
.observe(viewLifecycleOwner) { workInfo ->
if (workInfo != null && workInfo.state.isFinished) {
when (workInfo.state) {
WorkInfo.State.SUCCESS -> Log.i("WorkManager", "Upload succeeded")
WorkInfo.State.FAILURE -> Log.e("WorkManager", "Upload failed")
else -> Unit
}
}
}
If you need to run a cleanup task after the upload, you can chain work:
val cleanup = OneTimeWorkRequestBuilder<CleanupWorker>().build()
WorkManager.getInstance(context)
.beginWith(uploadRequest)
.then(cleanup)
.enqueue()
Debugging and testing WorkManager
WorkManager relies on the system scheduler, so you can verify that a job is registered and persists across reboots using adb.
- Enable Developer Options and USB debugging on the device.
- Run the app to enqueue the worker.
- List scheduled jobs:
adb shell cmd jobscheduler list
Look for a job ID associated with your package; the ID will remain after a reboot (adb reboot).
To see real‑time execution, filter Logcat for the worker’s tag:
adb logcat | grep LogUploadWorker
If the work does not run, check whether battery optimizations are interfering:
- Open Settings → Apps → [Your app] → Battery → Battery optimization.
- Set the app to “Not optimized” (or add it to a whitelist) and test again.
Note: On some manufacturers aggressive power‑saving profiles may still delay or skip execution unless the app is whitelisted.
Trade‑offs and limitations
WorkManager guarantees eventual execution, not exact timing. Because it defers to the system’s scheduler, you cannot rely on it for real‑time or UI‑thread work. If you need to fire at a precise moment (e.g., an alarm at 08:00 AM), use AlarmManager.setExactAndAllowWhileIdle or the exact APIs introduced in API 19.
Additionally, Doze mode and app standby buckets can defer network‑constrained work until the device exits a maintenance window. The practical way to confirm that constraints are respected is to toggle the device’s battery‑optimization setting for your app and observe whether the job runs sooner or later via Logcat.
Actionable guidance
- Always define constraints that match the true requirements of your work (network, charging, storage).
- Test with
adb shell cmd jobscheduler listbefore and after a reboot to verify persistence. - Monitor execution with
WorkManager.getWorkInfoByIdLiveDataorgetWorkInfosByTagLiveDataand log the outcome. - If you observe consistent skips, check the device’s battery‑optimization settings and consider asking users to whitelist your app.
By combining clear constraints, unique naming, and observable status, WorkManager lets you defer background work reliably across the wide variety of Android devices in use today.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.