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.xmland 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:
- Foreground Services — Perform operations that are noticeable to the user. They must display a persistent notification.
- Background Services — Perform operations that are not directly noticeable to the user.
- 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 builderNotificationManager.IMPORTANCE_DEFAULT— sets the notification importance levelstartForeground(1, notification)— promotes the service and shows the notification;1is the notification IDstopForeground(true)— removes the notification when work is done, thenstopSelf()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
| Feature | Background Service | Foreground Service | Bound Service |
|---|---|---|---|
| Visible to user | No | Yes (notification) | No |
| Notification required | No | Yes | No |
| Survives activity close | Yes | Yes | Only while bound |
| Return value | START_STICKY | START_STICKY | START_NOT_STICKY |
onBind() returns | null | null | IBinder |
| Use case | Silent tasks | Music, uploads | Activity ↔ 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
NotificationviastartForeground(); Bound Services return anIBinderfromonBind()so callers can invoke methods directly. START_STICKYcauses the OS to restart a killed service;START_NOT_STICKYlets it die, which is appropriate for Bound Services.- Every service must be declared in
AndroidManifest.xmlwith<service android:name=".YourService" />. - Binding is asynchronous — only access the service reference inside
onServiceConnected, and always guard calls with anisBoundflag.
📚 Further Reading
- Android Services overview — developer.android.com — the source of truth on service types, lifecycle, and background execution limits
- Foreground services guide — developer.android.com — required reading before shipping a media or location service
- Bound services guide — developer.android.com — covers AIDL and Messenger for cross-process IPC beyond
LocalBinder - NotificationCompat.Builder reference — full API for building rich service notifications
- ⬅️ Previous: Android Building Blocks -I : Activity
- ➡️ Next: Encapsulation in Java