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?
Updating the Notification When the Timer Changes
TimerService
contains a coroutine that ticks every second while the level is running. You’ll use the timer value to update the notification view.
Look for broadcastUpdate()
inside the TimerService.kt, and add this code inside the if
:
// 1
sendBroadcast(
Intent(TIMER_ACTION)
.putExtra(NOTIFICATION_TEXT, elapsedTime)
)
// 2
helper.updateNotification(
getString(R.string.time_is_running, elapsedTime.secondsToTime())
)
Here is what this code block does:
- Here, you send a broadcast with the elapsed time to
MainActivity
. With it,MainActivity
can update the time in theTextView
below the card’s view. - This helper method updates the status bar notification you posted above.
At this point, you’ve implemented everything the user sees while playing the game. But what happens if the user kills the app while the timer is running?
To handle that, add the following line to broadcastUpdate()
in the else if
:
helper.updateNotification(getString(R.string.get_back))
This line updates the notification text to call the user back to the game.
Awesome work! Build and run to test the timer. Start a level and notice how the timer changes at the bottom. Pull down the status bar to see how the timer changes within the notification. Try exiting the application to test the message text update in the notification.
Using Background Processing for Complex Work
Sometimes, the user doesn’t need to know about certain actions happening, e.g. if you store the user’s activity on the server for developing purposes, you don’t have to display a notification. Place this kind of continuous work and similar in the background.
To achieve that, you can use:
- Custom Background Service – by default, the service runs on the UI thread. To avoid blocking it, create a Service with a job processing on the background thread.
- IntentService – a subclass of Service that executes requests sequentially by using worker thread. Since Android 8, it’s usage is not recommended. Also, IntentService is deprecated from Android 11.
- JobIntentService – a replacement for IntentService. Instead of service, it uses JobScheduler for executing jobs. In earlier versions than Android 8, it will act just like IntentService. For more info, read the official JobIntentService documentation.
- Background work with WorkManager – this is a general concept for doing background work in Android. Use it when you want to execute a periodical job in the future, or when you have some job constraints.
Explaining Background Execution Limits
Android 8 and newer versions have restrictions when using Android Services on the background thread:
- The system will destroy the service after some time if the app runs the service in the background. Once it goes to the background, it has a window of a few minutes in which it can interact with services. After that, the system kills all running services.
- The system will throw an exception if the service was started when the app is in the background and outside of the time window.
Find more in Background execution limits documentation.
Now, it’s time to learn about the last kind of service.
Creating a Bound Service
A bound service is an implementation of Service
. It’s a component that other components bind to, interacting with it and performing interprocess communication (IPC). The bound component can be an Activity, another service, a broadcast receiver or a content provider.
The lifecycle of the bound service depends on another application component. When the component unbinds, the bound service destroys itself. Consequently, it can’t run in the background forever.
However, it doesn’t necessarily need a bound component — it can start and stop itself as well. In that case, there’s an option to run indefinitely.
To try this out, you’ll give Memo the ability to play audio.
Converting MusicService into actual Service
To implement the audio playing feature, you’ll use MusicService
. Firstly, modify it to have access to basic service methods.
Open MusicService.kt and make it extend Service
:
class MusicService : Service() {
As its type says, another component will use this service to bind to it. You already learned about the mandatory method you need for binding to service, so add it to MusicService
:
override fun onBind(intent: Intent?): IBinder = binder
The difference between the last implementation of onBind()
and this one is, obviously, the return value. Since you’re binding to service here, you need to provide the IBinder value.
IBinder is a programming interface of Binder that clients use to interact with the service. Its methods allow you to send a call to an IBinder object and receive a call coming in to a Binder object. To learn more, check out IBinder’s documentation.
Android Studio shows an error because you haven’t defined binder
variable. To fix this, add the following to the top of the class:
private val binder by lazy { MusicBinder() }
Here, you’re doing a lazy initialization of binder
, which is a type of MusicBinder
. Implementation of MusicBinder
is your next step.
Defining a Binder
This service can’t be accessed outside of this application and doesn’t have its own process. Therefore, you have to create an interface that will have access to the service’s context. Do that by extending the Binder class. This way all components can use all public methods from Binder or the Service.
Now, to define the interface, add these lines at the bottom of the class:
inner class MusicBinder : Binder() {
fun getService(): MusicService = this@MusicService
}
MusicBinder
is nested inside another class and it can access all methods from it. It can use all of Binder’s methods as well. Inside of it, you create a method for retrieving a service context.
Defining Service Methods for Managing Audio
This service allows the user to interact with the audio icons for playing, pausing, stopping and shuffling the music. For simplicity, the methods for managing audio are already defined. However, a music player isn’t.
To instantiate the music player, use this code inside initializeMediaPlayer()
inside MusicService.kt:
musicMediaPlayer = MediaPlayer.create(this, randomSongs.first()).apply {
isLooping = true
}
Here, you use MediaPlayer
to run a continuously repeating audio of the first song in randomSongs
.
Another fun feature is that this bound service can provide the name of the track that’s playing. Inside MusicService
, add:
fun getNameOfSong(): String =
resources.getResourceEntryName(randomSongs.first())
.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.ENGLISH)
else it.toString()
}.replace("_", " ")
In this method, you’re reading a track name from the resources and changing the result String
to be more readable to the user.
All done! Now, you’ll use these methods from another component.