Devesh Rx Logo
Devesh Rx Blog

Android Services: Background, Foreground, and Bound Services (Part 4)

March 26, 2026

Android Services: Background, Foreground, and Bound Services (Part 4)

Android services let your app keep running long‑lasting tasks without freezing the UI. In this post we’ll break down what a service is, explore its three main variants, and walk through concrete code examples.

What Is an Android Service?

A service is a component that performs work on a background thread. By off‑loading heavy operations—such as data sync or media uploads—to a service, your app’s main UI thread remains free to respond to user input. The core purpose of a service is simply to keep performance optimal while tasks run separately from the screen.

Types of Services

ServiceCore Idea
Background ServiceExecutes silently; no user notification.
Foreground ServiceRuns in the background and shows a persistent notification so the user is aware that work is ongoing.
Bound ServiceProvides an interface for other components (activities, services) to talk to it—essentially IPC (inter‑process communication).

1️⃣ Background Service – Step by Step

1.1 Create the Service Class

class MyService : Service() {
    override fun onCreate() { /* called when service is first created */ }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        /* starts background work */
    }
    override fun onDestroy() { /* called when system stops the service */ }
    override fun onBind(intent: Intent?): IBinder? = null   // not needed for a pure background service
}

Why onBind returns null?
Because this service isn’t meant to be bound; it just runs in the background.

1.2 Add a Periodic Task with Kotlin Coroutines

private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    serviceScope.launch {
        while (isActive) {          // coroutine keeps running until cancelled
            delay(3000)             // wait 3 seconds
            Log.d("MyService", "service status is running smoothly")
        }
    }
    return START_STICKY           // restarts if killed by the OS
}

Key points

  • The coroutine runs on Dispatchers.IO, an appropriate background thread.
  • A 3‑second delay simulates a long‑running task (e.g., image upload).

1.3 Clean Up in onDestroy

override fun onDestroy() {
    serviceScope.cancel()          // stops the coroutine to avoid leaks
    super.onDestroy()
}

If you omit this cancellation, the coroutine would keep running even after the service is stopped, potentially causing instability.

1.4 Declare the Service in AndroidManifest.xml

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="false" />

Why these attributes?

  • android:enabled="true" ensures the service is available at runtime.
  • android:exported="false" keeps it private to your app.

1.5 UI to Start/Stop (Jetpack Compose)

Button(onClick = { startService(Intent(this, MyService::class.java)) }) {
    Text("Start Service")
}
Button(onClick = { stopService(Intent(this, MyService::class.java)) }) {
    Text("Stop Service")
}

Only two lines are needed—just like starting an activity.

1.6 Verify in Action

  • Launch the app → tap Start Service.
  • A toast confirms launch and Logcat shows “service status” every 3 s.
  • Tap Stop Service; onDestroy appears in Logcat, confirming cleanup.

2️⃣ Foreground Service – Making Background Work Visible

2.1 Create the Service Class with a Notification

class MyForegroundService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildNotification()
        startForeground(1, notification)   // shows persistent notification

        serviceScope.launch {                // same coroutine task as background service
            while (isActive) {
                delay(3000)
                Log.d("MyForegroundService", "service status is running smoothly")
            }
        }

        return START_STICKY
    }

    private fun buildNotification(): Notification {
        val channelName = "Sync Channel"
        val channelId   = "sync_channel_id"

        return Notification.Builder(this, channelId)
            .setContentTitle("Foreground Service")
            .setContentText("Service is running smoothly")
            .setSmallIcon(R.drawable.ic_sync)   // replace with your icon
            .build()
    }
}
  • The notification tells the user that a background task is active.
  • startForeground must be called before any heavy work begins.

2.2 Manifest Permissions & Service Declaration

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<service
    android:name=".MyForegroundService"
    android:foregroundServiceType="dataSync" />

Why these permissions?

  • FOREGROUND_SERVICE allows the system to run the service in the foreground.
  • POST_NOTIFICATIONS is required because we’re displaying a notification.

2.3 UI Buttons to Control the Foreground Service

Button(onClick = {
    val intent = Intent(this, MyForegroundService::class.java).apply {
        action = MyForegroundService.Action.START.name
    }
    startService(intent)
}) { Text("Start Foreground Service") }

Button(onClick = {
    val intent = Intent(this, MyForegroundService::class.java).apply {
        action = MyForegroundService.Action.STOP.name
    }
    startService(intent)
}) { Text("Stop Foreground Service") }

2.4 Running the Service

  1. The first launch triggers a notification‑permission dialog—accept it once.
  2. Tap Start → a persistent notification appears and Logcat logs status every 3 s.
  3. Tap Stop → notification disappears, onDestroy fires in Logcat.

3️⃣ Bound Service – IPC Between Components

3.1 Service Implementation with LocalBinder

class MyBoundService : Service() {
    private val binder = LocalBinder()
    private val randomGenerator = Random()

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

    override fun onBind(intent: Intent?): IBinder? = binder

    // Public method for clients to call
    fun generateRandomNumber(max: Int = 100): Int =
        randomGenerator.nextInt(max + 1)
}
  • LocalBinder acts as a glue that lets activities or other services obtain the current instance of MyBoundService.
  • The service exposes generateRandomNumber() so any bound component can request data directly.

3.2 Declare in Manifest

<service android:name=".MyBoundService"
         android:enabled="true"
         android:exported="false" />

No extra permissions are required for a simple bound service.

3.3 Bind the Service from an Activity

class MainActivity : ComponentActivity() {
    private var boundService: MyBoundService? = null
    private var isBound = false

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            boundService = (binder as MyBoundService.LocalBinder).getService()
            isBound = true
        }
        override fun onServiceDisconnected(name: ComponentName?) {
            boundService = null
            isBound = false
        }
    }

    override fun onStart() {
        super.onStart()
        Intent(this, MyBoundService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

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

3.4 UI to Fetch Data from the Bound Service

var randomNumber by remember { mutableStateOf(0) }

Column {
    Text(text = if (isBound) "Service is bound" else "Service not bound")
    Text(text = "Random number: $randomNumber")

    Button(onClick = {
        boundService?.let { service ->
            randomNumber = service.generateRandomNumber()
        }
    }) {
        Text("Get Number from Service")
    }
}

What happens behind the scenes?

  1. The activity binds to MyBoundService on start.
  2. When the user taps Get Number from Service, the activity calls generateRandomNumber() through the binder, receives a new random integer, and displays it.

Recap of what we learned

ConceptWhy It Matters
Background ServiceSilent background work; ideal for tasks that don’t need to inform the user.
Foreground ServicePersistent notification keeps the system and user aware of ongoing work—necessary for long‑running processes like sync or location tracking.
Bound ServiceEnables tight coupling between activities/services, allowing them to call each other’s methods directly via a binder—great for shared data or complex IPC.

With these code patterns you can now:

  • Spin up silent background jobs that restart automatically.
  • Run foreground services that respect user notifications and system policies.
  • Create bound services for component‑to‑component communication.

Happy coding, and may your apps run smoothly in the background!


📌 Full Course Playlist https://www.youtube.com/playlist?list=PLO1OrQEU0vHNmD9Xqzs-qXwzzwrDvdhVu

#Tutorial
0 Introduction
1 Setting up Android Studio IDE
2 Mastering Android Studio: Navigating the IDE & Project Structure
3 Android Activity & Lifecycle Explained
4 Android Services: Background, Foreground, and Bound Services Explained
5 Android Broadcast Receivers: The Complete Guide to Listening and Sending Events

~ ~ THANK YOU FOR READING ~ ~

Share: