Binder: Android Interface Definition Language
Android interface definition language (AIDL) is used to define the contract that a Binder service object provides. With the help of AIDL developers only need to focus on implementing the business logic of the Binder service interface instead of all kinds of boilerplate code. The tool for auto-generating Java source code is aidl
. It is included in Android SDK under build-tools
directory. Not too long ago AIDL is only used for auto-generating Java code. But now Android includes a aidl-cpp
tool to auto-generate C++ code as well. It is included in the Android Open Source Project (AOSP). If you don't want to install Android SDK or compile AOSP, you can install a standalone version of aidl
and aidl-cpp
with the apt
tool (tested under Ubuntu 18.04 LTS):
android1001@ubuntu:~$ sudo apt install aidl
Google has a detailed documentation about AIDL [1] and another introduction about aidl-cpp
[2]. So we will not go through the specifications of AIDL in this article. In this article we will go through the *.aidl
files in HamKing [3] project and the auto-generated Java files to learn it in a contextual way. This sample project is already introduced a the article "Binder introduction".
Here are all the *.aidl
files in the project:
There are two types of definitions: interface
and parcelable
. An interface
defines a Binder service and aidl
will auto-generate two kinds of Binder objects with it. The first one is a Stub
class that extends from Binder
and should be implemented by server side of the service. The other one is a Proxy
class that should be used by the client side of the service. A parcelable
defines a Parcelable
type which is the data model Binder uses in user space. aidl
will auto-generate the Parcelable
class based on the definition. This is a recent feature of AIDL so you should use latest build tools version for that. Previously developers need to handcraft the Parcelable
classes.
The parameters and return types in an interface method can be a raw type, a Parcelable
or a Binder object. A parameter can be modified by in
, out
or inout
and by default it is in
. The in
modifier means that the data in that argument only moves from client to server. Server side only receives the data in it and should not make any changes to it. The out
modifier means the contrary. In this case the parameter client side provides is just a placeholder, the data will not be serialized and send to server. Instead, the placeholder will be initialized with data that server side returns. The inout
is a combination of in
and out
. The data will be passed to server side and server will return data for that field. In short, for in
data goes from client to server, for out
data goes from server to client, for inout
data goes both ways.
For example, the order
parameter in requestOrder
is inout
because the client leaves the mOrderId
field in order
parameter empty and expecting server to fill it. For the same reason, the order
parameter in pickupOrder
is inout
because the client is expecting the server to fill in the mBurgerImage
field. All other parameters are in
because the client just wants to send the data to the server.
We will copy the *.aidl
files out from HamKing project to a test folder since they are the only files we care about in this article:
Then we use the aidl
tool to generate Java source code for IHamKingInterface.aidl
:
The is the modified version of IHamKingInterface.java
file under the generated
folder:
The three interface methods in the IHamKingInterface.aidl
file are just added to the Java IHamKingInterface
class. The Stub
class is meant for server side to extend and implement and Proxy
is used by client side. They both implement the IHamKingInterface
but Stub
does the real business logic of the interface methods. Proxy
just serializes all arguments into a Parcel
and calls the remote Stub
to do the actual work with the help of Binder driver.
It is not difficult to figure out how aidl
implements the in
and out
semantics. The client
parameter is a in
type, so it is only marshaled into _data
on line 101 and then sent to server on line 111. The order
parameter is a inout
so it is both sent to server in _data
and read back from server in _reply
.
We described in the article “Binder architecture and core components” that due to client or server side bugs, the proxy side of a service may not be pointing to the correct remote Binder service. Binder framework cannot prevent this from happening but it provides an interface descriptor mechanism for both side to detect the error. You are not obligated to use this mechanism but the code that aidl
generates does use it. The aidl
tool auto-generates a DESCRIPTOR
field to act as the identifier of the service. Line 10 calls attachInterface
to attach this identifier string to the native Binder service. First of all, the proxy side can request for this identifier by using INTERFACE_TRANSACTION
code. Native side will write the string in the reply Parcel
and the client can detect the potential mismatch. On the other hand, server can verify the mismatch with enforceInterface
method in Parcel
class. enforceInterface
will compare the expected descriptor string to the one sent by the client and throw an SecurityException
if a mismatch is found. The client sends the expected descriptor string by writeInterfaceToken
.
The code
argument in transact
method of an IBinder
defines the transaction code of a Binder transaction. For example, requestOrder
, cancelOrder
and pickupOrder
each has a unique transaction code. Binder driver knows nothing about the semantics of the code, it is totally a user space concept. How client and server interpret the code is based on a contract that both client and server agree. aidl
is responsible of generating this contract. What it does is generating an integer code for each interface method in the .aidl
file. The exact code an interface method gets depends on the order it appears in the .aidl
file. With that being said, simply switching the method orders in the .aidl
file makes it incompatible with the older version.
The asInterface
method does what is called "interface casting". The input parameter obj
can be a BinderProxy
or Binder
depending on whether the service is implemented in local process or in a remote process. This method will just down cast it to an interface type if it is a Binder
and use a Proxy
class to wrap it if it is a BinderProxy
. You cannot make any assumption about whether the IBinder
you receive is local or remote. For example, in the HamKing project the client app calls bindService
to bind to the RemoteService
. However the server app itself can bind to the RemoteService
as well. In both cases you will get back an IBinder
in onServiceConnected
. But the actual type of the IBinder
will be BinderProxy
if binding from the client app and will be Binder
if binding from the server app.
These are the important aspects of the auto-generated Java file. The aidl-cpp
tool can do the code auto-generation in C++ as well. The version of aidl-cpp
in Ubuntu 18.04 LTS distribution doesn't understand parcelable
declaration, so I will only use it against ICreditCard.aidl
:
If you follow the previous article “Binder architecture and core components”, the generated code should be easy to understand. The DECLARE_META_INTERFACE
macro just declares the interface descriptor and interface casting facilities which is the same concept as the Java counterpart. The IMPLEMENT_META_INTERFACE
macro expands to the definitions of the corresponding DECALRE_META_INTERFACE
. Other than that the credit_card.cpp
should be easy to understand since the code structure is the same as the Java version.
External links
[1] https://developer.android.com/guide/components/aidl
[2] https://android.googlesource.com/platform/system/tools/aidl/+/brillo-m10-dev/docs/aidl-cpp.md