Android Services: Getting Started

Learn about Android Services and the differences between foreground, background and bound services. By Gabriela Kordić.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Creating a Service Connection Callback

To use MusicService inside other classes, you have to create its instance. Open MainActivity.kt and add the following at the top of the class:

private var musicService: MusicService? = null

Here, you’re declaring a service variable that holds a service instance. For now, the service is null. You’ll assign a new value when the activity connects to the service by using the Binder interface. To catch the connection state changes, create a connection callback.

Add the code below the musicService initialization:

// 1
private val boundServiceConnection = object : ServiceConnection {

  // 2
  override fun onServiceConnected(className: ComponentName, service: IBinder) {
    val binder: MusicService.MusicBinder = service as MusicService.MusicBinder
    musicService = binder.getService()
    mainViewModel.isMusicServiceBound = true
  }

  // 3
  override fun onServiceDisconnected(arg0: ComponentName) {
    musicService?.runAction(MusicState.STOP)
    musicService = null
    mainViewModel.isMusicServiceBound = false
  }
}

That code is easy to digest:

  1. This is a callback for the service connection state.
  2. When activity connects to service, the system uses MusicBinder instance and getService() to give a reference to musicService.
  3. When service disconnects, the audio will stop if the service reference isn’t already null, clearing the service reference.

This sets a flag, isMusicServiceBound, that checks a service-bound state in both methods according to the parameter you provided. This is important to avoid DeadObjectException exceptions, which remote methods throw when the connection breaks.

Binding to the Service

Your next step is to bind the service when MainActivity starts. Find onStart() and add:

if (!mainViewModel.isMusicServiceBound) bindToMusicService()

Here, you’re checking whether the service has a binding already. If not, you call a binding method.

The binding method doesn’t exist yet, so add its code below onDestroy():

private fun bindToMusicService() {
  // 1
  Intent(this, MusicService::class.java).also {
    // 2
    bindService(it, boundServiceConnection, Context.BIND_AUTO_CREATE)
  }
}

In this code, you:

  1. Declare an intent to start MusicService.
  2. Provide the Intent to the service along with the connection callback and a flag that automatically creates the service if the binding exists.

To avoid memory leaks or bugs, you need to add code to unbind the service. Add the following to unbindMusicService():

unbindService(boundServiceConnection)

With this, you tell the service to execute onServiceDisconnected() in the boundServiceConnection callback.

Using Service Methods

Once the service unbindes, you need to stop the music. Add the following code above the unbindService() line you just added:

musicService?.runAction(MusicState.STOP)

Here, you use the service instance to stop the audio. Remember that you weren’t able to call methods from TimerService. Then, you needed to provide flags through Intent. However, here you have a connection — a binding — to the service, so you’re able to call its methods and receive a response.

To trigger an audio action, use sendCommandToBoundService(). Now, create a generic call for changing the action to reduce a number of code lines. Add this line to sendCommandToBoundService():

musicService?.runAction(state)

With this, you tell the service to execute an action that matches the sendCommandToBoundService() parameter.

There is one more thing to do! Once the song starts to play, the service can provide info about the song’s name. Before you try that out, you need to fix the part with receiving a result.

Inside getNameOfSong(), replace the line which returns “Unknown” text with:

musicService?.getNameOfSong() ?: getString(R.string.unknown)

Here, you call a method from the service that checks which audio track is currently playing and returns an optional String result. If the result is null, you use a resource text instead.

Build and run, then press the Play icon to start the audio. Press GET SONG NAME to see the running song name in a Toast message. Finally, you can enjoy sound while playing your game!

Manipulating the MusicService actions

Interprocess Communication

Every Android app runs in its own process with a unique process ID. When you start an app, the Android system generates a process and runs a main activity on the main thread. This has the advantage that the app lives in an isolated environment and other apps can’t interrupt it.

Based on this, Android allows you to run components in a different process that isn’t used to start an app. To do this, you need to use the process tag inside AndroidManifest.xml. The process can have a random name, like myNewProcess.

Multiprocessing gives your app better security and memory management. If you use it, however, you need to find a way to communicate between different processes.

You only need to include one programming interface — IBinder — but Android provides three ways of defining it:

You already implemented the first approach in this tutorial, but feel free to investigate the other two.

Where to Go From Here?

Download the final project using the Download Materials button at the top or bottom of this tutorial.

You’ve learned a lot of new things about Android Services and how to implement them in different ways. Services are useful for app optimization and improving the user experience.

Here are some links you can refer to:

Android 12 will bring some changes to foreground notifications. Check them out in the official foreground services documentation.

If you have any questions or comments, feel free to join the forum discussion below.