Services in Android Explained with Examples (Started, Bound and Foreground)
When you need to run work in the background in your Android app, you reach for a Service. Services let you play music while the screen is off, sync data in the background, track location, or perform long-running operations without keeping an Activity open. In this complete, beginner-friendly guide, you will learn what Services are, the different types (started, bound, and foreground), how the lifecycle works, and how to implement a simple service with real code examples.
What Is a Service in Android?
A Service is an Android component designed to perform work in the background without a user interface. Unlike an Activity, a Service does not draw anything on the screen. Instead, it runs logic such as playing audio, downloading files, or processing data, even when the user switches to another app.
Services are still part of your app process. They can keep running even if the Activity that started them is destroyed, depending on how you design and stop them. This makes them powerful but also means you must manage them carefully to avoid wasting resources or draining the battery.
Why and When to Use a Service
You should use a Service when you need to do something that:
- Needs to continue running even if the user leaves the Activity (for example, music playback or file upload).
- Should run in the background without a visible UI, but still belongs to your app’s logic.
- Must expose functionality to other app components (or even other apps) via a bound interface.
For simple, short background work, modern apps often use WorkManager or coroutines with lifecycle scopes. Services are better suited for ongoing tasks, long-running operations, or when you need a stable component the system knows about explicitly.
Types of Services in Android
In Android, you will commonly encounter three main types of Services: started services, bound services, and foreground services. Understanding the difference between them is key to choosing the right approach for your use case.
1. Started Service
A started service is launched when an app component (usually an Activity) calls startService() or startForegroundService() with an Intent. Once started, the service can run in the background independently of the component that started it.
The service keeps running until it stops itself with stopSelf() or another component calls stopService(). This pattern is useful for tasks like uploading a file or processing some data that does not need to return a direct result to the caller.
2. Bound Service
A bound service allows components (Activities, Fragments, or even other apps) to bind to it and interact via a well-defined interface. Clients call bindService() and receive an IBinder that lets them call methods on the service directly, often like a local object.
The bound service typically runs only while at least one client is bound. When all clients unbind, the system can destroy the service. This is useful for scenarios where you want to expose ongoing functionality, such as a music player service that exposes play/pause/seek methods to multiple Activities.
3. Foreground Service
A foreground service is a special kind of service that the user is actively aware of. It shows a persistent notification and has a higher priority so the system is less likely to kill it under memory pressure.
Foreground services are required for tasks that must keep running even when the app is in the background and must be visible to the user—for example, playing music, recording audio, tracking fitness activities, or navigation. From Android 8.0 and higher, background execution limits make foreground services the recommended way to run ongoing work.
Service Lifecycle Basics
To use Services correctly, you need to understand their main lifecycle callbacks. For a simple started service, the key methods are:
onCreate()– Called once when the service is first created. Use it for one-time setup such as creating threads or initializing resources.onStartCommand()– Called every time a client starts the service usingstartService()orstartForegroundService(). This is where you handle the incoming Intent and start your background work.onDestroy()– Called when the service is about to be destroyed. Use it to clean up resources, stop threads, and release anything you allocated inonCreate().
For bound services, another important method is onBind(), which returns an IBinder that clients use to communicate with the service.
Creating a Simple Started Service – Example
Let’s create a simple started service that simulates a background task, such as downloading data or doing some computation. It will log progress and then stop itself when finished.
Step 1: Create the Service Class
Create a new Kotlin class MyBackgroundService.kt that extends Service and overrides the main lifecycle methods:
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
class MyBackgroundService : Service() {
private val tag = "MyBackgroundService"
private var isRunning = false
override fun onCreate() {
super.onCreate()
Log.d(tag, "onCreate called")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(tag, "onStartCommand called")
if (!isRunning) {
isRunning = true
doBackgroundWork()
}
// If the system kills the service, do not recreate automatically
return START_NOT_STICKY
}
private fun doBackgroundWork() {
Thread {
try {
for (i in 1..5) {
Log.d(tag, "Working... step $i")
Thread.sleep(1000L)
}
} catch (e: InterruptedException) {
e.printStackTrace()
} finally {
Log.d(tag, "Work finished, stopping service")
stopSelf()
}
}.start()
}
override fun onDestroy() {
super.onDestroy()
Log.d(tag, "onDestroy called")
isRunning = false
}
override fun onBind(intent: Intent?): IBinder? {
// We are creating a started service, not a bound service
return null
}
}
Here, the service creates a background thread when started, simulates some work for five seconds, logs progress, and then calls stopSelf() when done.
Step 2: Declare the Service in AndroidManifest.xml
Next, register the service in your manifest so Android knows it exists:
<application
...>
<service
android:name=".MyBackgroundService"
android:exported="false" />
</application>
Setting android:exported="false" keeps this service internal to your app. Other apps cannot start it directly.
Step 3: Start and Stop the Service from an Activity
Now, you can start the service from an Activity, for example on a button click:
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startBtn: Button = findViewById(R.id.btnStartService)
val stopBtn: Button = findViewById(R.id.btnStopService)
startBtn.setOnClickListener {
val intent = Intent(this, MyBackgroundService::class.java)
startService(intent)
}
stopBtn.setOnClickListener {
val intent = Intent(this, MyBackgroundService::class.java)
stopService(intent)
}
}
}
When you tap “Start Service”, the background work begins. When it finishes, the service stops itself. You can also tap “Stop Service” to stop it manually before it completes the work.
Foreground Service Example (High-Priority Background Work)
For tasks that must keep running in the background and should be visible to the user, you need a foreground service with a persistent notification. This is typical for music players, navigation, or long file downloads.
Below is a simplified example of creating a foreground service that shows a notification while running:
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
class MyForegroundService : Service() {
private val channelId = "my_foreground_channel"
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = buildNotification()
startForeground(1, notification)
// TODO: Start your long-running task here (e.g. music playback)
return START_STICKY
}
private fun buildNotification(): Notification {
return NotificationCompat.Builder(this, channelId)
.setContentTitle("Service running")
.setContentText("Doing work in the background...")
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(true)
.build()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channelId,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
override fun onBind(intent: Intent?): IBinder? = null
}
On Android 8.0 and higher, you should start this service using startForegroundService() from your Activity, and then call startForeground() inside onStartCommand() to avoid background execution restrictions.
Bound Service – Brief Overview
A bound service is more advanced, but it is worth understanding the idea. You create a service that exposes an IBinder which clients use to call methods directly on the service. This is ideal for features like a music player or download manager where multiple screens may want to interact with the same ongoing task.
For example, you might have a MusicService that exposes functions play(), pause(), and getCurrentPosition(). Both your main Activity and a notification or widget could bind to this service and control playback via that interface.
Best Practices and Common Pitfalls
Services are powerful, but misusing them can lead to battery drain, ANR (Application Not Responding) errors, and poor performance. Keep these best practices in mind:
- Do not perform heavy work on the main thread inside
onStartCommand(). Always move long tasks to a background thread, coroutine, or another asynchronous mechanism. - Stop services when work is complete by calling
stopSelf(), and avoid keeping them alive longer than necessary. - Use foreground services for user-visible, long-running tasks, and always provide a clear notification and a way to stop the work.
- Prefer modern alternatives like WorkManager for deferrable background tasks that must be executed reliably but not immediately.
- Be mindful of Android’s background execution limits on recent versions; respect Doze mode and battery optimization policies.
Conclusion
Services in Android are a crucial building block for background processing. By understanding the differences between started, bound, and foreground services, and by learning how the lifecycle works, you can design background tasks that are both powerful and respectful of user resources.
Using the examples in this guide, you can start building your own services for tasks like data syncing, media playback, tracking, or long-running computations. Combine Services with proper threading, notifications, and modern APIs, and your apps will feel faster, more reliable, and much more professional on real Android devices.

0 Comments