Topic 20 of 28 · Android Native Developer

Topic 6 : Android Building Blocks -II : Services

Lesson TL;DRTopic 6: Android Building Blocks II : Services 📖 14 min read · 🎯 intermediate · 🧭 Prerequisites: abstractioninjava, androidbuildingblocksiactivity Why this matters Ever noticed how a music app keep...
14 min read·intermediate·android · services · foreground-service · bound-service

Topic 6: Android Building Blocks -II : Services

📖 14 min read · 🎯 intermediate · 🧭 Prerequisites: abstraction-in-java, android-building-blocks-i-activity

Why this matters

Ever noticed how a music app keeps playing after you switch to your messages? Or how a file keeps uploading even when you close the app? That's not magic — that's a Service doing its job quietly in the background. Up until now, everything we built lived on the screen. The moment the user looked away, the work stopped. But real apps don't work that way. In this lesson, you'll learn how Android Services let your app keep running — fetching data, playing audio, syncing files — even when there's nothing visible on screen.

What You'll Learn

  • What an Android Service is and when to use one instead of an Activity
  • How to implement the three types of Services: Background, Foreground, and Bound
  • How to register a Service in AndroidManifest.xml and control its lifecycle
  • How to bind an Activity to a Service and communicate between them via IBinder

The Analogy

Think of a city's water treatment plant. The citizens of Vizag never visit it — there's no lobby, no receptionist, no UI — but it runs continuously in the background, purifying water even while everyone sleeps. A Foreground Service is like a plant that posts a status sign on the city noticeboard ("Water treatment in progress — all clear"), so residents know work is happening. A Bound Service is like a utility helpline: a resident (Activity) can call in, ask a question, get a direct answer, and hang up. Background Services are the silent night-shift workers — no sign, no phone line, just work.

Chapter 1: Overview of Services

A Service is an Android component that performs long-running operations in the background and does not provide a user interface. Common use cases include:

  • Performing network operations
  • Playing music in the background
  • Running periodic tasks
  • Uploading or downloading data

There are three main types of services in Android:

  1. Foreground Services — Perform operations that are noticeable to the user. They must display a persistent notification.
  2. Background Services — Perform operations that are not directly noticeable to the user.
  3. Bound Services — Allow components (such as Activities) to bind to them, send requests, receive responses, and even perform interprocess communication (IPC).
graph TD
    A[Android Service] --> B[Foreground Service]
    A --> C[Background Service]
    A --> D[Bound Service]
    B --> B1["Displays notification\nstartForeground()"]
    C --> C1["Silent background work\nonStartCommand()"]
    D --> D1["IPC / local calls\nonBind() → IBinder"]

Chapter 2: Creating a Simple (Background) Service

Step 1: Define the Service

MyService.java

package com.example.myapp;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Service Created");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Service Started");
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                Log.d(TAG, "Running task " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            stopSelf();
        }).start();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Service Destroyed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

START_STICKY tells the OS to recreate the service if it is killed, passing a null intent. This is the correct return value for services that should keep running until explicitly stopped.

Step 2: Register the Service in the Manifest

Every service must be declared in AndroidManifest.xml or the OS will refuse to start it.

AndroidManifest.xml

<service android:name=".MyService" />

Step 3: Start and Stop the Service from an Activity

MainActivity.java

package com.example.myapp;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startButton = findViewById(R.id.start_service_button);
        Button stopButton  = findViewById(R.id.stop_service_button);

        startButton.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyService.class);
            startService(intent);
        });

        stopButton.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyService.class);
            stopService(intent);
        });
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/start_service_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Service" />

    <Button
        android:id="@+id/stop_service_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Service" />

</LinearLayout>

Service Lifecycle at a Glance

sequenceDiagram
    participant A as Activity
    participant S as MyService
    A->>S: startService(intent)
    S->>S: onCreate()
    S->>S: onStartCommand()
    Note over S: Background work runs
    S->>S: stopSelf() or
    A->>S: stopService(intent)
    S->>S: onDestroy()

Chapter 3: Foreground Service

A Foreground Service must call startForeground() immediately after starting, passing a valid Notification. On Android O (API 26) and above, you also need to create a NotificationChannel first.

MyForegroundService.java

package com.example.myapp;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.core.app.NotificationCompat;

public class MyForegroundService extends Service {
    private static final String TAG        = "MyForegroundService";
    private static final String CHANNEL_ID = "ForegroundServiceChannel";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Foreground Service Created");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Foreground Service Started");

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    CHANNEL_ID,
                    "Foreground Service Channel",
                    NotificationManager.IMPORTANCE_DEFAULT
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            if (manager != null) {
                manager.createNotificationChannel(channel);
            }
        }

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Foreground Service")
                .setContentText("Foreground Service is running")
                .setSmallIcon(R.drawable.ic_service)
                .build();

        startForeground(1, notification);

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                Log.d(TAG, "Running task " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            stopForeground(true);
            stopSelf();
        }).start();

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Foreground Service Destroyed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Key details:

  • CHANNEL_ID = "ForegroundServiceChannel" — the string that ties the channel to the notification builder
  • NotificationManager.IMPORTANCE_DEFAULT — sets the notification importance level
  • startForeground(1, notification) — promotes the service and shows the notification; 1 is the notification ID
  • stopForeground(true) — removes the notification when work is done, then stopSelf() terminates the service

Register in the Manifest

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

Start and Stop the Foreground Service

MainActivity.java

package com.example.myapp;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startButton = findViewById(R.id.start_foreground_service_button);
        Button stopButton  = findViewById(R.id.stop_foreground_service_button);

        startButton.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyForegroundService.class);
            startService(intent);
        });

        stopButton.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyForegroundService.class);
            stopService(intent);
        });
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/start_foreground_service_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Foreground Service" />

    <Button
        android:id="@+id/stop_foreground_service_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Foreground Service" />

</LinearLayout>

Chapter 4: Bound Service

A Bound Service allows components such as Activities to bind to the service, send requests, receive responses, and perform interprocess communication (IPC). The binding contract is established through an IBinder object returned from onBind().

The canonical local-binding pattern uses an inner Binder subclass called LocalBinder that hands the caller a direct reference to the service instance.

MyBoundService.java

package com.example.myapp;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyBoundService extends Service {
    private static final String TAG = "MyBoundService";
    private final IBinder binder = new LocalBinder();

    public class LocalBinder extends Binder {
        MyBoundService getService() {
            return MyBoundService.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "Bound Service Created");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "Bound Service Started");
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "Bound Service Destroyed");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "Service Bound");
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "Service Unbound");
        return super.onUnbind(intent);
    }

    public String getHelloMessage() {
        return "Hello from Bound Service!";
    }
}

START_NOT_STICKY is correct here: a bound service should not auto-restart after being killed by the OS, because the binding client (Activity) would have lost its reference anyway.

Register in the Manifest

<service android:name=".MyBoundService" />

Bind and Unbind from an Activity

Binding is asynchronous. You provide a ServiceConnection callback; when the OS completes the bind, onServiceConnected fires and hands you the IBinder. Cast it to LocalBinder and call getService() to get the live service reference.

MainActivity.java

package com.example.myapp;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private MyBoundService myBoundService;
    private boolean isBound = false;

    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MyBoundService.LocalBinder binder = (MyBoundService.LocalBinder) service;
            myBoundService = binder.getService();
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button bindButton       = findViewById(R.id.bind_service_button);
        Button unbindButton     = findViewById(R.id.unbind_service_button);
        Button getMessageButton = findViewById(R.id.get_message_button);
        TextView messageTextView = findViewById(R.id.message_text_view);

        bindButton.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, MyBoundService.class);
            bindService(intent, connection, BIND_AUTO_CREATE);
        });

        unbindButton.setOnClickListener(v -> {
            if (isBound) {
                unbindService(connection);
                isBound = false;
            }
        });

        getMessageButton.setOnClickListener(v -> {
            if (isBound) {
                String message = myBoundService.getHelloMessage();
                messageTextView.setText(message);
            }
        });
    }
}

BIND_AUTO_CREATE tells Android to create the service if it does not already exist when bindService is called.

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/bind_service_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Bind Service" />

    <Button
        android:id="@+id/unbind_service_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Unbind Service" />

    <Button
        android:id="@+id/get_message_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Get Message" />

    <TextView
        android:id="@+id/message_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Service Message" />

</LinearLayout>

Bound Service Lifecycle

sequenceDiagram
    participant A as Activity
    participant OS as Android OS
    participant S as MyBoundService
    A->>OS: bindService(intent, connection, BIND_AUTO_CREATE)
    OS->>S: onCreate() + onBind()
    OS->>A: onServiceConnected(binder)
    A->>S: binder.getService().getHelloMessage()
    S-->>A: "Hello from Bound Service!"
    A->>OS: unbindService(connection)
    OS->>S: onUnbind() → onDestroy()

Chapter 5: Comparing the Three Service Types

FeatureBackground ServiceForeground ServiceBound Service
Visible to userNoYes (notification)No
Notification requiredNoYesNo
Survives activity closeYesYesOnly while bound
Return valueSTART_STICKYSTART_STICKYSTART_NOT_STICKY
onBind() returnsnullnullIBinder
Use caseSilent tasksMusic, uploadsActivity ↔ Service calls

🧪 Try It Yourself

Task: Build a Bound Service that acts as a simple counter. The service keeps an int count field. Expose two public methods: increment() and getCount(). Bind to it from MainActivity, wire up two buttons — "Increment" and "Show Count" — and display the current count in a TextView.

Success criterion: Each tap of "Increment" increases the internal counter. Tapping "Show Count" sets the TextView to the current value (e.g., Count: 3). Unbinding and rebinding should preserve the count (because the service instance stays alive while bound).

Starter snippet for the service:

public class CounterService extends Service {
    private int count = 0;
    private final IBinder binder = new LocalBinder();

    public class LocalBinder extends Binder {
        CounterService getService() { return CounterService.this; }
    }

    @Override
    public IBinder onBind(Intent intent) { return binder; }

    public void increment() { count++; }
    public int getCount()   { return count; }
}

Register it in AndroidManifest.xml, bind it from MainActivity using BIND_AUTO_CREATE, and you're off.

🔍 Checkpoint Quiz

Q1. What is the key difference between a Foreground Service and a Background Service?

A) Foreground Services run on the main thread; Background Services run on a worker thread B) Foreground Services must display a persistent notification; Background Services do not C) Foreground Services are only available on API 26+; Background Services work on all versions D) Foreground Services cannot perform network operations

Q2. Given this onStartCommand implementation, what happens if the OS kills the service while it is running?

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    doWork();
    return START_STICKY;
}

A) The service is destroyed permanently B) The service is restarted by the OS with a null intent C) The service is restarted by the OS with the original intent D) onBind() is called instead

Q3. What is wrong with the following bound-service usage in an Activity?

bindButton.setOnClickListener(v -> {
    Intent intent = new Intent(this, MyBoundService.class);
    bindService(intent, connection, BIND_AUTO_CREATE);
});

getMessageButton.setOnClickListener(v -> {
    String msg = myBoundService.getHelloMessage(); // called immediately after bind click
    textView.setText(msg);
});

A) bindService requires BIND_NOT_FOREGROUND flag B) myBoundService may still be null because binding is asynchronous; the reference is only valid inside onServiceConnected C) getHelloMessage() must be called on a background thread D) intent should use an explicit class name string, not the .class reference

Q4. You are building a podcast app that continues playing audio after the user swipes the app away. Which service type should you use, and why?

A1. B — Foreground Services are required by the Android platform to post a visible notification so the user is aware of ongoing work; Background Services run silently.

A2. B — START_STICKY signals the OS to restart the service after it is killed; the redelivered intent will be null because the original intent is not cached.

A3. B — bindService is asynchronous. myBoundService is set inside onServiceConnected, which fires on the next looper cycle. Calling getHelloMessage() in the same click that triggers the bind will almost certainly hit a NullPointerException.

A4. Foreground Service. A media player must remain alive even when no UI is visible, and Android's background execution limits would kill a plain Background Service. Displaying a playback notification (required for Foreground Services) also gives users transport controls — a double win.

🪞 Recap

  • A Service is a UI-less Android component designed for long-running background work: network calls, media playback, periodic tasks, and data sync.
  • Background Services run silently; Foreground Services must post a Notification via startForeground(); Bound Services return an IBinder from onBind() so callers can invoke methods directly.
  • START_STICKY causes the OS to restart a killed service; START_NOT_STICKY lets it die, which is appropriate for Bound Services.
  • Every service must be declared in AndroidManifest.xml with <service android:name=".YourService" />.
  • Binding is asynchronous — only access the service reference inside onServiceConnected, and always guard calls with an isBound flag.

📚 Further Reading

Like this topic? It’s one of 28 in Android Native Developer.

Block your seat for ₹2,500 and join the next cohort.