Performing Inter Process Communication

Communication between application components in different processes is made possible in Android by a specific IPC approach. This, again, is necessary because each application on the platform runs in its own process, and processes are intentionally separated from one another. In order to pass messages and objects between processes, you have to use the Android IPC path.

To begin exploring this path we are first going to build a small, focused sample application to examine the means to generate a remote interface using AIDL, and then we will connect to that interface through a proxy that we will expose using a Service (the other Service purpose). Along the way we will expand on the IBinder and Binder concepts Android uses to pass messages and types during IPC.

4.4.1 Android Interface Definition Language

Android provides its own Interface Definition Language that you can use to create IDL files. These files then become the input to the aidl tool, which Android also includes. This tool is used to generate a Java interface and inner Stub class that you can, in turn, use to create a remotely accessible object.

AIDL files have a specific syntax that allows you to define methods, with return types and parameters (you cannot define static fields, unlike with a typical Java interface). In the basic AIDL syntax you define your package, imports, and interfacejust like you would in Java, as shown in listing 4.7.

Listing 4.7 An example .aidl remote interface definition language file package com.msi .manning .binder; <—O Define the package interface ISimpleMathService { <—Q Declare the interface name int add(int a, int b);

int subtract (int a, int b) ; Q Describe a method

String echo (in String input); \

The package O, import statements (of which we have none here), and interface Q constructs in AIDL are straightforward—they are analogous to regular Java. When you define methods, you must specify a directional tag for all nonprimitive types with each parameter (in, out, or inout). Primitives are allowed only as in and are therefore treated as in by default (and thus don't need the tag). This directional tag is used by the platform to generate the necessary code for marshaling and unmarshaling instances of your interface across IPC boundaries. It's better to go in only one direction where you can, for performance reasons, so try to use only what you really need.

In this case we have declared an interface named ISimpleMathService that includes methods Q that perform addition, subtraction, and echoing a String. This is an oversimplified example, of course, but it does demonstrate the approach.

When using AIDL you also have to be aware that only certain types are allowed; these types are shown in table 4.5.

Once you have defined your interface methods with return types and parameters with directional tags in the AIDL format, you then invoke the aidl tool to generate a

Table 4.5 Android IDL allowed types

Type

Description

Import Required

Java primitives

boolean, byte, short, int, float, double, long, char.

No

String

java.lang.String.

No

CharSequence

java.lang.CharSequence.

No

List

Can be generic; all types used in collection must be one of IDL allowed. Ultimately implemented as an ArrayList.

No

Map

Can be generic, all types used in collection must be one of IDL allowed. Ultimately implemented as a HashMap.

No

Other AIDL interfaces

Any other AIDL-generated interface type.

Yes

Parcelable objects

Objects that implement the Android Parcelable interface (more about this in section 4.4.3).

Yes

Java interface that represents your AIDL specification. From the command line you can invoke [ANDROID_HOME]/tools/aidl to see the options and syntax for this tool. Generally you just need to point it at your .aidl file, and it will emit a Java interface of the same name. If you use the Eclipse plug-in, it will automatically invoke the aidl tool for you (it recognizes .aidl files and invokes the tool).

The interface that gets generated through AIDL includes an inner static abstract class named Stub that extends Binder and implements the outer class interface. This Stub class represents the local side of your remotable interface. Stub also includes an asInterface(IBinder binder) method that returns a remote version of your interface type. Callers can use this method to get a handle on the remote object and from there invoke remote methods. The AIDL process generates a Proxy class (another inner class, this time inside Stub) that is used to wire up the plumbing and return to callers from the asInterface method. The diagram in figure 4.6 depicts this IPC local/ remote relationship.

Once you have all of the generated parts involved, create a concrete class that extends from Stub and implements your interface. You then expose this interface to callers through a Service.

Android Aidl Diagram
Figure 4.6 Diagram of the Android AIDL process

4.4.2 Exposing a remote interface

The glue in all of the moving parts of AIDL that we have discussed up to now is the point where a remote interface is exposed—via a Service. In Android parlance, exposing a remote interface through a Service is known as publishing.

To publish a remote interface you create a class that extends Service and returns an IBinder through the onBind(Intent intent) method within. The IBinder that you return here is what clients will use to access a particular remote object. As we discussed in the previous section, the AIDL-generated Stub class (which itself extends Binder) is usually used to extend from and return an implementation of a remotable interface. This is usually what is returned from a Service class's onBind method—and hence this is how a remote interface is exposed to any other process that can bind to a Service. All of this is shown in listing 4.8, where we implement and publish the ISimpleMathService we created in the previous section.

Listing 4.8 A Service implementation that exposes an IBinder remotable object public class SimpleMathService extends Service {

private final ISimpleMathService.Stub binder = new ISimpleMathService.Stub() {

public String echo (String input) { return "echo " + input;

Implement the remote interface

@Override public IBinder onBind(Intent intent) { return this.binder;

Return an IBinder representing the remotable object

A concrete instance of the generated AIDL Java interface is required to return an IBinder to any caller than binds to a Service. The way to create an implementation is to implement the Stub class that the aidl tool generates O. This class, again, implements the AIDL interface and extends Binder. Once the IBinder is established, it is then simply returned from the onBind method ©.

Now that we have seen where a caller can hook into a Service and get a reference to a remotable object, we need to walk through finishing that connection by binding to a Service from an Activity.

Binding to a Service

When an Activity class binds to a Service, which is done using the Context. bindService(Intent i, ServiceConnection connection, int flags) method, the

ServiceConnection object that is passed in is used to send several callbacks, from the Service back to the Activity. One significant callback happens when the binding process completes. This callback comes in the form of the onServiceConnected (Compo-nentName className, IBinder binder) method. The platform automatically injects the IBinder onBind result (from the Service being bound to) into this method, making this object available to the caller. We show how this works in code in listing 4.9.

Listing 4.9 Binding to a Service within an Activity public class ActivityExample extends Activity { B Define remote interface type variable private ISimpleMathService service; <-' Include A

private boolean bound; <—C Define bound state boolean ServiceConnection

. . . View element declarations omitted for brevity implementation private ServiceConnection connection = new ServiceConnection () { <j_

public void onServiceConnected(ComponentName className, iBinder iservice) { E React to onServiceConnected callback service = ISimpleMathService.Stub.asInterface(iservice) ; <-

Toast.makeText(ActivityExample.this,

"connected to Service", Toast. LENGTH_SHORT).show();

} bound = true; Establish remote interface type Q

public void onServiceDisconnected(ComponentName className) { <1-

service = null;

Toast.makeText(ActivityExample.this,

"disconnected from Service", Toast.LENGTH_SHORT).show(); bound = false;

} React to onServiceDisconnected callback Q

@Override public void onCreate(Bundle icicle) {

. . . View element inflation omitted for brevity this.addButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { try {

int result = service.add(

Integer.parseInt(inputa.getText().toString()), <-

Integer.parseInt(inputb.getText().toString())); output.setText(String.valueOf(result)); } catch (DeadObjectException e) {

Log. e ("ActivityExample", "error", e) ; Use remote object

} catch (RemoteException e) { for operations H

Log.e("ActivityExample", "error", e) ;

. . . subtractButton, similar to addButton, omitted for brevity

@Override public void onStart() { super.onStart();

this.bindService(

new Intent(ActivityExample.this,

SimpleMathService.class), connection,

Context. bind_auto_create) ; <—I Perform binding

@Override public void onPause() { super.onPause() ; if (bound) {

bound = false;

this.unbindService(connection); <- Perform unbinding

In order to use the remotable ISimpleMathService we defined in AIDL, we declare a variable of the generated Java interface type O. Along with this service variable, we include a boolean to keep track of the current state of the binding Q.

We next see the ServiceConnection object ©, which is essential to the binding process. This object is used with Context methods to bind and unbind. When a Service is bound, the onServiceConnected callback is fired Q. Within this callback the remote IBinder reference is returned and can be assigned to the remotable type ©. After the connection-related callback there is a similar onServiceDisconnected callback that is fired when a Service is unbound ©.

Once the connection is established and the remote IBinder is in place, it can be used to perform the operations it defines ©. Here we are using the add, subtract, and echo methods we created in AIDL in listing 4.7.

With this class we see the Activity lifecycle methods that are now familiar. In onStart we establish the binding using bindService ©, and in onPause we use unbindService ©. A Service that is bound but not started can itself be cleaned up by the system to free up resources. If we don't unbind these, resources might unnecessarily hang around.

A Service, as you have seen and will learn more about next, is invoked using an Intent. Here again, explicit or implicit Intent invocation can be used. Significantly, any application (with the correct permissions) can call into a Service and bind to it, returning the IBinder to perform operations—it need not be an Activity in the same application as the Service (this is how applications in different processes communicate).

That brings us to the difference between starting a Service and binding to one and what the implications are for each usage.

4.4.4 Starting versus binding

Again, Services serve two purposes in Android, and you can use them as you have now seen in two corresponding ways:

■ Starting—Context.startService(Intent service, Bundle b)

■ Binding—Context.bindService(Intent service, ServiceConnection c, int flag)

Starting a Service tells the platform to launch it in the background and keep it running, without any particular connection to any other Activity or application. We used the WeatherReportService in this manner to run in the background and issue severe weather alerts.

Binding to a Service, as we did with our sample SimpleMathService, is how you get a handle to a remote object and call methods defined there from an Activity. As we have discussed, because every Android application is running in its own process, using a bound Service (which returns an IBinder through ServiceConnection) is how you pass data between processes.

Marshaling and unmarshaling remotable objects across process boundaries is fairly complicated. This is the reason the AIDL process has so many moving parts. Fortunately you don't generally have to deal with all of the internals; you can instead stick to a simple recipe that will enable you to create and use remotable objects:

1 Define your interface using AIDL, in the form of an [INTERFACE_NAME].aidl file; see listing 4.7.

2 Generate a Java interface for your .aidl file (automatic in Eclipse).

3 Extend from the generated [INTERFACE_NAME].Stub class and implement your interface methods; see listing 4.8.

4 Expose your interface to clients through a Service and the Service onBind(Intent i) method; see listing 4.8.

5 Bind to your Service with a ServiceConnection to get a handle to the remotable object, and use it; see listing 4.9.

Another important aspect of the Service concept to be aware of, and one that is affected by whether or not a Service is bound or started or both, is the lifecycle.

4.4.5 Service lifecycle

Along with overall application lifecycle that we introduced in chapter 2 and the Activity lifecycle that we discussed in detail in chapter 3, services also have their own well-defined process phases. Which parts of the Service lifecycle are invoked is affected by how the Service is being used: started, bound, or both. SERVICE-STARTED LIFECYCLE

If a Service is started by Context.startService(Intent service, Bundle b), as shown in listing 4.5, it runs in the background whether or not anything is bound to it. In this case, if it is needed, the Service onCreate() method will be called, and then the onStart (int id, Bundle args) method will be called. If a Service is started more than once, the onStart(int id, Bundle args) method will be called multiple times, but additional instances of the Service will not be created (still needs only one stop call).

The Service will continue to run in the background until it is explicitly stopped by the Context.stopService() method or its own stopSelf() method. You should also keep in mind that the platform may kill services if resources are running low, so your application needs to be able to react accordingly (restart a service automatically, function without it, and the like). SERVICE-BOUND LIFECYCLE

If a Service is bound by an Activity calling Context.bindService(Intent service, ServiceConnection connection, int flags), as shown in listing 4.9, it will run as long as the connection is established. An Activity establishes the connection using the Context and is responsible for closing it as well.

When a Service is only bound in this manner and not also started, its onCreate() method is invoked, but onStart(int id, Bundle args) is not used. In these cases the Service is eligible to be stopped and cleaned up by the platform when no longer bound. SERVICE-STARTED AND -BOUND LIFECYCLE

If a Service is both started and bound, which is allowable, it will basically keep running in the background, similarly to the started lifecycle. The only real difference is the lifecycle itself. Because of the starting and binding, both onStart(int id, Bundle args) and onCreate() will be called. CLEANING UP WHEN A SERVICE STOPS

When a Service is stopped, either explicitly after having been started or implicitly when there are no more bound connections (and it was not started), the onDestroy() method is invoked. Inside onDestroy() every Service should perform final cleanup, stopping any spawned threads and the like.

Now that we have shown how a Service is implemented, how one can be used both in terms of starting and binding, and what the lifecycle looks like, we need to take a closer look at details of remotable data types when using Android IPC and IDL.

4.4.6 Binder and Parcelable

The IBinder interface is the base of the remoting protocol in Android. As you have seen, you don't implement this interface directly; rather you typically use AIDL to generate an interface that contains a Stub Binder implementation.

The key to the IBinder and Binder-enabling IPC, once the interfaces are defined and implemented, is the IBinder.transact() method and corresponding Binder. onTransact() method. Though you don't typically work with these internal methods directly, they are the backbone of the remoting process. Each method you define using AIDL is handled synchronously through the transaction process (enabling the same semantics as if the method were local).

All of the objects you pass in and out, through the interface methods you define using AIDL, use the transact process. These objects must be Parcelable in order to be able to be placed inside a Parcel and moved across the local/remote process barrier in the Binder transaction methods.

The only time you need to worry about something being Parcelable is when you want to send a custom object through Android IPC. If you use the default allowable types in your interface definition files—primitives, String, CharSequence, List, and Map—everything is automatically handled. If you need to use something beyond those, only then do you need to implement Parcelable.

The Android documentation describes what methods you need to implement to create a Parcelable class. The only tricky part of doing this is remembering to create an .aidl file for each Parcelable interface. These .aidl files are different from those you use to define Binder classes themselves; for these you need to remember not to generate from the aidl tool. Trying to use the aidl tool won't work, and it isn't intended to work. The documentation states these files are used "like a header in C," and so they are not intended to be processed by the aidl tool.

Also, when considering creation of your own Parcelable types, make sure you really need them. Passing complex objects across the IPC boundary in an embedded environment is an expensive operation and should be avoided if possible (not to mention that manually creating these types is fairly tedious).

Rounding out our IPC discussion with a quick overview of Parcelable completes our tour of Android Intent and Service usage.

4.5 Summary

In this chapter we covered a broad swath of Android territory. We first focused on the Intent abstraction, defining what intents are, how they are resolved using Intent-Filter objects, and what some built-in platform-provided Intent handlers are. We also addressed explicit Intent invocation versus implicit Intent invocation and the reasons you might choose one type over another. In that discussion we completed the RestaurantFinder sample application.

After we covered the basics of Intent classes, we moved on to a new sample application, WeatherReporter. Within the scope of this application, we explored the concept of a BroadcastReceiver and an Android Service. We used the receiver to start the Service, and we designed the Service to send notification alerts for severe weather events. Along with Service implementation details we covered the difference between starting and binding services and the moving parts behind the Android IPC system, which uses the Android IDL process.

Through looking at all these components in several complete examples, you should now have a good idea of the basic foundation of these concepts. In the next chapter we will build on this foundation a bit further by looking at the various means Android provides to retrieve and store data, including using preferences, the file system, databases, and creating a ContentProvider.

+1 0

Responses

  • Zewdi
    How to add byte paramater in aidl method android?
    2 years ago
  • saara
    What is the use of android inter process communication?
    1 year ago

Post a comment