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.
Service
s
(see the Android developer documentation for full details - here).<service android:name=".MusicService"></service>
android.app.Service
and then launch it from the main activity.onBind()
method in your serviceimport android.app.Service import android.content.Intent import android.os.IBinder class MyService: Service() { // start handler override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return START_STICKY // we will look at this return value below } // bind handler - not needed in many cases but defined as an abstract // method in Service, therefore must be overridden override fun onBind(intent: Intent?): IBinder? { return null // can just return null if binding is not needed } }
Services, like activities, have lifecycle methods including:
onCreate()
- when the service is created;onStartCommand(intent: Intent?, startFlags: Int, id: Int): Int
- runs when a service is started (see below); onBind(intent: Intent?): IBinder?
- runs when a service is bound. As seen above, you have to implement this even if you do not use binding, but it can just return null
onDestroy()
- when a service is destroyed.val startIntent = Intent(this, MusicService::class.java) startService(startIntent)
onStartCommand()
is called when a service is started with startService()
START_STICKY
(see here)START_NOT_STICKY
insteadSTART_STICKY
:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // do something, e.g. start GPS, or music playing, etc. return START_STICKY // Important if you want service to be restarted! }
intent
parameter will be null (again, see here; otherwise, this is the intent used to start the service)stopService()
with the same Intent used to start the service, for example:
stopService(serviceIntent)
onDestroy()
method of the service will be called. In the Service's onDestroy()
, you must stop any threads, background tasks, etc. that the Service is running, as otherwise, the thread will continue to run after its parent Service has been destroyed, resulting in possible memory leaks and unintended behaviourBinder
object which provides access to your Service from your Activity.android.os.Binder
and provides a method to return the Service.onBind()
method within the Service to create a new Binder object and return it when the
Activity binds to the service.
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. 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:
onBind()
method. This method runs when the activity binds to it.musicService
. This is needed as the activity will receive only the binder, not the service. Thus, if the activity wants the service, it must obtain it from the binder.In our activity, we must create a ServiceConnection
object, to obtain a connection to the service. Here is an example:
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:
onServiceConnected()
method is a callback method which runs as soon as the Activity has been bound to the Service. The Binder object (the inner class within our Service, see above) is provided as a parameter to onServiceConnected()
, so we cast it to the correct object (MusicService.MusicServiceBinder
) and obtain our Service using its service
attribute.onServiceDisconnected()
runs when the service is disconnected from the activity. Here, we're not doing anything in the method but we still need to override it.bindService()
to actually bind the serviceFinally, 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.
unbindService()
to let Android know that the activity does not wish to be connected anymore.unbindService()
takes the ServiceConnection as an argumentoverride fun onDestroy() { super.onDestroy() unbindService(gpsServiceConn) }
startService()
and bindService()
.Intent
and as such can pass data to another
application component as a Bundle
BroadcastReceiver
objectaction
IntentFilter
val broadcast = Intent().apply { action = "sendTime" putExtra("time", System.currentTimeMillis()) } sendBroadcast(broadcast)
intent
with an action
of sendTime
time
, containing the current time in
milliseconds since Jan 1st 1970sendBroadcast()
to send the Intent as a broadcastService
(so that the Service can broadcast updates to one or more activities) but could equally well be placed in any application component, such as an activityreceiver = object:BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { "sendTime" -> toast("${intent?.getLongExtra("time", 0)}") } } }
IntentFilter
in our activity to explicitly state
that this activity is capable of receiving intents with an action of sendTime
:
val filter = IntentFilter().apply { addAction("sendTime") }
registerReceiver(receiver, filter)
sendTime
. Even though this is specified by the
intent filter, it is recommended to do this as "it is possible for senders
to force delivery to specific recipients, bypassing filter resolution"
(see the documentation)onDestroy()
override fun onDestroy() { super.onDestroy() unregisterReceiver(receiver) }
https://github.com/nwcourses/ServicesStarter.gitwhich is another version of the mapping application. It features a map plus three buttons: "Start GPS", "Get GPS location" and "Stop GPS". Using a service, complete the app as follows. The functionality associated with each button should only run if the
permissionsGranted
boolean is true
.
initService()
method, start and bind the service. The service should contain a LocationListener. Start this up in a method of the service called startGps()
and call this from onStartCommand()
. Also write a method in the service called stopGps
and remove updates on the listener in this method. Call this method from the onDestroy()
in the service.startGps()
method.stopGps()
method.startGps()
and stopGps()
methods). Register the receiver in onStartCommand()
.