Android Services: Getting Started
Learn about Android Services and the differences between foreground, background and bound services. By Gabriela Kordić.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Services: Getting Started
30 mins
- Getting Started
- Introducing Android Services
- Declaring a Service in the Manifest
- Implementing the Service Interface
- Creating a Foreground Service
- Implementing the Service Methods
- Adding Permissions
- Creating a Notification Channel
- Creating a Notification Builder
- Building a Notification
- Starting and Stopping the Service
- Updating the Notification When the Timer Changes
- Using Background Processing for Complex Work
- Explaining Background Execution Limits
- Creating a Bound Service
- Converting MusicService into actual Service
- Defining a Binder
- Defining Service Methods for Managing Audio
- Creating a Service Connection Callback
- Binding to the Service
- Using Service Methods
- Interprocess Communication
- Where to Go From Here?
Implementing the Service Methods
Look closely and notice that Android Studio is showing an error because onBind()
is missing.
To fix this, add the following methods to TimerService
:
// 1
override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
// 2
intent?.extras?.run {
when (getSerializable(SERVICE_COMMAND) as TimerState) {
TimerState.START -> startTimer()
TimerState.PAUSE -> pauseTimerService()
TimerState.STOP -> endTimerService()
else -> return START_NOT_STICKY
}
}
// 3
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
// 4
handler.removeCallbacks(runnable)
job.cancel()
}
Here is a breakdown of the code:
- Since this is a foreground service, you don’t need binding so you return
null
instead ofIBinder
. Also note that you won’t implementonCreate()
now since you don’t need any specific setup for the service. - Here, you go through the
Intent
extras to get the value that the keySERVICE_COMMAND
saves. This value indicates which action the service should execute. - If the system kills the service because the memory runs out,
START_NOT_STICKY
tells the system not to recreate the service with an undefinedIntent
. Alternative constants areSTART_STICKY
andSTART_REDELIVER_INTENT
. If you’re interested in these, check out the official service constants documentation. - Remove all
Handler
callbacks for the ticking time to keep the memory clean. Also, clean up the resources by canceling the whole timerJob
since you’ll destroy the service in the next step.
Adding Permissions
All apps that target Android 9 (API level 28) or higher must request permission to use a foreground service. The system automatically grants that request because it’s a type of normal permission. Failure to do so leads the app to throw a SecurityException.
In Project view, navigate to app ▸ manifest ▸ AndroidManifest.xml and add the following code above the application
tag:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
This lets you run the foreground service in Memo.
Next, you’ll create a notification for the service.
Creating a Notification Channel
Every foreground service must notify the user that it has started. You also need to define a Notification Manager, which notifies the user of events that happen and holds information about one or more notifications.
Open NotificationHelper.kt and add this code at the top of the class:
private val notificationManager by lazy {
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
Here, you’re lazy initializing notificationManager
by getting the NOTIFICATION_SERVICE
system service and casting it as NotificationManager
.
In Android 8 and later, the notification must be part of a notification channel. To create one, add this code to NotificationHelper.kt:
@RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() =
// 1
NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
).apply {
// 2
description = CHANNEL_DESCRIPTION
setSound(null, null)
}
In this code, you:
- Create a notification channel with
CHANNEL_ID
as its ID,CHANNEL_NAME
as its name and the default importance. - Set the notification channel’s description to
CHANNEL_DESCRIPTION
and sound tonull
which just means that there is no sound played when a notification is triggered in the channel.
The next step is to define a notification view.
Creating a Notification Builder
For the user to be aware of the service running, create a view that the user can see in the status bar. At the top of NotificationHelper.kt, add the notification code:
// 1
private val notificationBuilder: NotificationCompat.Builder by lazy {
NotificationCompat.Builder(context, CHANNEL_ID)
// 2
.setContentTitle(context.getString(R.string.app_name))
.setSound(null)
.setContentIntent(contentIntent)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationCompat.PRIORITY_HIGH)
// 3
.setAutoCancel(true)
}
If you get an error saying contentIntent
isn’t defined, don’t worry. You’ll take care of it in the next steps.
For now, in this code:
- You’re using
NotificationCompat.Builder
to create a status bar notification builder. - The builder has to contain information about some of the notification’s options. However, you don’t have to define the notification options when you declare a builder. If you’re missing information about the title, sound and so on, you can set that information later, in the notification itself.
- This kind of notification should be set as auto-cancelable, which means that when the user clicks it, it’s automatically dismissed.
Building a Notification
Now, it’s time to build the notification itself. Add the following to NotificationHelper.kt:
fun getNotification(): Notification {
// 1
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.createNotificationChannel(createChannel())
}
// 2
return notificationBuilder.build()
}
Here’s what’s happening above:
- If the version of Android is equal to or greater than Android 8, the system creates a notification channel and returns
Notification
. Unfortunately, the Support Library for versions before Android 8 doesn’t provide notification channel APIs. Read more about this issue in the official notification channel documentation. - Return a
Notification
after invokingbuild()
on the builder.
Notification can be dynamically updated. In this case, you’ll update the text if the user closes the app while the service is running.
Below getNotification()
, add the following:
fun updateNotification(notificationText: String? = null) {
// 1
notificationText?.let { notificationBuilder.setContentText(it) }
// 2
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
Here’s what’s happening:
- You update the text in the notification published in the status bar via the notificationBuilder
- Then notify the Notification Manager about which notification to update. To do that, you use a unique
NOTIFICATION_ID
.
Finally, you’ll define what happens when a user clicks on the notification. Add this to the top of NotificationHelper.kt:
private val contentIntent by lazy {
PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT
)
}
Here, you’re doing a lazy initialization of PendingIntent
, which launches MainActivity
when the user presses the notification.
Great job! You’ve done a lot of work already. Now, it’s time to actually run the foreground service.
Starting and Stopping the Service
To start the service in the foreground, use startForeground()
. This method needs two parameters: the unique positive integer ID of the notification and Notificaton
.
Open TimerService.kt and add the below to startTimer()
method:
startForeground(NotificationHelper.NOTIFICATION_ID, helper.getNotification())
This method makes the service run in the foreground and posts the notification with the ID of NOTIFICATION_ID
to the status bar. You’ll notice that helper
is highlighted in red because you haven’t defined that variable yet.
To fix that, create an instance of NotificationHelper
class at the top of TimerService
:
private val helper by lazy { NotificationHelper(this) }
To try starting the service from another component, open MainActivity.kt, find sendCommandToForegroundService()
and add:
ContextCompat.startForegroundService(this, getServiceIntent(timerState))
Here, you start the service from Activity. You pass its Context and the Intent to start the service.
Everything that starts must end, and that includes Service
. Open TimerService.kt and add the following to stopService()
:
// 1
stopForeground(true)
// 2
stopSelf()
In this code block:
- This call tells the system that it should remove this service from foreground state. The passed boolean argument corresponds to removing the notification when set to
true
- Since a service can start itself, it must handle stopping itself, as well.
stopSelf()
handles that use case.