Chapter 13. Inter-Process Communication

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:

Intents

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.

Remote methods

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.

Intents: Simple, Low-Overhead IPC

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.

Intent Objects Used in Inter-Process Communication

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.

Activity Objects and Navigating the User Interface Hierarchy

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.

Example: An Intent to Pick How We Say “Hello World”

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.

Example 13-1. An Intent that chooses alternate “Hello World” strings
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.

Example 13-2. Resource for alternate “Hello World” strings
<?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>
Output of simple “Hello World” program
Figure 13-1. Output of simple “Hello World” program

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.

Getting a Result via Inter-Process Communication

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.

Example 13-3. HelloWorldActivity.java
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).

Output of “Hello World” program after user selection
Figure 13-2. Output of “Hello World” program after user selection

Let’s take a closer look at how it’s done. Here, again, you may want to follow along using the debugger.

Note

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.

Remote Methods and AIDL

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:

  1. Define the interface in the AIDL.

  2. 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.

  3. Invoke the methods where you want to use them.

Android Interface Definition Language

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.

Classes Underlying AIDL-Generated Interfaces

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.

Implementing the Stub interface

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.

Getting an instance of the remote Proxy object

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.

Publishing an Interface

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.

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.

What Binder Doesn’t Do

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 and Linux

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
35.171.45.182