I was not blogging very actively in the past few days and the reason for that was ... erm ... partying. The parties seem to be over for some days so I can return to my favourite pastime of finding service-oriented architecture (SOA) traits in Android.
Activity invocation is just one way of Android's service-oriented design. Android has a complete IDL-based invocation framework built into it. I was particularly curious about it because this sort of service invocation does not have to go through the service lifecycle management that slows down activity invocation considerably.
First, about services. Similarly to intents, I was a bit surprised to find out that the same Service class is used to cover two different service abstractions.
- If the service is started with Context.startService then it is a long-running background task whose lifecycle is not related to the lifecycle of the application that started it. Beside passing a start-up parameter bundle to this kind of service, Android platform does not provide any means of communicating with the service.
- If the service is started with Context.bindService then the lifecycles of the service and that of the application using it are tied. The application using the service is notified about the state of the service by means of service lifecycle events. It is also possible to communicate with this type of service using the Android IDL (AIDL) framework.
- The client requests connection to the service (by specifying an intent, as with activities). This launches the service (in separate address space)
- Once the service is ready to accept requests, the client is notified by means of a registered listener object (conforming to the ServiceConnection interface).
- The notification also carries the address of a stub object that can be used to invoke the service. If the service crashes or closes unexpectedly, the client is notified by the same service connection object.
- Meanwhile, the service can be invoked through the stub objects that the aidl tool generates from the service description.
The service description (with the extension of .aidl) is actually quite similar to a Java file. This is the AIDL of the example program.
package aexp.aidl;
// Adder service interface.
interface IAdderService {
int add( in int i1, in int i2 );
}
Currently one service can have only one interface although Dianne Hackborn from Google hopes for improvement here.
The convention of the aidl tool is that AIDL files are mixed with the Java files. This is how the tool finds them and converts them to Java stub files. In spite of the documentation's statement that only the Eclipse plugin converts AIDL to Java stub file, the Ant script generated by activityCreator invokes the aidl tool before compiling the Java file so if the AIDL is located correctly (in the Java source directory corresponding to the package declaration). In the picture below you can see IAdderService.aidl and the IAdderService.java Java stub file generated by the aidl tool.
Although AdderService is located in the same package as the activity, it has a life (and lifecycle) of its own. The service is declared in the AndroidManifest.xml file (again, XML fragment is garbled so that the blog engine does not corrupt it).
[service class=".AdderServiceImpl"/]
The service declaration could have intent filters as activities may have. In our example we target it directly, using its class name. In order to demonstrate the service lifecycle, the lifecycle events are logged.
The client is located in a separate application. The client also depends on the Java stub file generated by the service project so this file has to be copied by hand into the client project.
The interesting bit here is the AdderServiceConnection embedded class that acts as the service state listener. Just because the bindService call was issued, the service is not available! (actually, we can't obtain the stub object until the ServiceConnection object is notified by its onServiceConnected method). Note that the interfaceName argument of bindService is always null and this argument is supposed to disappear altogether.
Here is the snippet invoking the service.
private void invokeService( int i1, int i2 ) {
TextView t = (TextView)findViewById(R.id.invokeserv_result);
if( service == null )
t.setText( "Service not available" );
else {
try {
int result = service.add( i1,i2 );
t.setText( Integer.toString( result ) );
} catch( DeadObjectException ex ) {
t.setText( "Service invocation error" );
}
}
}
The code invoking the service needs to be prepared for the following exceptional cases:
- Service is not yet available (onServiceConnected was not yet called) or has gone (onServiceDisconnected was called).
- For some reason, the invocation was not succesful (DeadObjectException).
0 comments:
Post a Comment