Why and What's AIDL ?
Each application in Android
runs in its own process. An application cannot directly access the data
of another application for security reasons. However, a couple of
mechanisms allow communication between applications. One such mechanism
that you’ve seen throughout the book is Intents. Intents are
asynchronous, meaning that you can post a message for someone to receive
at some future point in time and just continue with your application.
Every
once in a while we need a more direct, synchronous access to another
process. There are many ways to implement this across process
boundaries, and collectively they are called Interprocess Communication, or IPC for short.
To allow
cross-application communication, Android provides its own version of an
IPC protocol. One of the biggest challenges in IPC is passing data
around, such as when passing parameters to method calls on the remote
systems. IPC protocols tend to get complicated because they have to
convert data from its in-memory format to a format that’s convenient for
sending to another process. This is called marshaling, and the unpacking at the receiver is called unmarshaling.
To help
with this,
"Android provides the Android Interface Definition Language,
or AIDL. It is a lightweight implementation of IPC using a syntax that
is very familiar to Java developers, and a tool that automatically
creates the hidden code required to connect a client and a remote
service."
To
illustrate how to use AIDL to create interprocess communication, we’ll
create two applications: a remote service called LogService and a client
called LogClient that will bind to that remote service.
Implementing the Remote Service
Our remote service, LogService, will simply allow remote clients to log a message to it.
We are going to start by creating the interface
for the remote service. This interface represents the API, or set of
capabilities that the service provides. We write this interface in AIDL
language and save it in the same directory as our Java code with an .aidl extension.
The
AIDL syntax is very similar to a regular Java interface. You simply
define the method signature. The datatypes supported by AIDL are
somewhat different from regular Java interfaces. But all Java primitive
datatypes are supported. So are the
String
, List
, Map
, and CharSequence
classes.
If you have a custom complex data type, such as a class, you need to make it
Parcelable
so that the Android run-time can marshal and unmarshal it. In this example, we’ll create a Message
as a custom type.Writing the AIDL
We
start by defining the interface for our service. As you can see, the
interface very much resembles a typical Java interface. For readers who
might have worked with CORBA in the past, AIDL has its roots in CORBA’s
IDL.
Example 14.1. ILogService.aidl
package com.marakana.logservice; // import com.marakana.logservice.Message; // interface ILogService { // void log_d(String tag, String message); // void log(in Message msg); // }
Next, we’ll look at the implementation of the
Message
AIDL.
Example 14.2. Message.aidl
package com.marakana.logservice; // /* */ parcelable Message;
Specifies the package it’s in. | |
Declares that Message is a parcelable object. We will define this object later in Java.
|
At
this point, we are done with the AIDL. As you save your files, Eclipse
automatically builds the code to which the client will connect, called
the stub because it looks like a
complete method to the client but actually just passes on the client
request to your remote service. The new Java file is located in Gen folder under /gen/com/marakana/logservice/LogService.java. Because this file is derived from your AIDL, you should never modify it. The aidl tool that comes with the Android SDK will regenerate it whenever you make any changes to your AIDL files.
Now that we have the AIDL and the generated Java stub, we are ready to implement the service.
Implementing the Service
Just like any Android service, we implement LogService in a Java class that subclasses the system
Service
class. But unlike our earlier Service implementations, where we ignored onBind()
but implemented onCreate()
, onStartCommand()
, and onDestroy()
, here we’re going to do the opposite. A method in a remote service starts when the client makes its request, which is called binding to the service, and therefore the client request triggers the service’s onBind()
method.
To implement our remote service, we’ll return an
IBinder
object from the onBind()
method in our service class. IBinder
represents the implementation of the remote service. To implement IBinder
, we subclass the ILogService.Stub
class from the auto-generated Java code, and provide the implementation for our AIDL-defined methods, in this case various log()
methods.
Example 14.3. LogService.java
package com.marakana.logservice; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; public class LogService extends Service { // @Override public IBinder onBind(Intent intent) { // final String version = intent.getExtras().getString("version"); return new ILogService.Stub() { // public void log_d(String tag, String message) throws RemoteException { // Log.d(tag, message + " version: " + version); } public void log(Message msg) throws RemoteException { // Log.d(msg.getTag(), msg.getText()); } }; } }
Now that we have implemented the service in Java, we have to provide the Java implementation of the
Message
parcel as well.Implementing a Parcel
Since
Message
is a Java object that we’re passing across processes, we need a way to
encode and decode this object—marshal and unmarshal it—so that it can be
passed. In Android, the object that is capable of doing that is called a
Parcel
and implements the Parcelable
interface.
To be a parcel, this object must know how to write itself to a stream and how to recreate itself.
Example 14.4. Message.java
package com.marakana.logservice; import android.os.Parcel; import android.os.Parcelable; public class Message implements Parcelable { // private String tag; private String text; public Message(Parcel in) { // tag = in.readString(); text = in.readString(); } public void writeToParcel(Parcel out, int flags) { // out.writeString(tag); out.writeString(text); } public int describeContents() { // return 0; } public static final Parcelable.Creator<Message> CREATOR = new Parcelable.Creator<Message>() { // public Message createFromParcel(Parcel source) { return new Message(source); } public Message[] newArray(int size) { return new Message[size]; } }; // Setters and Getters public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
At this point, we have implemented the required Java code. We now need to register our service with the Manifest file.
Registering with the Manifest File
As
always, whenever we provide one of the new main building blocks for
applications, we must register it with the system. The most common way
to do that is to define it in the Manifest file.
Just as we registered
UpdaterService
before, we provide a <service>
element specifying our service. The difference this time around is that
this service is going to be invoked remotely, so we should specify what
Action this service responds to. To do that, we specify the action and
the Intent Filter as part of this service registration.<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.marakana.logservice" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <!-- --> <service android:name=".LogService"> <!-- --> <intent-filter> <action android:name="com.marakana.logservice.ILogService" /> </intent-filter> </service> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
At this point, our service is complete. We can now move on to the client implementation.
Implementing the Remote Client
Now
that we have the remote service, we are going to create a client that
connects to that service to test that it all works well. Note that in
this example we purposely separated the client and the server into two
separate projects with different Java packages altogether, in order to
demonstrate how they are separate apps.
So
we’re going to create a new Android project in Eclipse for this client,
just as we’ve done before for various other applications. However, this
time around we are also going to make this project depend on the
LogService
project. This is important because LogClient
has to find the AIDL files we created as part of LogService
in order to know what that remote interface looks like. To do this in Eclipse:-
After you have created your
LogClient
project, right-click on your project in Package Explorer and choose Properties. - In the "Properties for LogClient" dialog box, choose Java Build Path, then click on the Projects tab.
- In this tab, click on "Add…" and point to your LogService project.
This procedure will add
LogService
as a dependent project for LogClient
.Binding to the Remote Service
Our
client is going to be an activity so that we can graphically see it
working. In this activity, we’re going to bind to the remote service and
from that point on use it as if it was just like any other local class.
Behind the scenes, the Android binder will marshal and unmarshal the
calls to the service.
The
binding process is asynchronous, meaning we request it and it happens
at some later point in time. To handle that, we need a callback
mechanism to handle remote service connections and disconnections.
Once
we have the service connected, we can make calls to it as if it was any
other local object. However, if we want to pass any complex data types,
such as a custom Java object, we have to create a parcel for it first.
In our case, we have Message as a custom type, and we have already made
it parcelable.
Example 14.5. LogActivity.java
package com.marakana.logclient; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.marakana.logservice.ILogService; import com.marakana.logservice.Message; public class LogActivity extends Activity implements OnClickListener { private static final String TAG = "LogActivity"; ILogService logService; LogConnection conn; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Request bind to the service conn = new LogConnection(); // Intent intent = new Intent("com.marakana.logservice.ILogService"); // intent.putExtra("version", "1.0"); // bindService(intent, conn, Context.BIND_AUTO_CREATE); // // Attach listener to button ((Button) findViewById(R.id.buttonClick)).setOnClickListener(this); } class LogConnection implements ServiceConnection { // public void onServiceConnected(ComponentName name, IBinder service) { // logService = ILogService.Stub.asInterface(service); // Log.i(TAG, "connected"); } public void onServiceDisconnected(ComponentName name) { // logService = null; Log.i(TAG, "disconnected"); } } public void onClick(View button) { try { logService.log_d("LogClient", "Hello from onClick()"); // Message msg = new Message(Parcel.obtain()); // msg.setTag("LogClient"); msg.setText("Hello from inClick() version 1.1"); logService.log(msg); // } catch (RemoteException e) { // Log.e(TAG, "onClick failed", e); } } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroyed"); unbindService(conn); // logService = null; } }
At this point our client is complete. There’s a simple UI with a single button that triggers an
onClick()
call. Once the user clicks the button, our client should be invoking the remote call in the service.Testing That All Works
Try to run the client from within Eclipse. Since Eclipse knows that
LogClient
is dependent on LogService
,
it should install both packages onto your device. Once the client
starts, it should bind to the service. Try clicking on the button and
check that LogService
is indeed logging.
Your adb logcat call should give you something like this:... I/LogActivity( 613): connected ... D/LogClient( 554): Hello from onClick() version: 1.0 D/LogClient( 554): Hello from inClick() version 1.1 ...
The first line is from
LogConnection
in the client, indicating that we’ve successfully bound to the service.
The other two lines are from the remote service, one for LogService.log_d()
and the other one for LogService.log()
, where we passed in the Message parcel.
If you run adb shell ps to see the running processes on your device, you’ll notice two separate line items for the client and the server:
app_43 554 33 130684 12748 ffffffff afd0eb08 S com.marakana.logservice app_42 613 33 132576 16552 ffffffff afd0eb08 S com.marakana.logclient
This indicates that indeed the client and server are two separate applications.
Summary
Android
provides an interprocess communication mechanism. It is based on
binder: a high-performance, shared-memory system. To create create a
remote service, we define it using the Android Interface Definition
Language (AIDL), similar to Java interfaces. We then implement the
remote interface and connect to it via the
IBinder
object. This allows us to connect our client to a remote service in a different process altogether.QUESTION: How to send one class from one process to another process?
Suppose you have a class that you would like to send from one process to another through
an IPC interface, you can do that. your class must support the
Parcelable
interface. Supporting the Parcelable
interface is
important because it allows the Android system to decompose objects into primitives that can be
marshalled across processes.To create a class that supports the
Parcelable
protocol, you must do the
following:
- Make your class implement the
Parcelable
interface. - Implement
writeToParcel
, which takes the current state of the object and writes it to aParcel
. - Add a static field called
CREATOR
to your class which is an object implementing theParcelable.Creator
interface. - Finally, create an
.aidl
file that declares your parcelable class (as shown for theRect.aidl
file, below). If you are using a custom build process, do not add the.aidl
file to your build. Similar to a header file in the C language, this.aidl
file isn't compiled.
For example, here is a
Rect.aidl
file to create a Rect
class that's
parcelable:package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;And 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(); } }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
reads 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
information about how to keep your application secure from malware
For more info about AIDL, read
ReplyDeletehttp://developer.android.com/guide/components/aidl.html
If service gets crashed how to indicate to UI ie in activity?
ReplyDelete