CategoriesBackground Task

Guide to background Task handling – Part 2 (Services)

In this article, we are going to look at Android services. Service is best used when there is a task that needs to be executed in the background without user interaction. However, there are service types that users can interact with. Usually, services are used for implementing long-running tasks.

Types of Services

There are three types of services to choose from based on the nature of the task.

Foreground Service

When we need an application user to notice a background task is processing we use this service type. This service can survive even after the application is destroyed. Users will see a notification when this type of service is running in the background. For example, downloading large files would use a foreground service to handle that task.

Background Service

When there is a background task that needs to implement and it is not necessary to inform the user we can use background service. For example, Performing a cashing mechanism in which the user doesn’t need to know a background service can handle that task.

Bind service

When there is a task that needs to be handled in the background with user interaction a bind service can be used. This interaction can be a form of sending result data back to the UI layer or sending a request to the service to perform an additional task from the UI layer. A bound service runs only as long as another application component is bound to it.

Limitations of Service

A Service can be created by implementing Service class. The services do not run on a separate thread even though the service is commonly referred to as a way to handle background tasks. A Service will always run on the application’s main thread. If the task needs to be executed in the service required to run off the application’s main thread that task should run on a suitable thread solution implanted inside the service. When you start a service on an application it’s your responsibility to close that service.

Above API level 26 there are few restrictions introduced to the services. Basically, it adds limitations on what apps can do while users aren’t directly interacting with them. If a service is started and the app goes to the background that service cannot freely operate in the background. This service will be killed by the OS. Because of this behavior of the services you cannot implement things like accessing location from the background and keep tracking the user while the user was unaware. For these specific implementations, a WorkManager can be used. Refer to this article for more information on service background execution limitations.

Coding Example

Let’s take a look at a coding example. In this coding example, I have implemented examples that showcase all three types of services.

Background Service Coding Example

In this example, I am going to play a music file using MediaPlayer. It is a similar scenario to what you would find on mobile games’ initial screen. Typically a piece of background music will play while you are in that menu to the point at which the game starts. When you create a service by inhering Service it still works on the main thread of the application. If the task has to perform a task that is required to be off the main thread then we have to introduce our own solution inside the service. Let’s take a look at our service class.

class MusicPlayBackgroundService : Service() {

    private val mediaPlayer by lazy { MediaPlayer.create(this, R.raw.sample_track) }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        mediaPlayer.apply {
            isLooping  = true
            start()
        }

        return START_STICKY
    }

    override fun onBind(p0: Intent?): IBinder? {
        return null
    }

    override fun onDestroy() {
        mediaPlayer.stop()
        super.onDestroy()
    }
}

I have a music file in my raw folder. first I lazy initialised the media player by providing that file. When a service is started it will trigger the onStartCommand function. That is the function which the media player should start to play the music. I’ve added the necessary code to play the file there.

onStartCommand function required to specify a return type out of the following values. This return type determines how the system should handle the in the event of service gets killed by the operating system.

START_NOT_STICKY

Having this value as the return type of the onStartCommand() make sure the service is not restarted in the event that it gets killed by the operating system. However, there is an exception to this, if there is a pending intent to be delivered the service may restart to complete that delivery.

START_STICKY

Having this value as the return type of the onStartCommand() make sure the service is not restarted in the event that it gets killed by the operating system. However, there is an exception to this, if there is a pending intent to be delivered the service may restart to complete that delivery.

START_REDELIVER_INTENT

Having this value as the return type of the onStartCommand() will restart the service and trigger onStartCommand() function again while delivering the last intent to that function. Any pending intents are delivered in return. This is good for executing tasks that need to resume.

So after defining the service class like above you need to add this service to the manifest file like this.

<service
	android:name=".services.MusicPlayBackgroundService"
  android:enabled="true"
  android:exported="false" />

and on the UI This service can be called like this.

val serviceIntent = Intent(context,MusicPlayBackgroundService::class.java)

LaunchedEffect(key1 = Unit){
        context.startService(serviceIntent)
}

It’s your responsibility to stop the service when your task is done to free up the mobile phone resources. So a service must be manually stopped by either call stopSelf() inside the Service class. or like from outside the service class by using stopService() function. I have configured this service to stop as soon as I leave the screen.

DisposableEffect(key1 = Unit){
        onDispose {
            context.stopService(serviceIntent)
        }
    }

Foreground Service Coding Example

The foreground service implementation is very similar to the background implementation. The only difference is this the foreground service going to show a notification to the user which allows us to keep it alive in the background to perform long-running tasks. In this example, I’m going to play a piece of music like in the above background service But this time when the service is started I’m going to keep it alive in the background until the application is closed. To start a foreground service a notification must be defined to inform the application that a background service is running. Let’s take a look at the service class on the create function.

private val notificationManager by lazy { getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
private lateinit var notification: Notification

    override fun onCreate() {
        super.onCreate()

        notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle(getText(R.string.music_notification_title))
            .setContentText(getText(R.string.music_notification_description))
            .setSmallIcon(android.R.drawable.ic_media_play)
            .build()

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationChannel = NotificationChannel(CHANNEL_ID,
                "Music Play Notification",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationChannel.description = "This is music play notification"
            notificationManager.createNotificationChannel(notificationChannel)
            notificationManager.notify(MUSIC_PLAY_NOTIFICATION_ID, notification)
        }

        startForeground(MUSIC_PLAY_NOTIFICATION_ID, notification)

    }

In a foreground service, a notification must be defined and call the function startForeground() to make it run as a foreground service in the Android operating system. To show a notification first we need to define the notification using notification compact builder then on the Android OS levels Orio and above we need to define the notification channel to show that notification. Everything else is the same as the background service as far as implementation goes but, since this foreground service is meant to live throughout the application till the application gets killed, I called the stopService() function inside onDestroy() function in the application class.

override fun onDestroy() {
    val serviceIntent = Intent(this, MusicPlayForegroundService::class.java)
    stopService(serviceIntent)
    super.onDestroy()
}

Bind service Coding Example

A bind service is a service that allows the UI layer to bind with the service and interact with it. This type of service runs only as long as another application component is bound to it. Multiple components can bind to the service at once, but when all of them unbind, the service is destroyed. When the UI layer binds to the service, it creates a connection between the UI layer and the service. This connection allows the UI layer to interact with the service and access its methods and variables. Once the UI layer is done with the service, it unbinds from the service to release any resources used by the service. Bind service is useful when we need to perform a task in the background and have the UI layer access the results of that task. In this example I’m going to implement the same music play functionality I used in the above example, but this time user has the ability to start and stop the music from UI.

class MusicPlayBindService : Service() {

    private val binder = MusicPlayBinder()
    private val mediaPlayer by lazy { MediaPlayer.create(this, R.raw.sample_track) }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    inner class MusicPlayBinder : Binder() {
        fun getService() : MusicPlayBindService {
            return this@MusicPlayBindService
        }
    }

    override fun onCreate() {
        mediaPlayer.apply {
            isLooping = true
        }

    }

    fun startMediaPlayer(){
        mediaPlayer.start()
    }

    fun stopMediaPlayer(){
        mediaPlayer.stop()
    }

}

When implementing a bind service we need to return the service that the UI wishes to access. First, an inner class needs to be created which inherits the Binder class. inside that, we can create a function and return the service. Inside the service class, an instance of that bind class should be created and that instance should be returned to the onBind() function. startMediaPlayer() and stopMediaPlayer() functions are responsible for starting and stopping the music respectively.

In the UI layer implement ServiceConnection to catch the service connection and disconnection events. on connect event we can get access to the bind service instance and save it into a local variable.

var bindService: MusicPlayBindService? by remember { mutableStateOf(null) }
var isBound by remember { mutableStateOf(false) }

val bindServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        val binder = service as MusicPlayBindService.MusicPlayBinder
        bindService = binder.getService()
        isBound = true
    }

    override fun onServiceDisconnected(name: ComponentName) {
        isBound = false
    }
}

Inside the LaunchedEffect on the screen the MusicPlayBindService is started by adding the below lines.

LaunchedEffect(key1 = Unit){
    val intent = Intent(context, MusicPlayBindService::class.java)
    context.bindService(intent, bindServiceConnection, Context.BIND_AUTO_CREATE)
}

Finally, The function for starting and stopping the music can be defined as below and connected to the respective buttons on the UI.

fun onClickStart(isBound: Boolean, service: MusicPlayBindService?){
    if(isBound){
        service?.startMediaPlayer()
    }
}

fun onClickStop(isBound: Boolean, service: MusicPlayBindService?){
    if(isBound){
        service?.stopMediaPlayer()
    }
}

This example concludes the article about services. You can find the code in this repository. In the main application UI, click on the “Service” button to see the above examples running.

Published by Chathuranga Shan

I am a mobile application developer who loves to get into new technologies in the Industry. Specialized in Android mobile application development with 5+ years’ experience working for different types of products across many business domains.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.