S. Gökhan Topçu
gtopcu@gmail.com
In my previous blog, I explained how you can use started services to handle one-off requests initiated by the same or different applications. In this blog, we will look at what bound services are, and where and how we can use them.
Bound services are services which override onBind() method and return an android.os.IBinder instance to its client, which the client can later use to interact with the service. Bound services can be called from the same applications, or from different applications using IPC (inter-process communication).
IPC is an OS level communication technology which works similar to RPC (remote procedure call) in Java which lets applications to execute methods residing in different JVMs (Java Virtual Machines). IPC also lets apps make native calls to components running in different application processes by marshalling/unmarshalling the variables and objects clients/services exchange while invoking each other's methods. With a bound service, you can provide clients outside your application to use the services you may provide. For instance, if you design a service which downloads and returns a list of available proxy servers and expose it by using specific intent filters (and use android:exported="true flag in your manifest), then other applications can use your service by binding to it.
A bound service has three methods that we ignored in our started service examples: onBind(), onUnbind() and onRebind(). The boolean parameter returned from the unUnbind() method specifies if the onRebind() method should be called when a client binds again after it has unbounded from the service.
Only activities, services, and content providers can bind to a service - you cannot bind to a service from a broadcast receiver. Multiple clients can bind to the same service. If the service instance does not exist when a client binds, then onCreate() gets called and an instance is created, onBind() is called and instance of the IBinder class is returned to the client. If another client binds to the service, onBind() method is not called again and the same IBinder is returned to all the other clients afterwards. And when all bound components unbind, then the service gets destroyed and onDestroy() is called.
It's important to note that a bound service can also be started - you can override onStartCommand() and throw a RuntimeException if you really want to stop any clients to start your service or just remind yourself that you shouldn't be starting this service. And if a bound service started as well, it will continue its existence even if all bound components unbind, until stopSelf() or stopService() methods are called to stop the service.
Components (activities, services or content providers) can bind to a service using bindService() method, and unbind using unbindService(). These methods take an instance of the ServiceConnection class which you override and pass them, and has two methods: onServiceConnected() and onServiceDisconnected(). onServiceConnected() gets called by the OS when your component binds the service and returns the IBinder object that is returned by the onBind() method in the service. onServiceDisconnected() is called when the service gets disconnected unexpectedly (i.e. when killed by OS to gain more resources). Be sure not to do any heavy processing here, since although these methods will be called by the OS, they will be executed on your main application thread (UI thread).
The bindService() method also takes a start mode flag as a parameter. You can use Context.BIND_AUTO_CREATE here so that the service will be created for you if no instance exists when a client decides to bound to it.
There are three ways to define a bound service depending on how the IBinder returned from the onBind() method is constructed:
1. Extending the Binder class
2. Using a Messenger
3. Using AIDL (Android Interface Definition Language)
1. Extending the Binder class
If your service doesn't need to serve other components in separate applications or processes, you can use an instance of the Binder class which is a subclass of IBinder and return it from onBind(). You can define the methods you'd like your service to provide in your Binder implementation as public methods, and these methods can call any other public methods in your service or even call other components to do their job. You can also return an instance of your service with a method in your Binder implementation, so that the clients can invoke the public methods in your service directly.This scenario is useful if you want to offshore some background work to your service and your service will always be kept within your application. The reason that this way of defining an IBinder won't work across applications/processes is because IPC is not used when using a Binder implementation. Binding components will need to cast the IBinder class returned from onServiceStarted() method in the ServiceConnection instance defined in the client to your own Binder implementation so that they will be able to access the methods defined.
Below is an example of a bound service which returns an IBinder using a custom Binder implementation. Notice how the binder class allows the binding component to access methods it defines, as well as the public methods in the service it resides in by returning an instance of it:
MyBoundService.java
/** @author S. Gokhan TOPCU
*/
package com.gtware.android.blogs.services;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.widget.Toast;
public class MyBoundService extends Service {
private IBinder mBinder; // interface for clients that bind
private boolean mAllowRebind; // indicates whether onRebind should be used
/*
* This works only if the client and service are in the same application and process
* The reason the service and client must be in the same application is so the client
* can cast the returned object and properly call its APIs. The service and client
* must also be in the same process, because this technique does not perform any
* marshalling across processes.
*
* You can implement methods which will call methods in the service or the bound activity,
* or just return the service itself within a binder.
*
*/
public class MyBinder extends Binder {
public void showBinderMessage() {
Toast.makeText(MyBoundService.this, "Service Binder Message", Toast.LENGTH_SHORT).show();
}
public MyBoundService getService() {
return MyBoundService.this;
}
}
public void showServiceMessage() {
Toast.makeText(MyBoundService.this, "Service Message", Toast.LENGTH_SHORT).show();
}
@Override
public void onCreate() {
mAllowRebind = true;
mBinder = new MyBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
throw new RuntimeException("You should not start this service");
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
Toast.makeText(this, "Service Binding", Toast.LENGTH_SHORT).show();
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
Toast.makeText(this, "Service Unbinding", Toast.LENGTH_SHORT).show();
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
Toast.makeText(this, "Service Done", Toast.LENGTH_SHORT).show();
}
}
MyBoundActivity.java
package com.gtware.android.blogs.services;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Toast;
import com.gtware.android.blogs.services.MyBoundService.MyBinder;
public class MyBoundActivity extends Activity {
private MyBoundService myBoundService;
@Override
protected void onStop() {
super.onStop();
if(myBoundService != null) {
unbindMyBoundService(null);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bound);
}
public void bindMyBoundService(View view) {
Intent i = new Intent(this, MyBoundService.class);
bindService(i, mMyBoundServiceConnection, Context.BIND_AUTO_CREATE);
}
public void unbindMyBoundService(View view) {
myBoundService = null;
unbindService(mMyBoundServiceConnection);
}
public ServiceConnection mMyBoundServiceConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName name) {
Toast.makeText(MyBoundActivity.this, "Service disconnected", Toast.LENGTH_LONG).show();
}
public void onServiceConnected(ComponentName name, IBinder binder) {
MyBinder myBinder = (MyBinder)binder;
myBinder.showBinderMessage();
myBoundService = myBinder.getService();
myBoundService.showServiceMessage();
Toast.makeText(MyBoundActivity.this, "Service connected", Toast.LENGTH_LONG).show();
}
};
}
bound.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical">
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="bindMyBoundService"
android:text="Bind Service"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="unbindMyBoundService"
android:text="Unbind Service"/>
</LinearLayout>
</RelativeLayout>
We cast the IBinder returned from the ServiceConnection.onServiceConnected() method to our Binder implementation MyBinder defined in the service. Then we use it to call a method to show a Toast, get the associated service using getService() which is an instance of our service just created, and this time call a method directly defined in the service to show another Toast. Do not forget, this implementation only works if your service and calling component are in the same application and process.
Binding components should unbind from a service when they're done interacting with it or when the application is stopped. Notice that we're also calling unbindService() from the onStop() method in the activity just in case you forget to click the Unbind Service button before you navigate away from the application
2. Using a Messenger
Using a Messenger allows you to make inter-process calls without using AIDL, so you can communicate with your services from different applications and their processes than your service is defined in. When using a Messenger, you communicate with your service (and the service may communicate back to you as well) using messages instead of invoking methods on the IBinder or the hosting service as you do when using a Binder implementation.A Messenger class is a wrapper around a Handler implementation. Both the service and the client defines a Messenger instance variable. The service constructs the Messenger using a Handler implementation, whereas the service clients construct it using the IBinder instance returned by the service. The service provides the IBinder object from the Messenger instance, so the IBinder instance is created automatically without the need to generate it yourself using AIDL. This IBinder is then returned by the service with the onBind() method.
Same services logic and lifecycle rules apply to a service using a Messenger - destroyed when all clients unbind, can also be started, and a ServiceConnection is used again to monitor the connection between the client and the service. Below is a service example which uses this technique, and an activity which binds and communicates with the service using a Messenger:
MyMessengerService.java
/** @author S. Gokhan TOPCU
*/
package com.gtware.android.blogs.services;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.widget.Toast;
/*
* If you need your service to communicate with remote processes, then you can use a
* Messenger to provide the interface for your service. This technique allows you to
* perform interprocess communication (IPC) without the need to use AIDL.
*
* In this way, there are no "methods" for the client to call on the service. Instead,
* the client delivers "messages" (Message objects) that the service receives in its Handler.
*
*/
public class MyMessengerService extends Service {
private boolean mAllowRebind; // indicates whether onRebind should be used
private Messenger mMessenger;
public static final int SHOW_TOAST = 0;
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_TOAST:
Toast.makeText(MyMessengerService.this, "Service Handler Message", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
@Override
public void onCreate() {
mAllowRebind = true;
mMessenger = new Messenger(new MyHandler());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
throw new RuntimeException("You should not start this service");
}
/**
* Return the IBinder generated by the Messenger which clients will send to use messages to this service
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "Service Binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
Toast.makeText(this, "Service Unbinding", Toast.LENGTH_SHORT).show();
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
Toast.makeText(this, "Service Done", Toast.LENGTH_SHORT).show();
}
}
MyMessengerActivity.java
package com.gtware.android.blogs.services;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.widget.Toast;
public class MyMessengerActivity extends Activity {
private Messenger myMessenger;
@Override
protected void onStop() {
super.onStop();
if(myMessenger != null) {
unbindMyMessengerService(null);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messenger);
}
public void bindMyMessengerService(View view) {
Intent i = new Intent(this, MyMessengerService.class);
bindService(i, mMyMessengerServiceConnection, Context.BIND_AUTO_CREATE);
}
public void unbindMyMessengerService(View view) {
myMessenger = null;
unbindService(mMyMessengerServiceConnection);
}
public ServiceConnection mMyMessengerServiceConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName name) {
Toast.makeText(MyMessengerActivity.this, "Service disconnected", Toast.LENGTH_LONG).show();
}
public void onServiceConnected(ComponentName name, IBinder binder) {
myMessenger = new Messenger(binder);
Toast.makeText(MyMessengerActivity.this, "Service connected", Toast.LENGTH_LONG).show();
Message message = Message.obtain(null, MyMessengerService.SHOW_TOAST, 0, 0);
try {
myMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
}
Just as with all services, do not forget to declare the service in your manifest file. This example uses the messenger.xml layout which is the same as the previous bound.xml file except for the method names specified in onClick attributes, matching the ones declared in the activity. When you run this app and click on the Bind Service button, service gets created, activity binds to it, activity sends a message to it and a Toast is displayed. When the activity unbinds, the service's unBind() method gets called and the activity unbinds, and since there're no other clients, the service gets destroyed. Also try clicking on the bind button twice - since the activity is already bound to the service after the first click, nothing happens and the connection is maintained. You can bind to any service using the Messenger pattern and send/receive messages even it's defined outside your application and does not run in the same application process. Android handles IPC automatically and does all the marshalling behind the scenes to make the connection.
Also notice that we're catching the RemoteException when sending a message to the Messenger. This is to handle a DeadObjectException, which is a subclass of RemoteException, which is thrown when the connection is broken when you're trying to send a message over this broken connection.
One hiccup for using the Messenger patterns is that you cannot use multi-threading - the same Handler will be executed to process all the incoming messages. So this pattern works in a queued fashion just like the MyQueuedService as shown in Part 1 of this blog.
Using AIDL (Android Interface Definition Language)
AIDL is the interface which two components agree to communicate with each other with. This involves sharing objects in memory, for which class primitives/objects will need to be marshalled in a binary format to be stored. AIDL takes care of marshalling/unmarshalling these and invoking the remote method for you, so that you don't have to deal with binary processing yourself. You only need to use AIDL if you need multi-threading and your service and your client run on different processes. If they are in the same process, just use the Binder pattern and call its methods from separate threads. An .aidl file contains the signature of the methods you want to expose to the service's clients and is coded using the regular Java syntax, and you need to implement a generated interface in your service and provide all the methods you defined in the .aidl file. Have a look at the official documentation if you're sure that you have to use this pattern.When to Bind/Unbind to a Service
If your activity needs to interact with the service even when it's running in the background, you should bind to it at onCreate() and onbind at onDestroy(). But this will increase your apps processing costs and it will be more susceptible to being killed by the OS when more resources are required, since the service will also keep running in the background. Background activities are chosen to be killed before any foreground ones by the OS, and consuming more resources in a background app means more priority for getting killed among other background apps.If you only need to use the service when the user is interacting with your activity, then use the onStart() and onStop() methods to bind/onbind. This is the general best practice.
Do not bind/onbind to a service at onPause()/onResume() methods unless you really have to, because there will be many unnecessary bind/unbind operations since these methods gets called frequently and you usually will want to keep your service at hand until you're sure the user is leaving the app. Also, these methods need to execute fast for swift transitions, and handling resource connections in these methods is not a right practice for any resource anyway (such as closing DBs, writing preferences/files, destroying network connections etc). Also, if your app has two different activities which binds to the same service, you double the service creation/binding/unbinding/destruction process since first app will be unbound and the service destroyed before the second can bind to it.
When to use Services, and which one to use
If you have a single client that needs to do some background work, just create a simple thread, or use an AsyncTask/Handler to do the job if you need to update the UI while doing it. If multiple clients need to use this processing logic, provide a static method in a utility class or define the AsyncTask/Handler in their own class files so all clients can access and utilize them.If you need to execute a simple task in the background within your application from a single client or multiple clients even when the user isn't interacting with your app, and you do not need multi-threading, use an IntentService.
If you also need multi-threading for the preceding scenario, extend the Service class directly and create new threads in the onStartCommand() button to execute each request that's received by the service every time a startService() call is made.
If you have multiple clients in the same application to do some background work which maintains resources such as network connections that can be shared to improve the process, then use a Bound Service and bind to the same service from all clients. This way, the service will be alive as long as at least one client is alive and maintain its resources. For network communications, this will also decrease your IO rate along with battery and resources consumption.
If you have client/clients that reside in different application processes than your service, use the Messenger pattern. Keep in mind that Messenger always executes its logic in the Handler's process. If you need the service to communicate back to the client, then create another Messenger in your client and pass it to the service so that it can use it to send messages back.
If you also need multi-threading for the preceding scenario, then using AIDL is your last resort. This pattern is usually required for providing OS-wide services to all applications, such as for providing download management.
In Part 3, you can find an example of how you can communicate back to the service client (i.e. activity) from the service and how you can keep a bound service alive across activities (i.e. when the screen is rotated).
Further Reading
Using Services in Android Part 1 - Started ServicesUsing Services in Android Part 3 - Two way communication with Rotation Support
Services
Bound Services
Service
IBinder
ServiceConnection
Binder
Messenger
AIDL
IntentService
AsyncTask
Handler
good one. Thank you man...........
ReplyDelete