Mobile Phone Handheld Hardware Hardware Rick Rogers John Lombardo O'Reilly Media, Inc. O'Reilly Media Android Application Development, 1st Edition13.2. Remote Methods and AIDLThis section describes how one program can provide other
programs with access to its methods. A number of important Android APIs
use this feature. For instance, the TelephonyManager
introduced in Chapter 15 uses a
remote object interface in order to manage and share the phone hardware in
an Android device. There are three steps to creating and using remote methods in
Android: Define the interface in the AIDL. Implement the interface. That is, write methods that match the
signatures in the interface and that perform the operations you want
in the program that provides the desired services. Invoke the methods where you want to use them.
13.2.1. Android Interface Definition LanguageTo communicate from one process to another, data stored in memory
has to be moved across process boundaries. That means the data has to be
"marshalled"—packaged for transport—and "unmarshalled"—put into the right
member variables after the data has been moved across the process
boundary. (Some Android documentation uses the word "flattened," with
the connotation of taking a data stored in several objects and turning
it into a "flat" array of bytes that can be sent between
processes.) Java's basic types, such as String, are easy to marshall, but complex
types, such as multidimensional arrays, are much harder. Marshalling
data spread in an object that holds references to other objects requires
following every reference and marshalling all the data that it
references. Usually, marshalling and unmarshalling is performed on the
parameters in a remote method call, to let you pass data from one
application to another and return results. Marshalling and unmarshalling data is tedious, and you would find
it hard to understand code that had to carry out the task every place it
uses inter-process communication. Therefore, most implementations of
remote objects or components use an interface definition language that
generates calls to marshalling methods. The syntax of the interface
definition language resembles the main language in use (Java in this
case), so that a remote procedure call closely resembles a normal method
call. However, the interface definition language really is a separate
language. AIDL syntax is identical to Java interface definition syntax, except that in AIDL you can
label the parameters for remote method calls as in,
out, or inout. Any parameter
labeled in will be transferred to the
remote method, whereas any parameter labeled out will be returned to the caller from the
remote method. In the example, from the ApiDemos application we use
here, the keywords indicating in and out parameters are not used. The
defaults apply: all parameters are in, the return
value is used for returning data from the remote method, and any
parameter labeled inout will transfer data to the
remote method and refer to a value transferred from the remote method
when it returns. In the example, the AIDL code is therefore completely
compatible, in syntax, to Java code. When you save your AIDL file in Eclipse, the Android Eclipse
plug-in compiles it. Both the calling and implementing side of a remote
method interface share the information in the AIDL file. For the examples in this section, we're excerpting code from the
ISecondary.aidl file in the
ApiDemos application. This is how you specify an interface to a remote object: interface ISecondary {
/**
* Request the PID of this service, to do evil things with it.
*/
int getPid();
/**
* This demonstrates the basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}This looks like Java code, but it isn't. It looks like an
interface definition. There are two method signatures, and no
implementation of the methods. That is all AIDL needs to create code
that moves the parameters between applications. Next we will take a look
at the code generated by AIDL to see exactly how the parameters are
moved from one process to another, and to see how to implement the API
defined in this AIDL definition. The Android SDK plug-in for Eclipse automatically compiles this
code to Java, resulting in the following set of Java definitions.
Normally this code is not formatted for readability, so what you see
here looks different from the file you see in the ApiDemos project in
your Eclipse IDE. But it is the same Java code: package com.example.android.apis.app;
import java.lang.String;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Binder;
import android.os.Parcel;
/**
* Example of a secondary interface associated with a service. (Note that
* the interface itself doesn't impact, it is just a matter of how you
* retrieve it from the service.)
*/
public interface ISecondary extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder
implements com.example.android.apis.app.ISecondary {
private static final java.lang.String DESCRIPTOR =
"com.example.android.apis.app.ISecondary";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an ISecondary interface,
* generating a proxy if needed.
*/
public static
com.example.android.apis.app.ISecondary asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin =
(android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) &&
(iin instanceof com.example.android.apis.app.ISecondary))) {
return ((com.example.android.apis.app.ISecondary) iin);
}
return new com.example.android.apis.app.ISecondary.Stub.Proxy(obj);
}
public android.os.IBinder asBinder() {
return this;
}
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel
reply,
int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getPid: {
data.enforceInterface(DESCRIPTOR);
int _result = this.getPid();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_basicTypes: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0 != data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements
com.example.android.apis.app.ISecondary {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
/**
* Request the PID of this service, to do evil things with it.
*/
public int getPid() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* This demonstrates the basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat,
double aDouble, java.lang.String aString)
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean) ? (1) : (0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getPid = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_basicTypes = (IBinder.FIRST_CALL_TRANSACTION
+ 1);
}
/**
* Request the PID of this service, to do evil things with it.
*/
public int getPid() throws android.os.RemoteException;
/**
* This demonstrates the basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, java.lang.String aString) throws android.os.RemoteException;
}
That's a lot of code! Now you can appreciate the value of AIDL
instead of building a remote object interface by hand. After we see what
is going on inside the AIDL-generated code, we will take a look
at the other two steps to creating and using a remote object interface:
implementing the methods and invoking them. 13.2.2. Classes Underlying AIDL-Generated InterfacesNow let's take a look at the android.os.IInterface class. It's a base type on which all the interfaces created by AIDL
are built, so they can be referenced through references of the same
type. ISecondary extends IInterface. Most of the code in the ISecondary interface is part of the definition of an abstract class called
Stub. You implement remote methods by
extending the Stub class. Every
remote interface has this class, but because it is inside the interface
created by AIDL particular to your remote methods, there is no name
conflict. The word "stub" was chosen to refer to this class because remote
method systems work by creating a method on the client with the same
name as the method that runs on the server. The client method is
considered a "stub" because it doesn't actually carry out the operation
requested; it just marshalls the data, sends it to the server, and
unmarshalls the return value. We'll show some details later in this
chapter. 13.2.2.1. Implementing the Stub interfaceSo how do you write the code that actually implements these remote
method calls? In this case, the implementation is in the class
RemoteService of the ApiDemos
application, and the following excerpt shows the method definitions.
The first line extends the abstract class and makes a new instance of
it: private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {
public int getPid() {
return Process.myPid();
}
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
}
};This is all you need to do to turn a method in your application
into a remote method. The rest of the work of invoking the method in
the other application, passing the parameters, and responding with a
return value from the remote method is performed by code generated by
AIDL in the Stub abstract
class. So, for a remote interface generated by AIDL, the code takes the
abstract Stub class and implements the method code
that will actually be used. But how does data from another process get
to these methods? That is where the onTransact
method comes in. The onTransact method (see the AIDL-generated code shown earlier) is called
when data in a Parcel object is delivered to a
remote interface in an Android program. This method is generated by
AIDL for each remote interface. In this case, it reads each argument
to the method from a Parcel object, makes the
method call, and writes the result to another
Parcel object used for the return value of a remote
method. Parcel objects are what Java applications in
Android pass to the Android IPC mechanism for moving between
processes. In the simple IPC example earlier in this chapter,
underlying the Context method calls used to move
Intent objects between applications, the
Intent object and the "extras" data associated with
it are marshalled, or "flattened," into a Parcel
object to be moved from one process to another and reconstituted into
an Intent object with the same extras in the other
process. Basic types such as long and
int are marshalled and unmarshalled by methods in
the Parcel class. Other classes in the Android base
classes, such as Intent and
String, implement the Parcelable interface. As the
name suggests, this provides an interface for the
Parcel class to marshall those objects. And on top
of that, implementing the Parcelable interface in your classes
enables them to be marshalled, unmarshalled, and moved from one
application to another. 13.2.2.2. Getting an instance of the remote Proxy objectThere is one more part to this story: how does a different
application find out about the interface called ISecondary, and how
does the caller of the remote method actually call these methods? The
answer is in the asInterface method of the
Stub class, and the Proxy class
nested within Stub. And that means that any
application that wants to make a remote method call must share the
interface definition with the application that implements the
interface. In practical terms, that means that the calling application
and the application that implements the remote interface have to be
compiled with the same AIDL files. Now let's take a look at how the remote interface gets called.
In the ApiDemos code we are using as an example here, this happens in
the RemoteServiceBinding class, where the
asInterface method is called: mSecondaryService =
ISecondary.Stub.asInterface(service);The parameter named service
here is a reference to an IBinder interface. The
Binder abstract class implements IBinder, and the
Stub class (the guts of what AIDL has generated)
extends Binder. Let's see how this parameter is used in the
asInterface method: public static com.example.android.apis.app.ISecondary asInterface(android.os.IBinder
obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface)
obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.android.apis.app.ISecondary))) {
return ((com.example.android.apis.app.ISecondary) iin);
}
return new com.example.android.apis.app.ISecondary.Stub.Proxy(obj);
}Here the parameter is named obj, and first it
is tested to see whether it is null. Then, asInterface checks to see
whether there is an instance of ISecondary with the correct name. What
that means is that the "remote" interface we were looking for is
actually in the same application as the code calling it. And that
means no inter-process communication is necessary. Otherwise, if it
isn't a local interface, an instance of the Proxy
object is created. Remember that this code is executing in the context
of the application that wants to call the remote interface. The Proxy class is the counterpart of the
Stub abstract class. It may seem a little
mind-bending that the Proxy class, which implements
ISecondary, is defined inside the Stub class, which
is itself inside the ISecondary interface, but it turns out to be
convenient. Otherwise, more class files would have to be created by
AIDL, and somehow uses of those classes managed. Looking inside the Proxy class, we see that
it has methods that have the same signature as the remote methods
defined in the AIDL file. Here, unlike in the abstract class
Stub, the methods are implemented, and the
implementations create Parcel objects and fill them with the
"flattened" parameters in exactly the right order for the
onTransact method to "unflatten" them and call the
remote methods. That means an application calls a remote method by getting an
instance of the Proxy class and calling the remote
methods as if they were local. You can see this here, excerpted from
the RemoteServiceBinding class: int pid = mSecondaryService.getPid(); Recall that mSecondaryService is returned
from the ISecondary.Stub.asInterface method.
Because the caller gets a Proxy object and the
remote methods are implemented in a Stub object,
and because both Proxy and Stub
implement ISecondary, it all looks like a local method call, but the
implementations of the methods are completely different in the calling
application and the application that implements the remote methods. To review: You define remote interfaces in AIDL. They look like Java
interfaces, but are not. AIDL turns your remote interface definition into a Java
interface with Stub and
Proxy classes nested inside. Both the application that calls the remote method and the
application that implements it use the same AIDL file and the same
generated interface.
The application calling the remote interface gets an instance of
the Proxy class that implements the very same
interface it is defined inside of. The instance also implements
"proxy" methods with the same signature as the remote methods, but
they package up their parameters into a Parcel
object and send them off to the application that implements the remote
methods and unpackages and returns the results. In the remote application, a concrete class extending
Stub has implementations of the remote methods. The
onTransact method "unflattens" data in a
Parcel object, calls the remote methods and
"flattens" the result, writes it into a Parcel, and sends that Parcel object back to
the calling application. However, if both the calling application and the remote service
are not, in fact, remote from one another, an instance of the concrete
class that implements the not-so-remote methods is used instead,
cutting out the inter-process communication if it is not
needed. 13.2.3. Publishing an InterfaceThe server
publishes an interface to make it possible for
other activities to find it. Publishing is accomplished by overriding
the onBind method of the Service
class (described in Section 1.6). A client calls the bindService
method of the Context
class, causing a call to the server's onBind method. The bindService and onBind methods are the "handshake" required to
start using a remote interface in a specific Service
object in a specific process running in the Android environment. Here is
the example of an onBind implementation from the the
class RemoteService in the ApiDemos
application: @Override
public IBinder onBind(Intent intent) {
// Select the interface to return. If your service only implements
// a single interface, you can just return it here without checking
// the Intent.
if (IRemoteService.class.getName().equals(intent.getAction())) {
return mBinder;
}
if (ISecondary.class.getName().equals(intent.getAction())) {
return mSecondaryBinder;
}
return null;
}
mBinder and mSecondaryBinder
refer to objects implementing the Stub interface. You will see the
implementation of mSecondaryBinder in the next
section, where implementation of the Stub interface is explained. Let's
take a look at this method in detail. First, the interface requested
depends on matching the name of the interface, which is passed in the
action parameter of the Intent object: if
(IRemoteService.class.getName().equals(intent.getAction())) {
return mBinder;
}In the client application looking for this interface, the contents
of the Intent object were specified in a call to the
bindService method of the Context class. That means that a program
publishing a remote method interface must be a subclass of Service. But a program using a remote method
interface can be any subclass of Context, including Activity and Service. The Intent object is used to
specify the interface. The class name of the interface is the action parameter of the Intent. If the interface matches, the onBind method returns an IBinder instance, an instance of the Stub interface in the remote interface. 13.2.4. Android IPC Compared with Java Native Interface (JNI)Remote procedure calls (RPC) using Android's inter-process communications
largely replace the use of the Java Native Interface (JNI) in Android.
In almost all cases, a remote procedure call is efficient enough to make
it a superior alternative to loading a library—especially one that
dynamically allocates a significant amount of memory—into the Java
virtual machine's address space. And if a process exposing an RPC
interface fails, it is less likely to bring down the Android UI with
it. Android inter-process communication behaves a lot like JNI: the
caller's thread is blocked until the result is returned. Marshalling
data across the IPC boundary is about the same amount of work as data
conversions in JNI. But Binder-based remote procedure calls have a
significant advantage over JNI: if non-Java code crashes or runs out of
memory, the caller of a remote procedure call gets an error that must be
handled, but the Java application does not crash. Remote procedure calls
are a more robust way to call "external" libraries and subject the Java
application to fewer risks in the form of clashing memory management
strategies and other differences between Java applications and libraries
implemented in languages other than Java. 13.2.5. What Binder Doesn't DoThere are at least three things Binder doesn't do, compared with other systems capable of
providing similar functionality: Binder does not manage version information. Binder does not traverse networks. It does not enable applications to discover interfaces.
Some inter-process communications systems enable the two sides of
an inter-process API to negotiate version compatibility. Binder, along
with the higher-level mechanisms built on Binder, does not do this. This
means APIs built on Binder should remain compatible with older versions
if the APIs are open for other applications to use, and it means that
consumers of remote APIs should be resilient to failures caused by
incompatibilities. Make sure to handle those exceptions! Binder-based inter-process communication is also limited to a
single node: it won't take you across the network to other Android
systems. This is a limitation, to be sure, but it is appropriate to a
mobile handset, where endpoint-to-endpoint data connections are rarely
used and often blocked by the routing in a mobile data network. 13.2.6. Binder and LinuxBinder is not a widely used IPC mechanism in Linux. D-BUS is the most widely
used IPC mechanism, and has become commonly used in both server and
desktop Linux distributions and in numerous applications and daemons. In
contrast, Binder was developed by Palm, abandoned, open-sourced as
OpenBinder, and subsequently adopted by Google for Android. Binder may not be the choice of most other Linux distributions,
but it isn't a bad choice: Binder is used throughout Android, including
performance-critical parts of Android, such as the Surface Flinger,
Android's system for sharing the screen among multiple processes. Binder
is simple and performant. It is also an example of the ways in which
Android diverges from the typical use of Linux in mobile handsets and
other small devices. Android is not a shrunken desktop Linux. The use of Binder, the
way Linux user IDs are used to "sandbox" applications, the unique 2D
graphics library, and other design decisions are all in the service of
making Android an ideal platform for running Android applications. It is
debatable whether every design decision that diverges from standards was
worth it, and developers who have started porting and extending Android
actively debate these issues, but some things are certain: Android performs well. None of the unique design decisions
that went into Android were to the detriment of performance. Android
performance is good enough to allow multitasking—something Apple
abjures in iPhone so as not to risk the multimedia user
experience. Android is not attempting to set a general direction for
Linux, or even for embedded Linux. Android has, of course, charted a
radically different course for application development. Android is
consciously different and optimized for a range of smartphone
hardware: big and powerful enough to run a browser, but not
encroaching on the laptop format enough to need a multiwindow user
interface. Android, as a whole, is meant to be just right for its
intended purpose.
|