binding multiple services for the same interface

Basic Tutorials concerning: GUI, Views, Activites, XML, Layouts, Intents, ...

binding multiple services for the same interface

Postby eppinator » Thu Mar 18, 2010 2:32 pm

Binding multiple / several services all implementing the same AIDL interface

Hello.
Right now I'm working on my diploma thesis and have to play around with android services.
Part of my application needs the ability to bind multiple services that all implement the same interface.
Why? Well it's some kind of the Strategy Pattern.
Think of one problem, and multiple solutions, each with different characteristics. For example a complex computation. For one request there could be different algorithms with different properties like computationtime or precision.
It's also some kind of implementing the SOA Paradigma (service Oriented Architecture) like with Soap WebServices.

Extensibility is very important. So what I want is that my main-application (the serviceclient) provides an interface (via AIDL) And based on that, different applications (especially services) could be created (from and published by other people like you for example) that implement this interface and do the work.
Now when the main application is running, it in some way detects which services are available for the interface and selects -> binds one or more of them and uses it's functions.

So summarized the problem is: detecting and binding multiple services for the same interface.
ToDo:
  • create the interface
  • implement the interface in different services
  • make the services detectable
  • find a way to dynamically detect which such services are available on the device.
  • retrieve some kind of service description for each service (to be able to decide which service we actually want to use)
  • select one ore more services and bind them for an activity/service

What I've discovered so far is more kind of the contrary thing: multiple activities binding to the same service. But no approach like this.
So I decided to post here my experience and results while trying to solve the problem.

Maybe you like it and find it useful or have other ideas how certain steps could be realized or improved.
For the tests we have 3 single applications
- the main app "ipctest serviceclient" that wants to bind to the other services and provides the interface
- several service-apps ("ipctest service one", "ipctest service two") that contain a service class, implementing the interface

1. Provide the interface.
This is straightforward. Just create a new AIDL-File
For the example it only has one method, that is supposed to return a string containing the identity of the implementing service

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. package ipctest.serviceclient;
  2. interface IIpcTestService{
  3.         String getIdentity();
  4. }
Parsed in 0.031 seconds, using GeSHi 1.0.8.4


2. Implement the interface
2a)
To make implementation easier I decided to write an abstract class from which service implementations should extend

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. package ipctest.serviceclient;
  2. public abstract class AIpcTestService extends Service {
  3.         private static final String TAG = "AIpcTestService";
  4.  
  5.         @Override
  6.         public IBinder onBind(Intent arg0) {
  7.                 return mBinder;
  8.         }
  9.  
  10.         // must be overriden in serviceimplementations
  11.         protected abstract String mGetIdentity();
  12.        
  13.         private final IIpcTestService.Stub mBinder = new IIpcTestService.Stub(){
  14.  
  15.                 @Override
  16.                 public String getIdentity() throws RemoteException {
  17.                         Log.d(TAG, "getIdentity() called");
  18.                         return mGetIdentity();
  19.                 }
  20.         };
  21. }
Parsed in 0.033 seconds, using GeSHi 1.0.8.4


2b)
Now we create new services, extending the abstract implementation for the interface.

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. package ipctest.serviceone;
  2.  
  3. public class IpcTestServiceOne extends AIpcTestService {
  4.         private static final String TAG = "IpcTestServiceOne";
  5.         private static final String mIdentity = "I am IpcTestService ONE";
  6.  
  7.         @Override
  8.         protected String mGetIdentity() {
  9.                 Log.d(TAG, "mGetIdentity() called");
  10.                 return mIdentity;
  11.         }
  12.  
  13.  
  14.         // don't forget to implement the methods for the service lifecycle
  15.         // of this service like onCreate(), onStart(), onDestroy() and so on...
  16.         // ...
  17.         // ...
  18. }
Parsed in 0.036 seconds, using GeSHi 1.0.8.4



Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1. package ipctest.servicetwo;
  2.  
  3. public class IpcTestServiceTwo extends AIpcTestService {
  4.         private static final String TAG = "IpcTestServiceTwo";
  5.         private static final String mIdentity = "I am IpcTestService TWO";
  6.  
  7.         @Override
  8.         protected String mGetIdentity() {
  9.                 Log.d(TAG, "mGetIdentity() called");
  10.                 return mIdentity;
  11.         }
  12.  
  13.  
  14.         // don't forget to implement the methods for the service lifecycle
  15.         // of this service like onCreate(), onStart(), onDestroy() and so on...
  16.         // ...
  17.         // ...
  18. }
Parsed in 0.038 seconds, using GeSHi 1.0.8.4


3. make the services detectable
To make the serviceimplementations runnable and detectable we have to declare them in the android manifest.

Inside the [font=Courier New]<application>[/font] we declare the service:

Syntax: [ Download ] [ Hide ]
Using xml Syntax Highlighting
  1.         <service android:name=".IpcTestServiceOne"
  2.                 android:process=":remote"
  3.                 android:label="IPC Test Service One">
  4.                 <intent-filter>
  5.                         <action android:name="ipctest.serviceclient.IIpcTestService" />
  6.                         <category android:name="android.intent.category.DEFAULT" />
  7.                 </intent-filter>
  8.         </service>
  9.  
Parsed in 0.001 seconds, using GeSHi 1.0.8.4

Syntax: [ Download ] [ Hide ]
Using xml Syntax Highlighting
  1.         <service android:name=".IpcTestServiceTwo"
  2.                 android:process=":remote"
  3.                 android:label="IPC Test Service Two">
  4.                 <intent-filter>
  5.                         <action android:name="ipctest.serviceclient.IIpcTestService" />
  6.                         <category android:name="android.intent.category.DEFAULT" />
  7.                 </intent-filter>
  8.         </service>
  9.  
Parsed in 0.002 seconds, using GeSHi 1.0.8.4

Note the intent-filter that specifies the name of the interface we implement as action. In this way every service that implements this interface reacts to the same intent - cool!

4. detect available services
So far so good.
Now we want dynamically detect all services that implement our interface, in fact before actually binding to them.
How we gonna do it:
First step we define an intent with the name of the interface as action like we declared in the services manifest. Then we a query the packagemanager to get a ResolveList, containing infos about our services.
(Next code-snippets are placed in the onCreate() in the main activity of my testapplication (ipctest serviceclient)

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.         final PackageManager pm = this.getPackageManager();
  2.         Intent interfaceIntent = new Intent(IIpcTestService.class.getName());
  3.         List<ResolveInfo> list = pm.queryIntentServices(interfaceIntent,0);
  4.  
Parsed in 0.037 seconds, using GeSHi 1.0.8.4

Now we could for example iterate through the [font=Courier New]ResolveList[/font] and print some infos about the found services into a textview:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.         Log.d( TAG, "Responded services: " + list.size() );
  2.         tv = (TextView) findViewById(R.id.textView);
  3.         for(ResolveInfo ri:list){
  4.                 tv.append(ri.loadLabel(pm) + " " + ri.serviceInfo.name+"n");
  5.         }
  6.  
Parsed in 0.037 seconds, using GeSHi 1.0.8.4

5. Binding to multiple services
Now comes the tricky part. We do not only want to bind to ONE of the just discovered services, we want to bind to ALL of them.
How we gonna do it?
For one service we need an instance of type [font=Courier New]IIpcTestService[/font] which we retrieve through a ServiceConnection.
For multiple services we need a whole collection of service instances.

In our main-activty we need a map for the serviceinstances and a map for the ServiceConnections, the key always refers to the full qualified name of the service-class:
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.         // Map containing the bound RemoteServices
  2.         private HashMap<String,IIpcTestService> remoteServices = new HashMap<String,IIpcTestService>();
  3.        
  4.         // ServiceConnection for bindings to the services (see lateron)
  5.         private HashMap<String,RemoteServiceConnection> connections = new HashMap<String,RemoteServiceConnection>();
  6.  
Parsed in 0.037 seconds, using GeSHi 1.0.8.4

In the onCreate, after getting the ResolveList (see 5.) wie iterate through the list and call bindService
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.         for(ResolveInfo ri:list){
  2.                 Log.d( TAG, "Try binding service: " + ri.serviceInfo.name );
  3.                 Intent intent = new Intent();
  4.                 intent.setClassName(ri.serviceInfo.packageName, ri.serviceInfo.name);
  5.                 RemoteServiceConnection con = new RemoteServiceConnection();
  6.                 connections.put(ri.serviceInfo.name, con);
  7.                 bindService(intent,con,Context.BIND_AUTO_CREATE);
  8.         }
  9.  
Parsed in 0.038 seconds, using GeSHi 1.0.8.4

You may ask why we need more than one ServiceConnection instance. In the beginning I used only one and it worked. I could bind both services with one single instance. But when it comes to UNbind them it becomes complicated. To unbind a service you call [font=Courier New]unbindService(ServiceConnection);[/font] You can't specify WHICH service you want to unbind, only the ServiceConnection-Object is passed. But having all services bound with ONE ServiceConnection, all services are unbound at once. So I guess we need different ServiceConnections - one for each Service.

Here is what the RemoteServiceConnection class looks like:
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.     class RemoteServiceConnection implements ServiceConnection {
  2.         public void onServiceConnected(ComponentName className, IBinder boundService ) {
  3.           IIpcTestService remoteService = IIpcTestService.Stub.asInterface((IBinder)boundService);
  4.           remoteServices.put(className.getClassName(), remoteService);
  5.           Log.d(TAG, "onServiceConnected() " + className.getClassName() );
  6.         }
  7.  
  8.         public void onServiceDisconnected(ComponentName className) {
  9.           remoteServices.remove(className.getClassName());
  10.         }
  11.     };
  12.  
Parsed in 0.038 seconds, using GeSHi 1.0.8.4


To avoid leaking serviceconnection don't forget to unbind them before your activity gets killed:
Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
  1.     public void onDestroy(){
  2.         super.onDestroy();
  3.         for(String s:remoteServices.keySet()){
  4.                 unbindService(connections.get(s));
  5.         }
  6.     }
  7.  
Parsed in 0.037 seconds, using GeSHi 1.0.8.4


And that's it.
It's not very convenient at this early stage, but it works.
I hope the codefragments gave you the idea of what I intended to do.
If necessary I could upload the sources of the testapplications

Another interesting thing I noticed: when you try to bind a service using an implizit intent and there are more than one services that match it, android doesnt ask you which to bind, it just binds the first one. Unlike with activities, where the user is asked for decision.

Next steps to do:
  • create some kind of service-description and evaluate it after getting the ResolveList. I think best way to do this is to add some meta-data in the manifest for each service implementation. To get meta-data we need to set the flag GET_META_DATA when querying the PackageManager:
    [font=Courier New]List<ResolveInfo> list = pm.queryIntentServices(interfaceIntent,pm.GET_META_DATA);[/font]
    I'm working and testing on it...
  • create a RemoteServiceManager class that manages the discovery, selection (based on some servicedescription) and binding of the services. All what we have done in the example-activity should be done by a manager class for easier use.
  • maybe I create a new class that holds the stuff for a remoteservice: status (bound or unbound) servicestub, the connection, servicedescription...
eppinator
Junior Developer
Junior Developer
 
Posts: 10
Joined: Thu Feb 11, 2010 1:00 pm

Top

Return to Novice Tutorials

Who is online

Users browsing this forum: No registered users and 10 guests