androidengineers.Book a session

Core Android Components Deep Dive

Service Types & Foreground Rules

article25 minMedium

Services are one of the most misused Android components. The right choice between Started, Bound, and Foreground service — and knowing when to skip Services entirely — is critical for reliability and battery life.

The Three Service Types

Started Service

Started with startService() or startForegroundService(). Runs until it calls stopSelf() or you call stopService().

class SyncService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Do work, then stop
        doSync()
        stopSelf(startId)  // Use startId version to avoid stopping if re-started
        return START_NOT_STICKY  // Don't restart if killed
    }
    override fun onBind(intent: Intent?) = null  // Not a bound service
}

Return values for onStartCommand:

  • START_STICKY: Restart after kill, null intent (good for media)
  • START_NOT_STICKY: Don't restart (good for one-shot work)
  • START_REDELIVER_INTENT: Restart with original intent (good for file downloads)

Bound Service

Clients bind with bindService(). Service lives as long as at least one client is bound. Useful for ongoing communication (music controls, location streaming).

class LocationService : Service() {
    private val binder = LocalBinder()

    inner class LocalBinder : Binder() {
        fun getService() = this@LocationService
    }

    override fun onBind(intent: Intent) = binder

    fun getLastLocation(): Location? = fusedLocationClient.lastLocation.result
}

// Client
class MyActivity : AppCompatActivity() {
    private var locationService: LocationService? = null
    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, binder: IBinder) {
            locationService = (binder as LocationService.LocalBinder).getService()
        }
        override fun onServiceDisconnected(name: ComponentName) {
            locationService = null
        }
    }

    override fun onStart() {
        super.onStart()
        bindService(Intent(this, LocationService::class.java), connection, BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
    }
}

Foreground Service

A service that shows a persistent notification and is not subject to background execution limits. Required for user-visible long-running operations.

class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification()
        // Must call within 5 seconds of startForegroundService() or get ANR
        startForeground(NOTIFICATION_ID, notification)
        return START_STICKY
    }
}

Foreground Service Types (Android 14+)

Android 14 requires declaring the foregroundServiceType in the manifest AND at runtime:

<service
    android:name=".MusicService"
    android:foregroundServiceType="mediaPlayback" />
// Android 14+ requires passing the type at runtime too
ServiceCompat.startForeground(
    this,
    NOTIFICATION_ID,
    notification,
    ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
)
TypeUse caseRequired permission
mediaPlaybackAudio/video playbackNone extra
locationReal-time location updatesACCESS_FINE_LOCATION
cameraActive camera sessionCAMERA
microphoneRecording audioRECORD_AUDIO
dataSyncUploading/downloadingNone extra
healthFitness trackingBODY_SENSORS

The 5-Second Rule

When you call startForegroundService(), your service has exactly 5 seconds to call startForeground(). If it doesn't, the system throws an ANR-style ForegroundServiceDidNotStartInTimeException.

// ❌ Risky: heavy work before startForeground
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    initializeHeavyResources()  // could take >5s
    startForeground(ID, notification)  // too late!
    return START_STICKY
}

// ✅ Call startForeground first, then do heavy work
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    startForeground(ID, buildNotification())  // immediate
    CoroutineScope(Dispatchers.IO).launch {
        initializeHeavyResources()  // now safe
    }
    return START_STICKY
}

When NOT to Use a Service

TaskBetter alternative
One-time background workWorkManager
Periodic background workWorkManager PeriodicWorkRequest
Coroutine work in a ViewModelviewModelScope.launch
Work while app is visibleCoroutine in a lifecycle-aware scope
In-process background tasksDispatchers.IO coroutine

Services add complexity, consume memory, and are subject to strict OS restrictions. Use WorkManager unless you specifically need a foreground service with visible UI (notification).

Key Takeaways

ConceptRule
Started serviceFire-and-forget; must call stopSelf() when done
Bound serviceLifecycle tied to clients; unbind in onStop()
Foreground serviceShow notification; call startForeground() within 5s
Android 14+Declare foregroundServiceType in manifest AND at runtime
Default alternativeUse WorkManager for most background work

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Service Types & Foreground Rules | Android System Design | Android Engineers