Skip to content
All posts
June 29, 20263 min read

Android Background Work: WorkManager vs Coroutines vs Services

Three tools for background work, three different jobs. Picking the wrong one means battery drain, broken behavior on OEM devices, or work that silently stops running.

AndroidKotlinWorkManagerArchitecture
Share:

Background work is one of the most misunderstood areas of Android development. Developers reach for Services when they should use WorkManager, use WorkManager for things that should be Coroutines, and forget that OEM battery optimizations will kill both.

Here's the actual breakdown.

The three categories

Immediate, short-lived work — triggered by user action, needs to complete before the user navigates away. Use Coroutines.

Deferrable, guaranteed work — needs to survive process death, app kills, device restarts. Use WorkManager.

Long-running foreground work — active while the user can see a persistent notification. Use ForegroundService.

Each category has one right answer. The confusion comes from using the wrong tool for the category.

Coroutines for immediate work

If the user tapped a button and you need to make an API call, save data, or process something — Coroutines in the ViewModel scope.

kotlin
fun onSaveButtonClicked() {
    viewModelScope.launch {
        val result = repository.saveData(currentData)
        _uiState.update { it.copy(saved = result.isSuccess) }
    }
}

code
viewModelScope
is cancelled when the ViewModel is cleared. That's intentional — the work is tied to the UI. If the user leaves, stop.

Don't use WorkManager for this. WorkManager is for work that must complete even if the user kills the app. Using it for a button tap is over-engineering that adds latency and complexity.

WorkManager for guaranteed work

Syncing data, uploading photos, processing reports — work that must complete eventually, even after a restart.

kotlin
class SyncWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            repository.syncData()
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) Result.retry() else Result.failure()
        }
    }
}

// Schedule it
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS)
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
    )
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "data_sync",
    ExistingPeriodicWorkPolicy.KEEP,
    syncRequest
)

WorkManager persists work to a database and reschedules across restarts. It also respects battery optimization and Doze mode — it defers work when the device is idle rather than fighting the OS.

ForegroundService for long-running work

Location tracking, music playback, file downloads. Work that the user knows is active (visible notification), that runs continuously.

kotlin
class LocationTrackingService : Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification()
        startForeground(NOTIFICATION_ID, notification)

        serviceScope.launch {
            locationManager.locationUpdates()
                .collect { location -> repository.saveLocation(location) }
        }

        return START_STICKY
    }
}

ForegroundService requires a visible notification — that's the contract with the user. It stays alive while the notification is visible. The OS will not kill it without user action.

The OEM problem

Stock Android behavior and Samsung/Xiaomi/Huawei behavior are different. Aggressive battery optimization on OEM devices kills background work — including WorkManager — far more aggressively than AOSP.

For critical background work: prompt users to whitelist your app from battery optimization.

code
PowerManager.isIgnoringBatteryOptimizations()
lets you check.
code
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
opens the settings screen.

It's an awkward UX. It's also the only reliable solution.

Decision tree

  • User tapped something → Coroutines in viewModelScope
  • Must complete eventually, can be deferred → WorkManager
  • Runs continuously with user awareness → ForegroundService
  • Scheduled, periodic, needs guarantee → WorkManager with periodic request
  • Response to system event (network change, boot) → WorkManager with constraints

The most common mistake: reaching for WorkManager because "it's more reliable" for work that's actually immediate and doesn't need to survive process death. It's not more reliable — it's slower to start and more complex to debug. Use the simplest tool that fits.

Share:
S

Sudarshan Chaudhari

AI Systems Builder / Product Engineer

Bangkok, Thailand

Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

MyFamilyTracker

Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus

Apps tagged with this