Foreground Services in Android
A Foreground Service is an Android service that performs a task while actively notifying the user, generally through a notification in the status bar. Unlike background services, which may get paused or terminated by the system to free up resources, foreground services are prioritized, ensuring that they continue running as long as needed.
Why Use a Foreground Service?
Foreground services are ideal for tasks that:
When to Use Foreground Services
Use a foreground service when:
How to Implement a Foreground Service
In Kotlin, implementing a foreground service involves:
Let's look at a creative example: a Location Tracking foreground service.
Step 1: Define the Notification Channel in the Application Class
Defining the notification channel in the Application class avoids unnecessary re-creation and ensures it’s available across the app.
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Location Tracking",
NotificationManager.IMPORTANCE_LOW // Low importance to avoid disturbing the user
)
val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(channel)
}
}
companion object {
const val CHANNEL_ID = "location_channel"
}
}
Step 2: Define the Foreground Service with Action Control
Using onStartCommand, we define actions (ACTION_START and ACTION_STOP) to start or stop the service. START_STICKY in onStartCommand tells the system to recreate the service if it’s killed, ideal for long-running tasks.
领英推荐
class LocationTrackingService : Service() {
private lateinit var locationManager: LocationManager
private val geocoder by lazy { Geocoder(this, Locale.getDefault()) }
private var job: Job? = null
override fun onCreate() {
super.onCreate()
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
Actions.START.toString() -> startForegroundServiceWithNotification()
Actions.STOP.toString() -> stopSelf()
}
return START_STICKY
}
private fun startForegroundServiceWithNotification() {
val notification = buildNotification("Starting location tracking...")
startForeground(NOTIFICATION_ID, notification)
startLocationUpdates()
}
private fun buildNotification(content: String) =
NotificationCompat.Builder(this, LocationTrackerApp.CHANNEL_ID)
.setContentTitle("Location Tracker")
.setContentText(content)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.build()
@SuppressLint("MissingPermission")
private fun startLocationUpdates() {
try {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000L,
10f
) { location -> updateNotificationWithAddress(location) }
} catch (e: SecurityException) {
Log.e("LocationTrackingService", "Location permission not granted.")
}
}
@SuppressLint("NotificationPermission")
private fun updateNotificationWithAddress(location: Location) {
Toast.makeText(this, "${location.latitude} - ${location.longitude}", Toast.LENGTH_LONG)
.show()
job = CoroutineScope(Dispatchers.IO).launch {
try {
val addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1)
val address = addresses?.firstOrNull()
val addressText = address?.thoroughfare ?: "Unknown Location"
val notification = buildNotification("Current Location: $addressText")
val notificationManager =
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, notification)
} catch (e: Exception) {
Log.e("LocationTrackingService", "Failed to fetch address: ${e.message}")
}
}
}
override fun onDestroy() {
super.onDestroy()
locationManager.removeUpdates { }
job?.cancel()
}
companion object {
const val NOTIFICATION_ID = 1
}
enum class Actions {
START, STOP
}
}
Explanation
Step 3: Add Permissions to the Manifest
Add necessary permissions to the manifest, including ACCESS_FINE_LOCATION for location tracking and POST_NOTIFICATIONS for Android 13 and above.
<manifest>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<application
android:name=".MyApplication"
...>
<service android:name=".LocationTrackingService"
android:foregroundServiceType="location" />
</application>
</manifest>
Step 4: Request Permissions in the Activity
Create an Activity with buttons to start and stop the service. Request location and notification permissions before starting the service.
class MainActivity : ComponentActivity() {
private val permissions = mutableListOf(
ACCESS_FINE_LOCATION,
ACCESS_COARSE_LOCATION
)
private val requestPermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val granted = permissions.all { it.value }
if (granted) {
startLocationService()
} else {
Toast.makeText(this, "Permission needed", Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions.add(POST_NOTIFICATIONS)
}
enableEdgeToEdge()
setContent {
LocationTrackerTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { _ ->
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Button(
onClick = {
requestPermissions.launch(permissions.toTypedArray())
}
) {
Text("Start Track")
}
Button(
onClick = {
stopLocationService()
}
) {
Text("Stop Track")
}
}
}
}
}
}
private fun startLocationService() {
if(!isGPSEnabled(this)){
enableGPS(this)
return
}
val startIntent = Intent(this, LocationTrackingService::class.java).apply {
action = LocationTrackingService.Actions.START.toString()
}
startService(startIntent)
}
private fun stopLocationService() {
val stopIntent = Intent(this, LocationTrackingService::class.java).apply {
action = LocationTrackingService.Actions.STOP.toString()
}
startService(stopIntent)
}
fun isGPSEnabled(context: Context): Boolean {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
}
fun enableGPS(context: Context) {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
context.startActivity(intent)
}
}
Benefits of Using a Foreground Service Here
When Not to Use a Foreground Service
Avoid using foreground services for:
Why onBind and IBinder Aren’t Needed in a Foreground Service
For a foreground service, onBind is generally not needed because:
Conclusion
Foreground services provide Android developers with the means to handle long-term tasks needing user awareness. This example illustrated best practices, including notification channels, permission management, lifecycle efficiency, and action-based control. By following this approach, you can create reliable, user-centered foreground services.