Android is designed to host a variety of applications and to maximize user choice. The platform is intended to eliminate the duplication of functionality in different applications, to allow functionality to be discovered and invoked on the fly, and to let users replace applications with others that offer similar functionality. Applications must have as few dependencies as possible, and must be able to contract out operations to other applications that may change at the user’s discretion.
Inter-process communication (IPC) is thus the basis of key features of the Android programming model. The techniques we’ll look at in this chapter are:
These enable an application to select an Activity based on
the action you want to invoke and the data on which they operate.
In other words, you don’t need a hardcoded path to an application
to use its functions and exchange data with it. Data can be passed
in both directions using Intent
objects, and this enables a convenient, high-level system of
inter-process communication.
This feature resembles the remote procedure calls (RPCs) offered by other systems: it makes APIs accessible remotely. Remote objects allow you to make method calls that look “local” but are executed in another process. They involve the use of Android’s interface definition language (AIDL).
In this chapter, we will see how these features work and how they can be used in applications.
Android applications could avoid inter-process communication and provide functions in packages loaded by the applications that need them. If applications had to exchange data, they could use the filesystem or other traditional Unix/Linux IPC mechanisms (sockets, shared memory, etc.). But these practices are error prone and hard to maintain. In particular, some of the problems include:
Libraries are difficult to share among multiple Java processes. Java was designed to have threads, not processes, share common code resources.
Sharing address space easily leads to errors and inappropriate access to private data.
Consequently, modern programming environments have moved on to more robust component-like systems. Intents and remote methods fit the bill excellently for Android.
The Android system uses Intent
objects to enable applications to
specify an Activity or Service. Intent
objects also deliver data from one
application to another, providing a simple and convenient form of
IPC.
The Intent class, the Activity class, and Android’s Intent-based inter-process communication solve one of the user interface problems of smartphone platforms that support multiple separate applications: they feel like a collection of separate programs. You don’t have the simplicity of navigating a hierarchical user interface, as in simpler feature phones, and you don’t have multiple windows, as on a PC user interface. The way Activities work together on Android makes it possible to make a seamless user interface out of multiple applications, and inter-process communication can enhance cooperation among applications.
We’ll start with how the client makes a request. Several classes are involved:
Activity
and Context
We’ve seen Activity
objects used throughout
this book. The Context
class, a parent class of Activity
and Service
, contains the methods for
sending Intent
objects from
one Activity
object to
another, whether in the same process or a different one. So
every place you have an Activity
subclass— which is nearly
every place in your application that needs to display a UI—you
have the methods for slinging Intent
objects around to other
Activity instances elsewhere in the Android system.
Intent
Intent objects are passed from process to process,
using methods such as startActivity
and startActivityForResult
.
The Intent
class
itself provides constructors, accessors, and other utilities
for handling the content of an Intent
object, but no methods for
moving Intent
objects.
An important set of accessors are those named putExtra
. Several methods with this
name and different arguments—hence different signatures—let
you attach “extra” data to an Intent. This data can be used
for general-purpose inter-process communication. The first
examples in this chapter will use this kind of simple
inter-process communication.
Most mobile handset user interfaces consist of a linked web, or hierarchy, of “screens”—user interface views that occupy the whole screen, except for areas where titles and indicator icons are displayed and where soft-key labels (if any) are displayed. Usually, these hierarchies are implemented by a single program that manages a “stack” of screens backward from the current screen (and sometimes forward, as well, as in an iPod-like UI). Intent and Activity objects work together, using inter-process communication, to link different parts of different applications’ user interfaces into a coherent user experience with navigation that is unified and seamless when moving between applications. In this section we’ll show how UI navigation and inter-process communication go hand-in-hand.
Almost everyone writes “Hello World” programs. So there is a nearly universal need to augment these programs and prevent them from getting dull by providing a choice of greetings. That is what Example 13-1 does.
package example.sayhello; import example.sayhello.R; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; /** * An activity returning a result */ public class SayHello extends Activity { protected void onCreate(Bundle savedInstanceState) { // Call the parent class super.onCreate(savedInstanceState); // Put up the view for acquiring some input from the user setContentView(R.layout.main); // Set up the listeners for the buttons ((Button)findViewById(R.id.hello)).setOnClickListener(helloListener); ((Button)findViewById(R.id.goaway)).setOnClickListener(goAwayListener); } private OnClickListener helloListener = new OnClickListener() { public void onClick(View v) { returnResult("Hello, other Android!"); } }; private OnClickListener goAwayListener = new OnClickListener() { public void onClick(View v) { returnResult("Get off my lawn, damn kids!"); } }; // Put a result in an Intent object and set the result for this activity void returnResult(String greeting) { // Create the Intent object Intent i = new Intent(); // Put an extra named "result" in the intent i.putextra("result", greeting); // Make this Intent the result for this activity setResult(RESULT_OK, i); // End this activity finish(); } }
Example 13-2 shows the layout file that specifies the user interface provided by this activity.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="4dip" android:gravity="center_horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_weight="0" android:paddingBottom="8dip" android:text="Say hello, or not"/> <Button android:id="@+id/hello" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello"> <requestFocus /> </Button> <Button android:id="@+id/goaway" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Go away"> </Button> </LinearLayout>
This layout describes a screen with two buttons. The listeners
for these buttons are called HelloListener
and GoAwayListener
. In the Java code in Example 13-1, the listener methods
call returnResult
, passing the
string that will be returned.
You can try this program as a standalone application. Create a
new Android project with the package named example.sayhello
and an activity named
SayHello
. Use Example 13-1 for the SayHello
class and Example 13-2 for the main.xml layout file. When run, the
application will display Figure 13-1.
When you click on or press one of the buttons, the program
finishes and disappears from the screen. It also creates an Intent
object used as a “result” for the
activity. Let’s take a closer
look at how it does that. You may want to run the program under the
debugger and set a breakpoint on the first line of the returnResult
method, where we create an
Intent
object, and follow along
using the “step over” command in the debugger.
First, an Intent
object is
created. This is what gets moved from this process to the process
that started this Activity:
// Create the Intent object Intent i = new Intent();
Here we will see how Intent
objects facilitate inter-process communications: you can label and
associate several types of data with an Intent
object and send these “stowaways”
with the object from one process to another. Here we call putExtra
to add data to the Intent. Its
first argument is a String
that
labels the data; here we use “result” as the label. The second
argument, the actual payload, can be any data type supported by the
different putExtra
methods (which
differ in the arguments they take); in our simple example, we use a
String
for the payload as
well:
// Put an extra named "result" in the intent i.putExtra("result", greeting);
The returnResult
method
“returns” the result, not to the method that calls this method, but
through an Intent
object to the
code that started this instance of SayHello
. The following line sets the
result:
// Make this Intent the result for this activity setResult(RESULT_OK, i);
In this example, however, nothing happens to our result. Nobody expects it, and nobody uses it. Next we will change that, and see how one application can use a result produced by another.
This section modifies the “Hello World” application from an
earlier chapter to show how Android can make separate Activity
objects in separate programs seem of-a-piece. This version uses one
Activity to enable the user to choose which greeting to put on the
screen in another Activity. A copy of the data put into the Intent
object in the previous section ends
up in an Intent
object in the
HelloWorldActivity
Activity.
To enable a client to find the Intent, the server assigns it a
label called an action. In this case, we’ll
call our action PICK
, shown here
in Example 13-3.
package example.helloworld; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.widget.TextView; public class HelloWorldActivity extends Activity { TextView helloView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Make a text view and set it to be the content view helloView = new TextView(this); setContentView(helloView); // Make an Intent instance to fill in Intent helloIntent = new Intent(); // Set the action, and type helloIntent.setAction("android.intent.action.PICK"); helloIntent.setType("vnd.example.greeting/vnd.example.greeting-text"); // Ask an activity that matches our Intent object startActivityForResult(helloIntent, 0); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent result) { if (resultCode == RESULT_OK) { String greeting = result.getStringExtra("result"); helloView.setText(greeting); } } }
The changes we made will start an Activity in a separate application and a separate process to provide the user interface for selecting a greeting. After that greeting is returned by the other Activity, this one uses it to say hello.
Run the program. You will see the user interface presented by
the SayHello
program, just as in
Figure 13-1. But this time,
when you press one of the two buttons, the screen will display the
greeting you selected (Figure 13-2).
Let’s take a closer look at how it’s done. Here, again, you may want to follow along using the debugger.
Did you run the SayHello
program yet? You need to do that before you run our modified HelloWorldActivity
program. The Android
emulator installs programs the first time you run them, so once
you run SayHello
it will stay
around as long as the emulator is running. But if the program
hasn’t been run yet, the startActivityForResult
call in the
current example will fail, because Android cannot find SayHello
.
First, we need to start our helper application, which we do
using an Intent
object:
// Make an Intent instance to fill in Intent helloIntent = new Intent();
Then, we need to specify an Activity that is neither a part of our application nor part of an Activity in any of the programs that come with Android:
// Set the action, and type helloIntent.setAction("android.intent.action.PICK"); helloIntent.setType("vnd.example.greeting/vnd.example.greeting-text");
The setType
method requires
a MIME type. We will use a vendor-specific MIME type unique to our purpose (by vendor here, I mean
us). As a result, our SayHello
activity is launched because it has an Intent filter that matches
the parameters we have set in this Intent
object.
Now we call the startActivityForResult
method, passing the Intent
object we created to hold the information that tells the Android
framework to find an Activity matching the specifications in our
Intent: the PICK
action and the
requested MIME type. We don’t explicitly request the SayHello
Activity—we might want to replace
it with something else at some point—but for now, that activity is
what Android will find:
// Ask an activity that matches our Intent object startActivityForResult(helloIntent, 0);
The startActivityForResult
method navigates to a UI that obtains information and returns it.
This is a good illustration of using IPC for a task that could
otherwise have required redundant code in all applications that need
similar information.
Now SayHello
should run and
display its user interface for selecting a greeting. When you have
selected a greeting and the setResult
method is called, Android’s
inter-process communication system will move the result to this
process, and the OnActivityResult
method will be called. We’ve defined it as follows:
protected void onActivityResult(int requestCode, int resultCode, Intent result) { if (resultCode == RESULT_OK) { String greeting = result.getStringExtra("result"); helloView.setText(greeting); }
The method calls getStringExtra
to access the greeting we
have chosen. It uses the setText
method of the TextView
class to
display our selected greeting.
To summarize, in this example one program (SayHello
) acquires some information and
supplies it to another program (HelloWorldActivity
). We have successfully
used inter-process communication.
Android includes a component system based on remote objects
and methods, which we’ll examine in the next section. This is a
powerful feature with many uses, but remote method calls are
overkill in many cases. As you design your programs, first consider
whether your inter-process communications needs fit what Intents and
the Context
class’s
Intent-related methods can do. Particularly when you’re using
inter-process communication to provide a user interface in an
Activity, this high-level form of IPC is easy to use and appropriate
to the task.
This
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.
To 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.
Now 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.
So 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.
There 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.
The
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 Android Service Lifecycle).
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.
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.
There 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.
Binder 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.
35.171.45.182