SuriDevs Logo
From Foreground Services to WorkManager: Adapting Auto App Backup for Modern Android

From Foreground Services to WorkManager: Adapting Auto App Backup for Modern Android

By Sagar Maiyad  Oct 24, 2025

40% of my users on Android 14 were crashing. Every. Single. Day.

The crash logs all said the same thing: ForegroundServiceStartNotAllowedException. My auto backup service that had worked fine for four years just... stopped working. Google had finally won the war against background services, and I was collateral damage.

The Old Way Worked Great (Until It Didn't)

Our All Backup & Restore app had a simple approach since 2020: start a foreground service on boot, listen for app installs, backup immediately. Users loved the "real-time" feel.

class BootCompleteReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == "android.intent.action.BOOT_COMPLETED") {
            context?.startForegroundService(serviceIntent)
        }
    }
}

The service would show a persistent notification, register broadcast receivers for PACKAGE_ADDED and PACKAGE_REPLACED, and backup any new app within seconds.

Then the complaints started rolling in.

Death by a Thousand Paper Cuts

First came the battery complaints. "Why is there always a notification?" "App drains battery even when I'm not using it." Fair points, but the app needed to run constantly to catch installs. Trade-offs, right?

Then Android 14 landed and everything broke.

Fatal Exception: ForegroundServiceStartNotAllowedException
Unable to start service - not allowed to start foreground service

I added FOREGROUND_SERVICE_TYPE_DATA_SYNC. Still crashed. Added the new permissions. Still crashed. Added restart logic when the system killed the service.

Still. Crashed.

I spent two weeks fighting this. Samsung phones killed the service. Xiaomi's "battery optimization" murdered it. OnePlus had its own special way of breaking things. Every manufacturer had custom restrictions, and my service was losing the fight on all fronts.

Comparison between old foreground service approach with persistent notifications vs new WorkManager approach with no persistent notifications

The Moment I Gave Up (In a Good Way)

Mid-2025. I was looking at production data:

  • Significant portion of Android 14 users experiencing service crashes
  • Noticeable battery consumption from the background service
  • User retention dropping - people were uninstalling

I'd been fighting the platform for months. Maybe it was time to stop.

The realization hit me: real-time backup detection was a nice-to-have, not a must-have. Did users actually need their apps backed up within seconds of installation? Or would an hour delay be totally fine?

Spoiler: an hour delay was totally fine.

Polling Instead of Listening

The new approach is embarrassingly simple. Instead of running a service 24/7 listening for broadcasts, I just... check every hour if anything changed.

val workRequest = PeriodicWorkRequestBuilder<MonitorWorker>(
    1, TimeUnit.HOURS
)
.setConstraints(
    Constraints.Builder()
        .setRequiresBatteryNotLow(true)
        .setRequiresStorageNotLow(true)
        .build()
)
.build()

WorkManager.getInstance(context)
    .enqueueUniquePeriodicWork(
        "app_monitor_work",
        ExistingPeriodicWorkPolicy.KEEP,
        workRequest
    )

The MonitorWorker wakes up, compares the current installed apps against a saved list, detects any new or updated apps, and schedules backup workers for them. Then it goes back to sleep. No persistent notification. No fighting the system.

Battery usage comparison showing dramatic improvement from foreground service to WorkManager approach

The Change Detection Logic

fun detectChanges(lastKnown: List<AppInfo>, current: List<AppInfo>): List<AppInfo> {
    val changes = mutableListOf<AppInfo>()
    val lastKnownMap = lastKnown.associateBy { it.packageName }

    for (app in current) {
        val oldApp = lastKnownMap[app.packageName]
        when {
            oldApp == null -> changes.add(app.copy(isNew = true))
            app.versionCode > oldApp.versionCode -> changes.add(app.copy(isUpdate = true))
        }
    }
    return changes
}

I store the app list in SharedPreferences (serialized with Kotlin Serialization). Every hour, load the old list, get current apps from PackageManager, compare, save the new list. Simple.

The 20-App Update Incident

Two weeks after shipping the WorkManager version, I got ANR reports. Turns out Google Play sometimes updates 20+ apps at once (usually after a fresh install or when the phone was off for a while). My code was scheduling 20 backup workers simultaneously.

The fix was dumb but effective - stagger the workers:

changedApps.forEachIndexed { index, app ->
    val delaySeconds = index * 20L
    // App 0: immediate, App 1: 20s, App 2: 40s, etc.
    scheduleBackup(app, delaySeconds)
}

Now at most 1-2 workers run concurrently. ANRs gone.

That Weird Coroutine Bug

I had a subtle bug that took forever to track down. Some backups would just... fail silently. The worker reported success, but the backup didn't exist.

The culprit:

// This was the problem
fun saveBundleApp() = CoroutineScope(Dispatchers.IO).async {
    // Orphaned scope - not connected to parent lifecycle
}

When WorkManager cancelled the worker, this orphaned coroutine kept running... for about 2 seconds before it got garbage collected. The fix:

// This works properly
suspend fun saveBundleApp(): Result = coroutineScope {
    try {
        val result = async { /* backup logic */ }.await()
        result
    } catch (e: Exception) {
        Result.failure(e)
    }
}

Structured concurrency exists for a reason. I learned that reason the hard way.

WorkManager scheduling visualization showing periodic app monitoring and automated backup triggering

What Changed for Users

Before (Foreground Service):

  • Battery drain: Noticeable daily impact
  • Persistent notification: Always there, always annoying
  • Backup delay: Immediate (0-5 seconds)
  • Crashes on Android 14: Constant
  • App store rating: Trending down

After (WorkManager):

  • Battery drain: ~70% reduction
  • Notification: Only during active backup
  • Backup delay: Up to 1 hour (nobody complained)
  • Crashes: 95% reduction
  • Rating: Stabilized, battery complaints stopped

Visual representation of migration journey from complex foreground service to clean WorkManager solution

What I Wish I'd Known Earlier

The "real-time" requirement was in my head, not in user feedback. I assumed people needed instant backups. They didn't. They just wanted backups to happen automatically at some point. An hour delay? Nobody noticed.

Polling feels wrong to programmers. "Why check every hour when you could just listen for events?" Because listening requires running constantly, and Android doesn't want you running constantly. Polling wakes up, does work, goes to sleep. Android likes that.

WorkManager constraints are underrated. Setting setRequiresBatteryNotLow(true) means my workers don't run when the phone is dying. Users never see "this backup app drained my battery" in low-battery situations because... the app isn't running in low-battery situations.

Test on real phones from real manufacturers. Emulators don't have Samsung's "battery optimization" or Xiaomi's "battery saver" or OnePlus's "deep sleep mode." These features murder background processes in creative ways. If it works on the emulator but fails on a real Samsung phone, the Samsung phone is the ground truth.

If You're Still Using Foreground Services

Ask yourself: does this genuinely need to run 24/7? Or did I just build it that way because broadcast receivers were easy?

The migration pattern is straightforward:

  1. Save your state to persistent storage
  2. Schedule a periodic WorkManager worker
  3. On each run, compare current state to saved state
  4. Process any changes
  5. Save the new state
  6. Sleep

Add constraints so you don't run during low battery or low storage. Stagger work if you might have bursts of activity. Use structured concurrency.

The platform has made its stance clear: efficient apps get to run, inefficient apps get killed. WorkManager is the peace treaty between your app and the system. Sign it.


I spent months fighting Android's background restrictions. Then I spent two weeks implementing WorkManager and wondering why I hadn't done it sooner. The app is more stable, uses less battery, and I'm not debugging manufacturer-specific foreground service murders anymore.

Sometimes the right answer isn't working harder. It's working with the system instead of against it.

Android WorkManager Technical Deep Dive Background Services

Author

Sagar Maiyad
Written By
Sagar Maiyad

Sagar Maiyad - Android developer specializing in Kotlin, Jetpack Compose, and modern Android architecture. Sharing practical tutorials and real-world development insights.

View All Posts →

Latest Post

Latest Tags