Since each application runs in its own process, and you can write a service that runs in a different process from your Application's UI, sometimes you need to pass objects between processes. On the Android platform, one process can not normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and "marshall" the object across that boundary for you.
The code to do that marshalling is tedious to write, so we provide the AIDL tool to do it for you.
AIDL (Android Interface Definition Language) is an IDL language used to generate code that enables two processes on an Android-powered device to talk using interprocess communication (IPC). If you have code in one process (for example, in an Activity) that needs to call methods on an object in another process (for example, a Service), you would use AIDL to generate code to marshall the parameters.
The AIDL IPC mechanism is interface-based, similar to COM or Corba, but lighter weight. It uses a proxy class to pass values between the client and the implementation.
Follow these steps to implement an IPC service using AIDL.
tools/
directory. AIDL is a simple syntax that lets you declare an interface with one or more methods, that can take parameters and return values. These parameters and return values can be of any type, even other AIDL-generated interfaces. However, it is important to note that you must import all non-built-in types, even if they are defined in the same package as your interface. Here are the data types that AIDL can support:
import
statement is needed. import
statements needed):
import
statement is always needed for these.import
statement is always needed for these.Here is the basic AIDL syntax:
// My AIDL file, named SomeClass.aidl // Note that standard comment syntax is respected. // Comments before the import or package statements are not bubbled up // to the generated interface, but comments above interface/method/field // declarations are added to the generated interface. // Include your fully-qualified package statement. package com.android.sample; // See the list above for which classes need // import statements (hint--most of them) import com.android.sample.IAtmService; // Declare the interface. interface IBankAccountService { // Methods can take 0 or more parameters, and // return a value or void. int getAccountBalance(); void setOwnerNames(in List<String> names); // Methods can even take other AIDL-defined parameters. BankAccount createAccount(in String name, int startingDeposit, in IAtmService atmService); // All non-Java primitive parameters (e.g., int, bool, etc) require // a directional tag indicating which way the data will go. Available // values are in, out, inout. (Primitives are in by default, and cannot be otherwise). // Limit the direction to what is truly needed, because marshalling parameters // is expensive. int getCustomerList(in String branch, out String[] customerList); }
AIDL generates an interface file for you with the same name as your .aidl file. If you are using the Eclipse plugin, AIDL will automatically be run as part of the build process (you don't need to run AIDL first and then build your project). If you are not using the plugin, you should run AIDL first.
The generated interface includes an abstract inner class named Stub that declares all the methods that you declared in your .aidl file. Stub also defines a few helper methods, most notably asInterface(), which takes an IBinder (passed to a client's onServiceConnected() implementation when applicationContext.bindService() succeeds), and returns an instance of the interface used to call the IPC methods. See the section Calling an IPC Method for more details on how to make this cast.
To implement your interface, extend YourInterface.Stub, and implement the methods. (You can create the .aidl file and implement the stub methods without building between--the Android build process will process .aidl files before .java files.)
Here is an example of implementing an interface called IRemoteService, which exposes a single method, getPid(), using an anonymous instance:
// No need to import IRemoteService if it's in the same project. private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){ public int getPid(){ return Process.myPid(); } }
A few rules about implementing your interface:
Now that you've got your interface implementation, you need to expose it to clients. This is known as "publishing your service." To publish a service, inherit Service and implement Service.onBind(Intent) to return an instance of the class that implements your interface. Here's a code snippet of a service that exposes the IRemoteService interface to clients.
public class RemoteService extends Service { ... @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; } /** * The IRemoteInterface is defined through IDL */ private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { public void registerCallback(IRemoteServiceCallback cb) { if (cb != null) mCallbacks.register(cb); } public void unregisterCallback(IRemoteServiceCallback cb) { if (cb != null) mCallbacks.unregister(cb); } }; /** * A secondary interface to the service. */ 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) { } }; }
If you have a class that you would like to send from one process to another through an AIDL interface, you can do that. You must ensure that the code for your class is available to the other side of the IPC. Generally, that means that you're talking to a service that you started.
There are five parts to making a class support the Parcelable protocol:
public void writeToParcel(Parcel out)
that takes the
current state of the object and writes it to a parcel.public void readFromParcel(Parcel in)
that reads the
value in a parcel into your object.CREATOR
to your class which is an object implementing
the Parcelable.Creator interface.AIDL will use these methods and fields in the code it generates to marshall and unmarshall your objects.
Here is an example of how the Rect class implements the Parcelable protocol.
import android.os.Parcel; import android.os.Parcelable; public final class Rect implements Parcelable { public int left; public int top; public int right; public int bottom; public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { public Rect createFromParcel(Parcel in) { return new Rect(in); } public Rect[] newArray(int size) { return new Rect[size]; } }; public Rect() { } private Rect(Parcel in) { readFromParcel(in); } public void writeToParcel(Parcel out) { out.writeInt(left); out.writeInt(top); out.writeInt(right); out.writeInt(bottom); } public void readFromParcel(Parcel in) { left = in.readInt(); top = in.readInt(); right = in.readInt(); bottom = in.readInt(); } }
Here is Rect.aidl for this example
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
The marshalling in the Rect class is pretty simple. Take a look at the other methods on Parcel to see the other kinds of values you can write to a Parcel.
Warning: Don't forget the security implications of receiving data from other processes. In this case, the rect will read four numbers from the parcel, but it is up to you to ensure that these are within the acceptable range of values for whatever the caller is trying to do. See Security and Permissions for more on how to keep your application secure from malware.
Here are the steps a calling class should make to call your remote interface:
YourInterfaceName.Stub.asInterface((IBinder)service)
to
cast the returned parameter to YourInterface type.A few comments on calling an IPC service:
Here is some sample code demonstrating calling an AIDL-created service, taken from the Remote Activity sample in the ApiDemos project.
public class RemoteServiceBinding extends Activity { /** The primary interface we will be calling on the service. */ IRemoteService mService = null; /** Another interface we use on the service. */ ISecondary mSecondaryService = null; Button mKillButton; TextView mCallbackText; private boolean mIsBound; /** * Standard initialization of this activity. Set up the UI, then wait * for the user to poke it before doing anything. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.remote_service_binding); // Watch for button clicks. Button button = (Button)findViewById(R.id.bind); button.setOnClickListener(mBindListener); button = (Button)findViewById(R.id.unbind); button.setOnClickListener(mUnbindListener); mKillButton = (Button)findViewById(R.id.kill); mKillButton.setOnClickListener(mKillListener); mKillButton.setEnabled(false); mCallbackText = (TextView)findViewById(R.id.callback); mCallbackText.setText("Not attached."); } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service); mKillButton.setEnabled(true); mCallbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { mService.registerCallback(mCallback); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText(RemoteServiceBinding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; mKillButton.setEnabled(false); mCallbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. Toast.makeText(RemoteServiceBinding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); } }; /** * Class for interacting with the secondary interface of the service. */ private ServiceConnection mSecondaryConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // Connecting to a secondary interface is the same as any // other interface. mSecondaryService = ISecondary.Stub.asInterface(service); mKillButton.setEnabled(true); } public void onServiceDisconnected(ComponentName className) { mSecondaryService = null; mKillButton.setEnabled(false); } }; private OnClickListener mBindListener = new OnClickListener() { public void onClick(View v) { // Establish a couple connections with the service, binding // by interface names. This allows other applications to be // installed that replace the remote service by implementing // the same interface. bindService(new Intent(IRemoteService.class.getName()), mConnection, Context.BIND_AUTO_CREATE); bindService(new Intent(ISecondary.class.getName()), mSecondaryConnection, Context.BIND_AUTO_CREATE); mIsBound = true; mCallbackText.setText("Binding."); } }; private OnClickListener mUnbindListener = new OnClickListener() { public void onClick(View v) { if (mIsBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { mService.unregisterCallback(mCallback); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } } // Detach our existing connection. unbindService(mConnection); unbindService(mSecondaryConnection); mKillButton.setEnabled(false); mIsBound = false; mCallbackText.setText("Unbinding."); } } }; private OnClickListener mKillListener = new OnClickListener() { public void onClick(View v) { // To kill the process hosting our service, we need to know its // PID. Conveniently our service has a call that will return // to us that information. if (mSecondaryService != null) { try { int pid = mSecondaryService.getPid(); // Note that, though this API allows us to request to // kill any process based on its PID, the kernel will // still impose standard restrictions on which PIDs you // are actually able to kill. Typically this means only // the process running your application and any additional // processes created by that app as shown here; packages // sharing a common UID will also be able to kill each // other's processes. Process.killProcess(pid); mCallbackText.setText("Killed service process."); } catch (RemoteException ex) { // Recover gracefully from the process hosting the // server dying. // Just for purposes of the sample, put up a notification. Toast.makeText(RemoteServiceBinding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } } }; // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here will * NOT be running in our main thread like most other things -- so, * to update the UI, we need to use a Handler to hop over there. */ public void valueChanged(int value) { mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0)); } }; private static final int BUMP_MSG = 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case BUMP_MSG: mCallbackText.setText("Received from service: " + msg.arg1); break; default: super.handleMessage(msg); } } }; }