Services & Foreground Service

A Service is an Android component built to run in the background without any user interface of its own. It's a different tool from everything covered so far: WorkManager schedules and guarantees deferred work, coroutines run structured async code tied to a scope's lifetime, and a Service just keeps running, independent of any screen, for as long as it's needed.

Three Flavors of Service

"Service" actually covers three fairly different things, and picking the wrong one is a common source of confusion:

KindWhat it's forCurrent status
Plain (background) Service Unbounded background work with no user-visible indication Heavily restricted on modern Android — the OS aggressively limits or kills these; WorkManager replaces most of what this used to be used for
Foreground Service Ongoing work the user should be actively aware of, shown via a persistent notification — like tracking a hike's GPS route live The right choice for genuinely ongoing, user-visible background work
Bound Service Work another component (an Activity, another app) needs to interact with directly through a client-server style interface Used when you need two-way method calls into the running Service, not just a fire-and-forget task

For TrailLog, the one that matters most is the Foreground Service — it's the only one of the three genuinely suited to something like live GPS tracking while the user has switched to another app.

TrailLog's HikeTrackingService

Picture the user tapping "Start Hike." From that moment, TrailLog needs to keep recording GPS points even if the user locks their phone or switches to a messaging app mid-hike. That's a textbook Foreground Service: ongoing, user-initiated, and something the user should always be able to see is happening.

HikeTrackingService outline

class HikeTrackingService : Service() {

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = buildRecordingNotification()
        startForeground(NOTIFICATION_ID, notification)
        beginRecordingGpsPoints()
        return START_STICKY
    }

    override fun onBind(intent: Intent): IBinder? = null // not a bound service

    override fun onDestroy() {
        stopRecordingGpsPoints()
        super.onDestroy()
    }
}

startForeground(NOTIFICATION_ID, notification) is what promotes this Service to foreground status, and it must be called within seconds of the Service starting — Android requires it. That persistent "Recording hike…" notification isn't an inconvenience to route around; it's a genuine transparency requirement, so the user always has a visible, tappable reminder that GPS tracking is actively running in the background. Building the notification content itself follows the same channel-and-builder pattern covered in Notifications & Channels.

Starting the Service

From the Activity or Fragment where the user taps "Start Hike," you start the Service with ContextCompat.startForegroundService() rather than the plain startService() call — this variant is what correctly signals to the system that the Service intends to call startForeground() right away:

fun startHikeTracking(context: Context) {
    val intent = Intent(context, HikeTrackingService::class.java)
    ContextCompat.startForegroundService(context, intent)
}

Stopping It Cleanly

When the user taps "End Hike," the Service needs to stop recording and remove itself — and its notification — right away:

fun stopHikeTracking(context: Context) {
    val intent = Intent(context, HikeTrackingService::class.java)
    context.stopService(intent)
}

Inside the Service, onDestroy() is the right place to release any GPS callbacks or other resources — leaving them running after the Service is gone is a reliable way to leak battery and memory.

Note: This is correct, working Android Service code, but this website's own project doesn't run a live Android Studio build, so no notification or GPS tracking actually happens on this page.

The FOREGROUND_SERVICE_LOCATION Permission

Recent Android versions require an additional manifest permission specifically for foreground services that track location, on top of the regular location permission itself:

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

This exists on top of everything else this lesson covered because the OS treats "running continuously in the foreground" and "accessing the user's location" as two separate, both sensitive, capabilities that each need explicit declaration. The next lesson, Runtime Permissions, covers how TrailLog actually asks the user for location access at runtime — declaring it in the manifest is only step one.

Up next: A declared permission in the manifest isn't enough on its own — dangerous permissions like location have to be requested from the user while the app is running. Runtime Permissions covers exactly how.