Started services are great for background operations, but what if you need a service that’s more interactive?
… a type of service your activity can interact with.
Bound services are bound to other components…
A bound service is one thats’s bound to another application component, such as an activity. Unlike a started service, the component can interact with the bound service and acall its methods.
public class OdometerService extends Service { @Override public IBinder onBind(Intent intent) { } }
The above code implements one method, onBind(), which gets called when a component, such as an activity, wants to bind tho the service. It has one parameter, an Intent, and returns an IBinder object. IBinder is an interface that’s used to bind your servie to the activity, and you need to provied an implementation of it in your service code.
Implement a binder
You implement the IBinder by adding a new inner class to your service code that extends the Binder class(which implements the IBinder interface). This inner class needs to include a method that activities can user to get a reference to the bound service.
//When you create a bound service, you need to provide a Binder implementation public class OdometerBinder extends Binder{ //The activity will use this method to get a reference to the OdometerService OdometerService getOdometer(){ return OdometerService.this; } }
We need to return an instance of the OdometerBinder in OdormeterService’s onBind() method. To do this, we’ll create a new private vaiable for the inder, instantiate it, and return it in the onBind() method.
package eu.kio.odometer; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.Binder; import java.util.Random; public class OdometerService extends Service { //We're using a private final variable for our IBinder object private final IBinder binder = new OdometerBinder(); //This is our IBinder implementation piblic class OdometerBinder extends Binder{ OdometerService getOdometer(){ return OdometerService.this; } } @Override public IBinder onBind(Intent intent){ //Return the IBinder return binder; } }
Add a getDistance() method to the service
We’re going to add a method to OdometerService called getDistnce(), which our activity will call. We’ll get it to return a random number.
Here’s the full code…
package eu.kio.odometer; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.Binder; import java.util.Random; public class OdometerService extends Service { //We're using a private final vaiable for our IBinder-object private final IBinder binder = new OdometerBinder(); //We'll use Random() object to generate random numbers private final Random random = new Random(); //This is our IBinder implementation public class OdometerBinder extends Binder{ OdometerService getOdometer(){ return OdometerService.this; } } public OdometerService() { } @Override public IBinder onBind(Intent intent) { //Return the IBinder return binder; } //Add the getDistance method public double getDistance(){ return random.nextDouble(); } }
Get an activity to bind to OdometerService and call its getDistance() method.
a TextView will display the number returned by OdometerService’s getDistance() method
What the activity needs to do
To get an activity to connect to a bound service and call its methods, there are a few steps you need to perform:
- Create a service Connection
This uses the service’s IBiner object to form a connection with the service - Bind the activity to the service
Once you’ve bound it to the service, you can call the service’s methods directly - Interact with the service
In our case, we’ll use the service’s getDistance() method to update the acteivity’s text view - Unbind from the service when you’ve finished with it
When the service is no longer used,Android destroys the service to free up resources
Create a ServiceConnection
A ServiceConnection is an interface that enables your activity to bin to a service.It hast two methods that you need to define:
- onServiceConnected()
- onServiceDisconnected()
The onServiceConnected() method is called when a connection to the sercie is established.
The onServiceDisconected() is called when it disconnects.
Here’s what the basic code looks like:
package eu.kio.odometer; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; import android.content.ComponentName; import android.widget.TextView; import java.util.Locale; public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false; //Create a Service Connection object private ServiceConnection connection = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName componentName,IBinder binder){ //Code that runs when the service is connected } @Override public void onServiceDisconnected(ComponetName componentName){ //Code that runs when the service is disconnected } };
The onServiceConnceted() method is called when a connection is established between the activity and the service. It takes two parameters:
- ComponentName
this object describes the service that’s been connected - IBinder
this objet that’s defined by the service
There are two things we need the onServiceConnected() method to do:
- Use it’s IBinder parameter to get a reference to the service we’re connected to
We can do this by casting the IBinder to an OdometerService.OdometerBinder (as this is the type of IBinder we defined in OdometerService) and calling its getOdometer() method. - Record that the activity is bound to the service
Here’s the code to do these things:
..... public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false; //Create a Service Connection object private ServiceConnection connection = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName componentName,IBinder binder){ //Casting IBinder binder to OdometerService.OdometerBinder OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; //Use the IBinder to get a reference to the service odometer = odometerBinder.getOdometer(); //Record that the activity is bound to the service bound = true; } .....
The onServiceDisconnected() method is called when the service and the activity are disconnected. It takes one parameter, a ComponentName object that describes the service.
The only one thing we need the onServiceDisconnected() method to do when it’s called:
- record that the activity is no longer bound to the service
Here’s the code to do that…
.... @Override public void onServiceDisconnected(ComponentName componentName){ //Code that runs when the service is disconnected //Set bound to false, as MainACtivity is no longer bound to OdometerService bound = false; } ....
How to bind and unbind from the service
Use bindService() to bind the service
When you bind your service activity to a service, you usually do it in one of two places:
- In the activity’s onStart() method
When the activity becomes visible - In the activity’s onCreate() method
When the activity gets crated
We only need to display updates OdometerService when Mainactivity is visible, wo we’ll bind to the service in its onStart() method.
To bind to the service, you first reate an explicit intent that’s directed at the service you want to bind to. You then use the activity’s bindSercice() method to bind to the service, passing it the …
- the intent
- the ServicConncetion object defined by the service
- a flag to descibe how you want to bind
Here’s the code …
.... @Override protected void onStart(){ super.onStart(); Intent intent = new Intent(this, OdometerSerivice.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } ....
In the above example, we’ve used the flag Context.BIND_AUTO_CRATE to tell Anroid to create the service if it doesn’t already exist. There are other flags you can use instead…
Android documentation:
https://developer.android.com/reference/android/content/Context.html
Use unbindService() to unbind from the service
When you unbind your activity from a service, you usually add the code to your activity’s onStop() or onDestroy() method. It depends on where you put your bindService() code:
- If you bound to the service in your activity’s onStart() method, unbind from it in the onStop() method
- If you bound to the service in your activity’s onCreate() method, unbind from it in the onDestroy() method
You unbind from a service using the unbindService() method. The method takes on parameter, the ServiceConnection object.
Here’s the code…
.... private boolean bound = false; .... @Override protected void onStop(){ super.onStop(); if(bound){ unbindService(Connection); bound = false; } } ....
In the above code we’re using the value of the bound variable to test whether or not we need to unbind from the service.
The final thing is to get the activity to call OdometerService’s getDistance() method, and display its value
We’re going to call the OdometerServic’s getDistance() method every second, and update the TextView with its value using an Handler
Here’s the method….
private void displayDistance(){ //Get the TextView final TextView distanceView = (TextView)findViewById(R.id.distance); //Create a new Handler final Handler handler = new Handler(); //Call the Handler's post() method, passing in a new Runnable handler.post(new Runnable(){ @Override public void run(){ double distance = 0.0; if(bound && odometer != null){ //If we've got a reference to the OdometerService and we're bound to it, call getDistance() distance = odometer.getDistance(); } String distanceStr = String.format(Locale.getDefault(),"%1$,.2f miles",distance); distanceView.setText(distanceStr); /**Post the code in the Runnable to * be run again after a delay of 1 second. As this line of code is included in the Runnable * run() method.it will run every second (with a slight lag). */ handler.postDelayed(this,1000); } }); }
Full code for context….
OdometerService:
package eu.kio.odometer; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.Binder; import java.util.Random; public class OdometerService extends Service { //We're using a private final vaiable for our IBinder-object private final IBinder binder = new OdometerBinder(); private final Random random = new Random(); //This is our IBinder implementation public class OdometerBinder extends Binder{ OdometerService getOdometer(){ return OdometerService.this; } } public OdometerService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. //throw new UnsupportedOperationException("Not yet implemented"); //Return the IBinder return binder; } public double getDistance(){ return random.nextDouble(); } }
Mainactivity:
package eu.kio.odometer; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.content.ServiceConnection; import android.os.Handler; import android.os.IBinder; import android.content.ComponentName; import android.widget.TextView; import java.util.Locale; public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false; //Create a Service Connection object private ServiceConnection connection = new ServiceConnection(){ @Override public void onServiceConnected(ComponentName componentName,IBinder binder){ //Code that runs when the service is connected OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; //Use the IBinder to get a reference to the service odometer = odometerBinder.getOdometer(); //The activity is bound to the service, so set the bound variable to true bound = true; } @Override public void onServiceDisconnected(ComponentName componentName){ //Code that runs when the service is disconnected //Set bound to false, as MainACtivity is no longer bound to OdometerService bound = false; } }; @Override protected void onStart(){ super.onStart(); Intent intent = new Intent(this,OdometerService.class); bindService(intent,connection,Context.BIND_AUTO_CREATE); } @Override protected void onStop(){ super.onStop(); if(bound){ //This uses the SevicConnection object to unbind from the service unbindService(connection); //When we unbind, we'll set bound to false bound = false; } } private void displayDistance(){ //Get the TextView final TextView distanceView = (TextView)findViewById(R.id.distance); //Create a new Handler final Handler handler = new Handler(); //Call the Handler's post() method, passing in a new Runnable handler.post(new Runnable(){ @Override public void run(){ double distance = 0.0; if(bound && odometer != null){ //If we've got a reference to the OdometerService and we're bound to it, call getDistance() distance = odometer.getDistance(); } String distanceStr = String.format(Locale.getDefault(),"%1$,.2f miles",distance); distanceView.setText(distanceStr); /**Post the code in the Runnable to * be run again after a delay of 1 second. As this line of code is included in the Runnable * run() method.it will run every second (with a slight lag). */ handler.postDelayed(this,1000); } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //Update the TextView's value every second displayDistance(); } }