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)

Hi, and welcome back to the fundamentals of Android app development series! In this article, we are going to dive deep into Android Services. We will explore what a service is, break down the different types of services, and walk through how to implement them in your own Android applications. Let’s get started.

What is an Android Service?

A service is a task that runs in the background. Certain operations—such as data synchronization or uploading images—need to run completely separated from the user interface.

In our previous discussions, we learned that an Android Activity runs in the foreground, right in front of the screen. A service is the exact opposite. By offloading tasks to a background thread, your application’s performance remains highly optimized.

The Three Types of Android Services

Inside the Android ecosystem, there are mainly three types of services:

1. Background Services

A standard background service runs completely out of sight. It executes its operations in the background, with or without notifying the user.

2. Foreground Services

A foreground service also runs in the background, but it shows a foreground notification to the user. This notification indicates that a background task is actively running in the application.

3. Bound Services

A bound service acts as an interface to communicate between multiple application components. You can think of it as Inter-Process Communication (IPC). For example, if you want to pass data between Activity 1 and Activity 2, or between an Activity and a Service, a single bound service acts as the glue that allows these components to seamlessly communicate.


How to Implement Services in Your Application

Now, let’s explore how to actually implement these three types of services in your Android project.

Implementing a Background Service

Creating a basic background service involves a few straightforward steps:

  • Create a Service Class: Create a MyService class that inherits from android.app.Service.
  • Understand the Lifecycle Events:
    • onCreate: Triggered when the service is initially created.
    • onStartCommand: Triggered when the service starts. It uses an Intent to receive information sent at the beginning of the service.
    • onDestroy: Triggered when the Android operating system destroys the service.
    • onBind: Triggered when a component connects to the service (for basic background services, we simply return null).
  • Run an Asynchronous Task: You can run your background task using Kotlin Coroutines. For example, create a job that logs a “service status is running smoothly” message every three seconds. Place this inside onStartCommand.
  • Return a Service Status: Return START_STICKY at the end of onStartCommand. This ensures that if the Android OS kills your service, it will automatically restart. Alternatively, returning START_NOT_STICKY means the service will simply die out without restarting.
  • Clean Up Resources: Inside onDestroy, make sure to cancel your Coroutine scope. If you don’t, the coroutine will continue running even after the service stops, which can cause app instability.
// MyService.kt
package com.devesh.helloworldapp

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import kotlinx.coroutines.*

class MyService : Service() {
    private var loggingJob: Job? = null
    private val serviceScope = CoroutineScope(Dispatchers.Default + Job())

    override fun onCreate() {
        super.onCreate()
        Log.d("MyService", "Service Created")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("MyService", "Service Started")
        Toast.makeText(this, "Service Started", Toast.LENGTH_SHORT).show()

        // Task: Start periodic logging if it hasn't started yet
        if (loggingJob == null) {
            loggingJob = serviceScope.launch {
                while (isActive) {
                    Log.d("MyService", "Service Status: Running smoothly...")
                    delay(3000) // Log every 3 seconds
                }
            }
        }
        
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        // Stop the background logging when the service is destroyed
        serviceScope.cancel()
        Log.d("MyService", "Service Destroyed")
        Toast.makeText(this, "Service Stopped", Toast.LENGTH_SHORT).show()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
}
  • Update the Android Manifest: Declare the service directly below your <activity> tag using a <service> tag. Set the name to your service class, enabled="true", and exported="false".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
       ...
        >
        <activity>
           ...
        </activity>

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

    </application>

</manifest>
  • Control the Service via UI: Using an Android Jetpack Compose UI, you can easily start and stop the service by creating an Intent and passing it to startService() or stopService().
// MainActivity.kt

        Button(onClick = {
            /** Start MyService Service */
            val intent = Intent(context, MyService::class.java)
            context.startService(intent)
        }) {
            Text(text = "Start  Service")
        }

        Button(onClick = {
            /** Stop MyService Service */
            val intent = Intent(context, MyService::class.java)
            context.stopService(intent)
        }) {
            Text(text = "Stop Service")
        }

Implementing a Foreground Service

The core difference between a background and a foreground service is the notification. Here is how to set it up:

  • Create the Service Logic: Create a MyForegroundService class. Inside onStartCommand, check the Intent action. You can use an enum class with START and STOP actions to determine whether the service should begin or automatically shut down.
  • Create a Notification: You must notify the user. Create a notification channel (with a channel name and ID) and define a notification variable containing text (like “foreground service is running”) and an icon.
  • Call startForeground: Pass your service ID and notification variable into the startForeground() method. This pushes the notification to the user’s drawer.
// MyForegroundService.kt

package com.devesh.foreground_service_app_tutorial

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 android.util.Log
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

class MyForegroundService : Service() {

    private val TAG = "MyForegroundService"

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "Service Created")
    }

    override fun onBind(intent: Intent?): IBinder? {
        Log.d(TAG, "onBind called")
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand: action = ${intent?.action}")
        when (intent?.action) {
            Actions.START.toString() -> start()
            Actions.STOP.toString() -> {
                Log.d(TAG, "Stopping service via Intent")
                stopSelf()
            }
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun start() {
        Log.d(TAG, "Starting foreground service...")

        // Create Notification
        val channelId = "running_channel"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Running Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
            Log.d(TAG, "Notification channel created")
        }

        val notification = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("Run is active")
            .setContentText("Foreground service is running...")
            .build()

        // Start Foreground Service with Notification
        startForeground(1, notification)
        Log.d(TAG, "Foreground notification posted")

        // START YOUR TASK HERE ---
        myTask()
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "Service Destroyed")
        loggingJob?.cancel()
        Log.d(TAG, "Background task Stopped")
    }

    enum class Actions {
        START, STOP
    }



    private var loggingJob: Job? = null

    private val serviceScope = CoroutineScope(Dispatchers.Default + Job())
    fun myTask() {
        // Task: Start periodic logging if it hasn't started yet
        if (loggingJob == null) {
            Log.d(TAG, "Starting periodic task...")
            loggingJob = serviceScope.launch {
                while (isActive) {
                    Log.d(TAG, "Service Status: Running smoothly...")
                    delay(3000) // Log every 3 seconds
                }
            }
        }
    }

}
  • Add Permissions in the Manifest: To run a foreground service, you must declare three crucial permissions in your AndroidManifest.xml:
    1. FOREGROUND_SERVICE
    2. FOREGROUND_SERVICE_DATA_SYNC (or whichever type your service requires, such as media, camera, or location).
    3. POST_NOTIFICATIONS
  • Declare the Service Tag: In the manifest, add the <service> tag and explicitly declare the foregroundServiceType (e.g., dataSync).
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

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

    <application
        ...
        >

        <activity>
          ...
        </activity>

        <!-- Mention Foreground Service in Manifest-->
        <service
            android:name=".MyForegroundService"
            android:foregroundServiceType="dataSync" />

    </application>

</manifest>
  • Request User Permission: In your MainActivity, prompt the user for permission to display notifications.
  • Trigger via Intent: Launch the foreground service using an Intent that passes your specific START or STOP enum action as a string.
// MainActivity.kt

Button(onClick = {
    // Start Foreground Service
    val intent = Intent(context, MyForegroundService::class.java)
    intent.action=MyForegroundService.Actions.START.toString()
    context.startService(intent)
}) {
    Text(text = "Start Service")
}
Button(onClick = {
    // Stop Foreground Service
    val intent = Intent(context, MyForegroundService::class.java)
    intent.action=MyForegroundService.Actions.STOP.toString()
    context.startService(intent)
}) {
    Text(text = "Stop Service")
}

Implementing a Bound Service

Bound services are slightly more complex but highly powerful. They operate on a client-server relationship, where the service acts as the server and your components (like an Activity) act as the clients.

  • Create a Local Binder: Inside your MyBoundService class, create an inner class called LocalBinder. This binder acts as glue, returning the current instance of your service to the client component.
  • Implement onBind and onUnbind:
    • onBind: Return the LocalBinder instance you created.
    • onUnbind: Triggered when the service is completely disconnected or detached from a component.
  • Create a Callable Function: Write a public function—such as getRandomNumber()—which generates and returns a random number under 100. Any connected client can now call this function directly.
// MyBoundService.kt
package com.devesh.bind_service

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
import kotlin.random.Random

class MyBoundService : Service() {

    companion object {
        private const val TAG = "MyBoundService"
    }

    // Binder given to clients
    private val binder = LocalBinder()

    // Random number generator
    private val mGenerator = Random

    /**
     * Class used for the client Binder. Because we know this service always
     * runs in the same process as its clients.
     */
    inner class LocalBinder : Binder() {
        // Return this instance of MyBoundService so clients can call public methods
        fun getService(): MyBoundService = this@MyBoundService
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "Service onCreate")
    }

    /**
     * Called when a client (like an Activity) calls bindService().
     * This is where we return our Binder object to the client.
     */
    override fun onBind(intent: Intent): IBinder {
        Log.d(TAG, "Service onBind")
        return binder
    }

    /**
     * Called when all clients have disconnected from the service.
     */
    override fun onUnbind(intent: Intent?): Boolean {
        Log.d(TAG, "Service onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "Service onDestroy")
    }

    /** Method for clients to call to get a random number */
    fun getRandomNumber(): Int {
        val num = mGenerator.nextInt(100)
        Log.d(TAG, "Generating random number: $num")
        return num
    }
}
  • Establish a Service Connection: In your Activity, declare a ServiceConnection. This requires two critical functions:
    • onServiceConnected: Receives the LocalBinder, extracts the service instance, saves it to a variable (mService), and sets a boolean flag (mBound) to true.
    • onServiceDisconnected: Sets mBound to false and mService to null, destroying the instance.
  • Bind and Unbind the Service: In your Activity’s onStart event, launch an Intent using bindService() along with the BIND_AUTO_CREATE flag. In onStop, make sure to call unbindService() to disconnect safely.
  • Access the Service from UI: With the service bound, your UI button can simply call mService.getRandomNumber() and instantly display the result on the screen. This is a perfect example of effective Inter-Process Communication.
// MainActivity.kt
package com.devesh.bind_service

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.devesh.bind_service.ui.theme.HelloWorldAppTheme

class MainActivity : ComponentActivity() {

    companion object {
        private const val TAG = "MainActivity"
    }

    private var mService: MyBoundService? = null
    private var mBound by mutableStateOf(false)
    private var randomNumber by mutableStateOf(0)

    /**
     * ServiceConnection acts as the bridge between the Activity and the Service.
     * It listens for when the service is connected or disconnected.
     */
    private val connection = object : ServiceConnection {

        /**
         * Called when the connection with the service has been established.
         * @param service The IBinder of the communication channel to the Service.
         */
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            Log.d(TAG, "onServiceConnected called")
            // We've bound to MyBoundService, cast the IBinder and get MyBoundService instance
            val binder = service as MyBoundService.LocalBinder
            mService = binder.getService()
            mBound = true
        }

        /**
         * Called when the connection with the service has been unexpectedly disconnected.
         * This is NOT called when the client unbinds.
         */
        override fun onServiceDisconnected(arg0: ComponentName) {
            Log.d(TAG, "onServiceDisconnected called")
            mBound = false
            mService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate")
        enableEdgeToEdge()
        setContent {
            HelloWorldAppTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    BoundServiceScreen(
                        modifier = Modifier.padding(innerPadding),
                        isBound = mBound,
                        number = randomNumber,
                        onGetNumberClick = {
                            if (mBound) {
                                // Interaction: Calling a public method on the bound service
                                randomNumber = mService?.getRandomNumber() ?: 0
                                Log.d(TAG, "Button clicked: Received $randomNumber from service")
                            } else {
                                Log.d(TAG, "Button clicked: Service not bound")
                            }
                        }
                    )
                }
            }
        }
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart: Binding to service...")
        // Bind to LocalService
        Intent(this, MyBoundService::class.java).also { intent ->
            // Context.BIND_AUTO_CREATE: Automatically create the service as long as the binding exists
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop: Unbinding from service...")
        // Unbind from the service to avoid memory leaks
        if (mBound) {
            unbindService(connection)
            mBound = false
        }
    }
}

/**
 * UI for displaying the bound status and interacting with the service.
 */
@Composable
fun BoundServiceScreen(
    modifier: Modifier = Modifier,
    isBound: Boolean,
    number: Int,
    onGetNumberClick: () -> Unit
) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = if (isBound) "Service Bound" else "Service Unbound",
            fontSize = 20.sp
        )
        Spacer(modifier = Modifier.height(16.dp))
        Text(text = "Random Number: $number", fontSize = 24.sp)
        Spacer(modifier = Modifier.height(24.dp))
        Button(
            onClick = onGetNumberClick,
            enabled = isBound // Button is only active when service is bound
        ) {
            Text("Get Number from Service")
        }
    }
}

  • Add Permissions in the Manifest: To run a bound service, you must declare three crucial permissions in your AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        ...
        >
        <activity>
        ...
        </activity>

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

</manifest>

Summary

To recap what we’ve learned today:

  • A service is a task optimized to run completely in the background.
  • Background Services handle silent operations without user knowledge.
  • Foreground Services run background tasks while keeping the user informed via an active notification.
  • Bound Services provide an Inter-Process Communication (IPC) interface, allowing multiple app components to communicate with a single service instance.

That’s it for Android services! If you enjoyed this guide, be sure to check out the entire playlist covering the fundamentals of Android app development linked is given below. Have a great day, and happy coding!

📌 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

~ ~ THANK YOU FOR READING ~ ~

Share: