Mobile Application Development - Part 11

Introduction to the Activity Lifecycle, Services and Broadcasts

The Android Activity Lifecycle

To understand Android development more fully it is useful to have understanding of the Android activity lifecycle.

Mobile apps are not quite like standard desktop applications. In particular, the application may be interrupted, most commonly by the user answering a phone call. Also, users commonly switch from one app to another. Unless the user quits the app using the "back" button, when this happens the original app is still running in the background.

To manage these possiblities, Android activities have a defined lifecycle. The idea is to code activities to respond to the different points in the lifecycle. The lifecycle consists of a series of methods which run one after the other, as follows. When coding your app, you override whichever methods you want specific behaviour to occur at. With practically every activity, this includes onCreate(), but your activity might need to override the others too.

The Android activity lifecycle

It is described in full on the Android website, here.

The original article describes the lifecycle in full but to summarise, the following methods run when certain events occur:

The lifecycle can have important consequences for development. For example, in a mapping app you might want to stop GPS communication when the activity becomes invisible and start it again when it becomes visible again (to save battery), in which case you would stop the GPS in onPause() and start it in onResume(). If you did this in onDestroy() and onCreate() instead, the GPS would still be running if the activity was running but invisible, which for a mapping app would be unnecessary.

The differences between onStart()/onStop() and onResume()/onPause() are quite subtle and need only be considered if you are writing activities which do not occupy the whole of the screen, which is not so common. Generally, onPause() and onResume() are more commonly used.

Services

Leaving tasks running when the app is shut down

Frequently in an app we need to perform a task when the activity has been completely shut down. For example, in a music player, we probably want the music to continue to play when the user has closed the player's main activity - and we want the user to be able to pause or rewind the same music when they relaunch the activity. Another example might be a mapping application in which the user would like to record their walking route using GPS. We want the recording to continue even if the user closes the activity - and allow the user to stop the recording if they re-launch the activity.

Introduction to Services

Difference between a service and a coroutine

Intents

To understand services, you need to understand Intents, which are used to communicate between activities and services. What is an Intent? Essentially, it's a message which can be sent in between Android application components, such as between an activity and a service (in both directions) or to launch a second activity from the main activity. They can also be used to launch entirely separate applications; for example you can launch the standard camera app from your own app in order to take a picture. Intents contain two important components:

Implementing a Service

Service lifecycle methods

Rather like activities, services have lifecycle methods including:

Starting a service

Using onStartCommand() to start the service

Stopping a service and overriding onDestroy()

Binding a service

While starting a service is best for long running services, it has the disadvantage that you will not have a reference to the Service in the Activity. If you want to be able to easily control your Service from your Activity, there are two main approaches:

We will first look at binding the service. You must bind it instead of, or in addition to, starting the service (see the Android documentation for more details).

Binding a service is a little trickier than starting it as you need to provide a Binder object. This is an interface to the service which gives the outside world (e.g. the activity) access to it. The Binder object is an object which inherits from android.os.Binder.

The Activity is able to access the Binder (see below) and, as long as the Binder has a method which returns the Service, can obtain the Service. Thus, the Binder should provide a method to return the service.

As seen above, you must also provide an onBind() method within the Service to create a new Binder object and return it when the Activity binds to the service. Remember that you need this method even if you do not intend to use binding, but it can return null in that case.

A common pattern is to have the Binder as an inner class of the Service (an inner class is a class within another class)

Architecture of binding

To bind a service, we need two more components, a Binder (as we have seen) and a ServiceConnection, which is used to provide a connection between the activity and the service, and is discussed further below. The architecture of binding a service, showing the Binder and ServiceConnection, is shown below:
Binding a service

Example

Here is an example. (Note that IBinder is an interface which Binder implements).

class MusicService: Service() {
    inner class MusicServiceBinder(val musicService: MusicService): android.os.Binder()

    override fun onBind(intent:Intent) : IBinder {
        return MusicServiceBinder(this)
    }
}
Note the following:

The ServiceConnection

As we have seen, in our activity, we must create a ServiceConnection object, to obtain a connection to the service. Here is an example - this might go in your onCreate():

 val serviceConn = object: ServiceConnection {
     override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
        service = (binder as MusicService.MusicServiceBinder).musicService
     }

     override fun onServiceDisconnected(name: ComponentName?) {
     }
 }
It is an instance of an anonymous class (a class with no name, which inherits from the abstract class ServiceConnection and overrides the required methods on-the-fly. These methods are:

Using bindService() to initiate the binding

Finally, In your main activity, you bind the service using bindService() which, like startService() takes an Intent for the Service.

val bindIntent = Intent(this, MusicService::class.java);
bindService(bindIntent, serviceConn,  Context.BIND_AUTO_CREATE)
This will bind the activity to the service and trigger the onBind() method in the MusicService. The Context.BIND_AUTO_CREATE flag will "automatically create the service as long as the binding exists" (see here), without this flag, you will also need to call startService() to start the service.

Unbinding

Starting and binding

Broadcasts

BroadcastReceiver

Example of sending a broadcast

val broadcast = Intent().apply {
    action = "sendTime"
    putExtra("time", System.currentTimeMillis())
}
sendBroadcast(broadcast)

Receiving a broadcast

Unregistering a broadcast receiver

Important point - it might take some time to asynchronously start a service

It's important to note that it might take time to start a service. The process of starting a service takes place asynchronously and might take time to complete. This has important implications if you do something in the main activity (such as testing whether a permission has been granted) while waiting for the service to start. You might, for example, send a broadcast to tell the service to start listening for GPS updates as soon as the permissions have been checked. But at this stage (if the user does not need to explicitly grant permission via the dialog) the service may not have been started yet, so there will be no service to broadcast to! Consequently, your broadcast intent will go un-noticed.

A way round this is to send a broadcast from the service to the activity as soon as onStartCommand() has completed. This then tells the activity that the service is started, and ready to receive broadcasts. So as a result, the activity can then check permissions when this broadcast has been received from the service.

Exercise

You are going to write a stopwatch application using services and broadcasts. The idea is to develop a front end allowing the user to start, stop and reset a stopwatch via buttons on the UI. The stopwatch should be controlled by a service. Each button (start, stop and reset) on the UI should send a broadcast to the service, and the service should act accordingly.