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,nullintent (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
)
| Type | Use case | Required permission |
|---|---|---|
mediaPlayback | Audio/video playback | None extra |
location | Real-time location updates | ACCESS_FINE_LOCATION |
camera | Active camera session | CAMERA |
microphone | Recording audio | RECORD_AUDIO |
dataSync | Uploading/downloading | None extra |
health | Fitness tracking | BODY_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
| Task | Better alternative |
|---|---|
| One-time background work | WorkManager |
| Periodic background work | WorkManager PeriodicWorkRequest |
| Coroutine work in a ViewModel | viewModelScope.launch |
| Work while app is visible | Coroutine in a lifecycle-aware scope |
| In-process background tasks | Dispatchers.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
| Concept | Rule |
|---|---|
| Started service | Fire-and-forget; must call stopSelf() when done |
| Bound service | Lifecycle tied to clients; unbind in onStop() |
| Foreground service | Show notification; call startForeground() within 5s |
| Android 14+ | Declare foregroundServiceType in manifest AND at runtime |
| Default alternative | Use WorkManager for most background work |