The legend about AIDL. Part 2. In Action

Vladislav Puryev
14 min readJan 16, 2021

--

Hello everybody.

In previous article you could read about theoretical basics of Interface Definition Language and its main idea.

Well, unlike the previous one, this part will contain a lot of code and technical details. Sometimes you will see internal parts of Android system, the parts that most of developers should not worry about in their works.

The following material is dedicated to AIDL itself, how developer can use it, how it works and how RPC and IDL concepts are implemented in Android system. I hope you are interested and ready. Let’s start!

WARNING! The some code will be provided in shortened form. Full versions you can find in your IDE, in AOSP or Android Code Search. Please be careful, cause most of further code can be changed any time by Google developers, that is why some material can be obsolete and partially or completely wrong in the future, but actual for the moment of publication.

This series of articles consist of three parts:

  1. The legend about AIDL. Part 1. The roots
  2. The legend about AIDL. Part 2. In Action (you are here)
  3. The legend about AIDL. Part 3. The crucial ingredient

AIDL for applications developer

I think, that there are just two general situations, when you really need to use Android Interface Definition Language:

  1. You have two (or more) applications and you are completely sure, that user will install one “main” application which can provide some functions for others. It means, that one of your applications must be exactly installed on user’s device, cause other one (two, four and etc.) wouldn’t give user some features without “basic application”. It is possible to say, that your “basic application” is a server.
  2. You are developer of a new device, based on Android.

May be some more usages are exist, but anyway all of them require AIDL power just for one goal — communication between different processes (process is not always an application).

Why so? Well, a lot of parts of Android system and different applications are isolated. Every application has its own virtual machine and resources. I suppose, that you noticed, that sometimes you need extra data or features, that don’t belong to your program. For example, when you are developing or using Share function — options in dialog are not your applications, but you can use their features and pass your resources to them. Such interaction is called Interprocess Communication (IPC).

This article is not about processes, IPC and their behavior in Android generally. That is why just know: when one process (application) needs some features of other external process, then they perform special interaction procedures, that calling IPC (you can read about this term here, for example).

We can use AIDL, when we need communication of our application with other one or parts of system (different services). In other words, implement client-server model inside Android (cause applications are executed on different virtual machines). Now you will see that Android IDL is based on original IDL ideas.

The example

Let’s consider very simple example of IPC between two applications with help of AIDL. Two small applications will be developed for this.

The first application is a server, which provides some function (I decided to use just one for simplifying) to client side. First of all, the file with .aidl extension must be created (a lot of things about further information you can find here). For adding this file in Android Studio it is better just open menu and in New option choose AIDL, after that — AIDL file. The code of file is below (some generated code was deleted).

File .aidl

Well, it is just very small file, which looks like simple interface on Java. But be careful! Despite the fact, that this file should be written with language, which looks like Java, it cannot be used in Java/Kotlin code directly.

Well, interface must be implemented by new Android service. Because our applications are typically written on Java or Kotlin, they cannot implement interface in .aidl file directly. Now one of the main ideas of IDL starts to work — compilers. It is necessary to build project to make them work. After that the new file, called ICustomRemoteService.java, will be created and located in generated section of project. You will see this file in future, but now it is possible to implement it. The service class is very simple and it’s creating doesn’t have a lot of differences from creating of the bound services.

Code of CustomService at server side

This is enough to perform IPC between two Android applications. I think, that you noticed the creation of field, which called binder. If you read the previous article, you can guess what this code can mean (this will be considered further with more details). Well, main idea of this service is just to return to client application the value of remote process id, included in special string.

Let’s take a look at client side. Please note, that client application is a single program, which is not a part of server side. I mean, that it is separated application, developed in another project.

First of all, the .aidl file must be created with the same code, and with the same package name. It is really important, because you need to connect to you remote service, that is located in another application (it means, that you don’t have access to its class as you could do it with local services). Well, the declaration of package with the same name as at server side is necessary to perform explicit intent for binding service and is due to Android system specific things. These details are beyond of this article, just take this fact as it is. The .aidl file for client side must be exactly the same as for server application. Even package name must be identical.

After that you need to perform a binding to service at Activity class.

Code of MainActivity at client side

Let’s start reading from bottom. As you can see at code above, the binding process is performed without class name of Service, just with package name. It is not necessary to specify an action for Intent instance. But if you have several services, that using the same AIDL, it is better to specify additional details for right interaction. Anyway it is necessary to declare intent filter on server-side (read more here). The call of bindService() method is the same as you do for local services.

At onServiceConnected() callback you can see, that service instance is created by passing parameter, called service (that has type IBinder), to some method asInterface() of class Stub, which is located somewhere at ICustomRemoteService.

Well, no more interesting here. Let’s take a look at the non-human creation, interface ICustomRemoteService.

AIDL compiler product

And now, ladies and gentlemen, please welcome the alive prove of fact, that AIDL is based on ideas of Interface Definition Language as they are! The further code is quite big, that is why let’s try to investigate it step by step, from the top.

Warning! During AIDL research, I have noticed some differences between generated files at server and client side. After additional creation of new projects with update of compileSdkVersion the files become identical. Therefore I suppose, that AIDL code generation can be updated by Google during time and final files can have some differences.

The top of generated file ICustomRemoteService.java

First of all, generated file is just an interface. How do you like it? Interface, that extends another one — IInterface, located at package android.os. Let’s take a look there.

IInterface.java

It is just base interface for “Binder” descendants. Well, just one method and no more. Let’s return to generated class.

Static nested class Default in ICustomRemoteService.java

Further you can see static nested class (nice trick, isn’t it?), that is called Default. And this class implements its external interface ICustomRemoteService, but all overridden methods do nothing and return null. Honestly, I couldn’t find usage of this class in Android system (details are in the next article). For now, there is no anything interesting inside generated code. Except of next static nested class — Stub.

Class Stub

It implements external generated interface ICustomRemoteService too and, what is the most important thing, extends class Binder.

Binder is very significant and complex tool. It deserves its own series of articles and its studying is beyond of this material (more information about Binder you can find here, here and inside AOSP source code). For now you need to know just two things:

  • Binder implements interface IBinder.
  • All IPC goes through the Binder” in Android.
  • Binder is a narrow channel from first article.

Class Stub extends powerful class Binder and implements the .aidl interface, which is represented by external generated ICustomRemoteService.

First part of generated Stub class inside ICustomRemoteService.java

In the top of this class, you can see the special generated constant value. This value is using for unique identification of Binder object.

After that there is a constructor of Stub class. Method attachInterface() is using for setting Binder’s interface and descriptor value. As ICustomRemoteService interface is extension of IInterface, it is pretty legal to pass Stub class instance to this method. This constructor is calling in CustomService class (see “Code of CustomService at server side” code snippet).

The next one is method asInterface(). This is fully generated member. The RPC concept is possible in Android due to it.

For now, it is used in MainActivity class of client application (see “Code of MainActivity at client side” code snippet). And sure, client side doesn’t have any idea, what implementation it gets, it knows only about interface.

Method asInterface() is getting the IBinder implementation instance (remote service). Descriptor value is passing to method queryLocalInteface(), that is returning the IInterface instance (the Stub class extension — remote service again), this value returns to calling side. Otherwise the Proxy object is creating and returns. You will know more about Proxy class further. For now just accept next thing — asInterface() will return Proxy instance for remote call and Stub extension (body of if statement) for local one.

Method asBinder() is using for getting right IBinder implementation. It is necessary for right work with Proxy instances, but must be implemented in all IInterface users. Let’s take a look at next part of Stub class.

Second part of generated Stub class. Method onTransact()

There is an overridden method onTransact() from Binder class. As you can see, calling of super method is performed only in default section. The section INTERFACE_TRANSACTION contains getting constant from interface IBinder. There descriptor value is written to Parcel.

Parcel is not simple concept also. It is used for “marshalling” and “unmarshalling” data in Android RPC/IPC implementation. To learn more see official documentation and source code.

The second case section is most interesting and important. Please note the value of code TRANSACTION_getProcessId — special generated constant, that has first part at upper case. Second part has name of interface method. The number of constants is the same as amount of methods in interface. Constant’s name is depending on method name and probably that is why it is not possible to use overloaded methods in .aidl file. For now, there is just one method and just one constant.

WARNING! Watch for the identity of .aidl files. Constants in generated interface don’t consider parameters types and type of returning value.

In case of call from client application the data “marshaling” is performed for developer.

  • Firstly, method enforceInterface() is calling. This method calls special native method nativeEnforceInterface(), passes descriptor of IBinder object and special value of type long, that called mNativePtr.
  • After that, the first argument of remote method is declaring. Yes, as much arguments method has, then bigger amount of variables with name argN (N — index of argument) is generating.
  • Then, this “arg” gets value from readString() method of Parcel. What is going on if there are several parameters with the same type? You shouldn’t worry about this — Parcel call necessary native methods and all values are retrieving at their time (read more about parameters in official documentation).
  • When all parameters are initialized, the remote method (getProcessId()) is executing and main work is performing.
  • The result is passing to method writeString() of parameter reply and then switch path returns true.

Now it is time to research a class Proxy.

Class Proxy

Third part of generated Stub class. Class Proxy

First of all pay attention, that this class is private. It has one field, called mRemote, which is initialized in constructor. Let’s remember next sequence: client code gets IBinder instance inside overridden method onServiceConnected(), this IBinder object is passing to method asInterface(), then, cause client side is meant, the Proxy instance is creating and returns.

Sequence of retrieving ICustomRemoteService instance for client side

Thus Proxy gets IBinder implementation from narrow channel (the work of Binder with System native code). One more time — this IBinder implementation is remote bound service. The process above will be considered in more detailed way in the next article.

The next two methods are self-explained, but the third one is method, that client uses finally.

  • Firstly, two Parcels are creating: one for input and one for remote method return value.
  • After that, declaration of variable _result is performed (if method declared with void, then variable _result will not be generated). The type of _result matches to return type of method.
  • I suppose, that you have noticed, that this method can throw an RemoteException, therefore the main work is performing inside try block. Firstly method writeInterfaceToken() gets the DESCRIPTOR value. This method is calling the special nativeWriteInterfaceToken() inside.
  • After that, input parameters are writing to Parcel data and method transact() of IBinder instance is invoked, getting all necessary arguments. And it can throw RemoteException. As a result, method transact() calls onTransact() of Binder extension (see “Second part of generated Stub class. Method onTransact()” code snippet).
  • If transaction key has unrecognized value for remote side, then default Impl should be called (getDefaultImpl() will be considered further).
  • If everything is okay, then method readException() is called from Parcel _reply.
  • Finally, _result is getting return value from Parcel _reply (method readString()).

Wait a minute! Something is suspicious here, isn’t it? How does Parcel get result, if transact() returns boolean value?

Okay, this Parcel is passing to method onTransact(), where it is getting all necessary data. But this method is on remote side! And chances are it is not the same as passing object to method of another class. Well now, everything is quite simple (if take some things of Android native code as they are). Proxy doesn’t use the Binder instance, it uses BinderProxy. Let’s just take a look at its method transact().

Overridden method transact() inside BinderProxy

The most interesting is method transactNative(). When parameter reply inserts to this method, all other work go to native code, after that remaining operations are performing on remote side.

In the end of Proxy class implementation of remote method (getProcessId() in example), method recycle() is invoked for both parcels. After calling recycle() Parcel object must not be used any more. For more information — research Parcel class.

Finally, the result is returning to caller.

Proxy class declaration ends with static field, which called sDefaultImpl. Let’s move further.

The end of generated interface

Final part of generated Stub class

There is the constant TRANSACTION_getProcessId, that is using in Stub class overridden method onTransact(). As a result, it has value “0x00000001” (value of FIRST_CALL_TRANSACTION).

If .aidl file has more, than one method, then next constant will have value of FIRST_CALL_TRANSACTION, increased by 1 — “0x00000002”.

Respectively third constant will equals “0x00000003” and so on.

The next one is method setDefaultImpl(). It is pretty simple and needed only to set value for static field sDefaultImpl (that is field of Proxy class — see code snippet “Third part of generated Stub class. Class Proxy”). As you can see, this sDefaultImpl cannot be set by more, than one user in the same process. If passed value exists, then this method will set it and return true. Otherwise — false returns (or Exception will be thrown).

The last method is getDefaultImpl(), that is self-explained.

Field sDefaultImpl is using if remote method transact() returns false. But, sure, it must be set before this call. Probably, some developers use “default” implementations of their remote services if server-side is not completely ready. I mean, for example, if your client side has .aidl file with two methods, but remote side doesn’t have them. In this case method transact() returns false, because of constant for non-existent remote function. Then “default impl” can be used.

When class Stub ends, the only one interface method is written in generated file. I remind, that this method was implemented twice just in generated file. Sure, more methods declarations can be there, but, just imagine, how much “non-interface” code was created before this part.

As you could see — there were two “stubs” in one file for both sides: Stub for server side and Proxy — for client. And yes, for curious readers, Stub implements ICustomRemoteService interface, but it mustn’t declare implementation of its method, cause it is an abstract one (a lot of nice moves in this generated class, isn’t there?).

The whole AIDL scheme can be expressed like next one.

AIDL interactions

If it is interesting, you can compare the scheme above with this, which describes RPC from Microsoft. The not big difference is explained not by similar technologies, but the same concepts and techniques.

AIDL… and what?

Some paragraphs ago there were mentioned two main characters for connection between two different applications.

First is Binder. It is main engine for IPC in Android.

Second is Parcel. It is a special form for arguments in remote procedures calls in Android. Parcel represents marshalled data. The more important — this object can be shared between different applications through the Kernel of Android system.

Binder performs different operations with Parcel, depending on actions, that remote side needs from another application. Well, but what is the role of AIDL?

If IPC is meant, in this case AIDL is useless. That is right — it does nothing for making connection between processes, just calls different methods from Binder and Parcel instances.

Even RPC approach can be implemented without AIDL (see next article for details).

This is just useful feature for developers. Yes, yes, for humans. In this big article you can see a lot of code only for remote service with one method. How many lines of code does developer care about in this long journey? Only about code from The example section. All other things were created by special Android IDL compiler. Really, just imagine — more than one hundred lines of code just for one method. What if there are ten methods, twenty? And every time — create Parcel, put data there, call Binder, cares about exceptions and next, repeat as much times as number of methods. Oh yes, don’t forget, that you have two applications at least.

Source-level stubs” from previous article are not difficult, but can be very annoying. This is main goal of AIDL — to automate these actions of implementation RPC and IPC, hiding the specific low-level details, and make developers job quite easier.

Okay, the official documentation tells us, that AIDL is very useful, if we want to use “handle multithreading in your service”, otherwise we need to use Messenger. But class Messenger can show, that it uses AIDL by its own (you have seen some information from next article, try to forget it).

Therefore, the most important task for AIDL — to generate “routine” code for developer, that is helping to make RPC and IPC in Android. Are you interested in location of AIDL compilers? It can be found in code of AOSP, using path: aosp_directory/system/tools/aidl.

Directory aidl in AOSP

Please note, that this dir contains files, that called aidl_to_cpp.cppandaidl_to_java.cpp. It can lead to conclusion that .aidl files can be used for generation either java code or C++ code.

Conclusion

This article covered the work of AIDL. As a result — developer can set interactions between two different applications and do not need a lot of special code for right work of specific Android components. All this work is performing by special IDL compilers, which have their special implementation in Android system.

There were a lot of text and many lines of code. Hope, that it was interesting and useful for you. I suppose that now AIDL is not so mysterious and you learnt something new not only about special tool, but about entire Android system. If there were some mistakes, please let me know about it — the fixes will come as soon as possible.

It was very big article and I want to thank you for your time and interest. Stay well and have a good mood.

See you again.

Useful links

Thanks to my brother, who reviewed this article first and helped me to create it.

Also I would like to thank my co-worker Andrei for thoughtful review and help with improvement of this article.

--

--

Vladislav Puryev
Vladislav Puryev

No responses yet