AIDL in Android: Building Inter-Process Communication

AIDL, or Android Interface Definition Language, is a mechanism Android provides to facilitate communication between different apps.
It is similar to other IDLs, IDL in general facilitates communication between unknown Objects or Processes.
Table of Contents
What is AIDL?
AIDL is all about defining interfaces that allow one process to communicate with another, regardless of whether they belong to the same app or different apps altogether via interprocess communication (IPC).
How it works?
In Android, one process cannot access the memory of another process so they cannot understand each other, to talk they need to decompose their objects into primitives that the operating system can understand, and Android OS marshals the object across that boundary for you. the code to do that marshaling is complicated to write so Android handles it for you with the help of AIDL.
Each app in Android runs in its processes an app cannot access the memory space of another app this is called application sandboxing.
To allow cross-application communication android provides an implementation of interprocess communication or IPC.
IPC protocol tends to get complicated because all marshalling an unmarshalling of necessary data.
AIDL allows you to define the programming interface that both the client and service agree upon to communicate with each other using inter-process communication, AIDL is a lightweight implementation of IPC using a syntax that is very familiar to JAVA or Kotlin developer and it also provide a tool that automates stub creation.

Understanding Inter-Process Communication (IPC)
Inter-process communication (IPC) refers to the mechanisms and techniques used by different processes to communicate and share data within an operating system or across networked systems.
Types of IPC Mechanisms:
- Android offers several mechanisms for IPC, each catering to specific requirements:
- Intent-based Communication: Enables communication between components using intents, but it’s limited to specific scenarios like starting activities or broadcasting messages.
- Shared Preferences and Files: Allows sharing data via shared preferences or files, yet it might not be suitable for real-time communication or complex data structures.
- AIDL-based Communication: A robust mechanism specifically designed for RPC (Remote Procedure Call) between processes, offering a structured way to define interfaces and exchange complex data types.
Why AIDL?
In the world of Android, every app typically runs in its own user space, with its instance of the Android Runtime. This architecture ensures both security and stability. However, there are scenarios where apps or services might need to communicate with one another
For instance, two apps have specific functionalities or services common, Rather than creating the same logic for those functionalities. why not just use the functionality that the other application has? This is where AIDL comes into play.
When Use AIDL
- Implementing Client-Server Communication:
- When you have separate processes in your app architecture, such as a background service (server) needing to interact with the UI (client). AIDL helps define interfaces that allow these processes to communicate efficiently.
2. Inter-App Communication:
- When different apps need to communicate with each other, especially when one app needs to utilize specific functionalities or services provided by another app. AIDL enables the definition of interfaces that enable this interaction securely.
3. Sharing Data Across Processes:
- When you need to share data between different components of an app running in separate processes, AIDL helps define the structure and rules for exchanging this data efficiently and securely.
4. Implementing Remote Method Invocation:
- AIDL is used to define methods that can be invoked remotely across different processes. This is essential when you want to trigger actions or exchange data between disparate components of your app.
Remember, AIDL is particularly useful in scenarios involving IPC (Inter-Process Communication) and when you need to define structured interfaces for communication between different parts of your app or even between different apps on the Android platform.
Client-Server AIDL app

This app consists, of
Server app
The server app provides the following background functionality,
- isServiceSatarted() – This function returns the Boolean value of whether is service starts or not.
- getColor() – This function sends a random color to clients.
Client app
This app gets the functionality from the server i.e the logic part and updates the UI via the AIDL interface.
Implementation of AIDL
1. Gradle configuration
To use AIDL (Android Interface Definition Language) in your project, you typically need to enable the AIDL feature.
android {
// Other configurations...
buildFeatures {
aidl true
}
}
Server app
Note: This server app has no activity (which means no UI) so this app is doing only background operations.
2. Define the Interface
Create AIDL interface IColorAidl, This interface specifies the methods that the client can call on the server and vice versa. It’s crucial to define the interface in a .aidl file.

package rishiz.com.aidl_server;
interface IColorAidl {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
boolean isServiceSatarted();
int getColor();
}
3. Stub Generation
When you define an AIDL interface in Android, the system generates a “stub” for that interface. This stub acts as a proxy for the actual object residing in a different process.
To generate stub, Rebuild the project,

After rebuilding, the Android framework automatically generates the stub on the below path.

4. Implement the Stub for the Server-side Service
The Android SDK tools generate an interface in the Java programming language based on your .aidl
file. This interface has an inner abstract class named Stub
that extends Binder
and implements methods from your AIDL interface. You must extend the Stub
class and implement the methods.
private val binder: IColorAidl.Stub = object : IColorAidl.Stub() {
override fun isServiceSatarted(): Boolean {
return true
}
override fun getColor(): Int {
val rnd = Random.Default //kotlin.random
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))
}
Implement an Service
and override onBind()
to return your implementation of the Stub
class, Create the service named AidlService, and implement onBind that returns the binder object.
See Android App Components to know Services in detail.
package rishiz.com.aidl_server
import android.app.Service
import android.content.Intent
import android.graphics.Color
import android.os.IBinder
import android.util.Log
import kotlin.random.Random
class AidlService : Service() {
companion object {
private val TAG = AidlService::class.java.canonicalName
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Log.d(TAG, "onStartCommand")
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder {
Log.d(TAG, "onBind")
return binder
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
}
private val binder: IColorAidl.Stub = object : IColorAidl.Stub() {
override fun isServiceSatarted(): Boolean {
return true
}
override fun getColor(): Int {
val rnd = Random.Default //kotlin.random
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))
}
}
}
Override onBind methods return the binder and other callback methods of service.
Add this service to the manifest.
<service
android:name=".AidlService"
android:exported="true">
<!-- <intent-filter>-->
<!-- <action android:name="IService"/>-->
<!-- </intent-filter>-->
</service>
Here you can add intent action if needed so that the client app uses this action to bind this service this action else service can bind with a name, this will clear later.
Client App
1.Add dependencies
Add the following dependencies,
// LiveData
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
//For circular view
implementation 'com.mikhaellopez:circleview:1.4.1'
2. UI
Include the following views,
- Two text views one is to display text ServiceStarted and another for showing the service started status.
2. Circular imageview as per need.
3. One button to on the circular lights.

3.Create AIDL interface
Make one package with same name as your server app’s package name in which your AIDL file resides and, in that put, same “.aidl” files of server.

4. Bind to a service
On the client side (e.g., an Activity or another Service), bind to the AidlService to access its methods:
We have create LocalService to bind AidlService.
Application components (clients) can bind to a service by calling bindService(). The Android system then calls the service’s onBind() method, which returns an IBinder for interacting with the service.
The binding is asynchronous, and bindService() returns immediately without returning the IBinder to the client. To receive the IBinder, the client must create an instance of ServiceConnection and pass it to bindService(). The ServiceConnection includes a callback method that the system calls to deliver the IBinder.

To bind to a service from your client, follow these steps,
1.Implement ServiceConnection.
Your implementation must override two callback methods:
onServiceConnected()
- The system calls this to deliver the IBinder returned by the service’s onBind() method.
onServiceDisconnected()
- The Android system calls this when the connection to the service is unexpectedly lost, such as when the service crashes or is killed. This is not called when the client unbinds.
private var aidlserviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "Connected to aidlService")
if (iColorAidl == null) {
iColorAidl = IColorAidl.Stub.asInterface(service)
}
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d(TAG, "Disconnected from aidlService")
iColorAidl = null
}
}
2.Call bindService()
, passing the ServiceConnection
implementation
With this ServiceConnection
, the client can bind to a service by passing it to bindService()
, as shown (recommded to bind onCreate())
I. If Intent action not provide
Intent().setClassName("rishiz.com.aidl_server","rishiz.com.aidl_server.AidlService")
.also{
bindService(it, aidlserviceConnection, Context.BIND_AUTO_CREATE)
}
II. Passing intent action.
Intent("AidlService").setPackage("rishiz.com.aidl_server")
.also{
bindService(it, aidlserviceConnection, Context.BIND_AUTO_CREATE)
}
3.When the system calls your onServiceConnected() callback method, you can begin making calls to the service, using the methods defined by the interface.
4. To disconnect from the service, call unbindService()
It is a better practice to unbind the client as soon as it is done interacting with the service.
Manage the lifecycle of a bound service.
5.Create a bound service
Our intention is to make LocalService as Service that provides binding.
If only the local application uses your service and it doesn’t need to work across processes, then you can implement your own Binder
class that provides your client direct access to public methods in the service.

Here’s how to set it up:
- In your service, create an instance of
Binder
that does one of the following:- Contains public methods that the client can call.
- Returns the current Service instance, which has public methods the client can call.
- Returns an instance of another class hosted by the service with public methods the client can call.
- Return this instance of
Binder
from theonBind()
callback method. - In the client, receive the
Binder
from theonServiceConnected()
callback method and make calls to the bound service using the methods provided.

class LocalService : Service() {
// Binder given to clients.
private val binder = LocalBinder()
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
inner class LocalBinder : Binder() {
// Return this instance of LocalService so clients can call public methods.
fun getService(): LocalService = this@LocalService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}
Final LocalService Code:
class LocalService : Service() {
var iColorAidl: IColorAidl? = null
private val binder = LocalBinder()
companion object {
private val TAG = LocalService::class.java.canonicalName
}
inner class LocalBinder : Binder() {
// Return this instance of LocalService so clients can call public methods.
val service: LocalService
get() = this@LocalService
}
private var aidlserviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "Connected to aidlService")
if (iColorAidl == null) {
iColorAidl = IColorAidl.Stub.asInterface(service)
}
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d(TAG, "Disconnected from aidlService")
iColorAidl = null
}
}
@RequiresApi(Build.VERSION_CODES.N)
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
//If intent action not provided for server side service
Intent().setClassName("rishiz.com.aidl_server", "rishiz.com.aidl_server.AidlService")
.also {
bindService(it, aidlserviceConnection, Context.BIND_AUTO_CREATE)
}
//If intent action provided for server side service
Intent("AidlService").setPackage("rishiz.com.aidl_server")
.also {
bindService(it, aidlserviceConnection, Context.BIND_AUTO_CREATE)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Log.d(TAG, "onStartCommand")
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder {
Log.d(TAG, "onBind")
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onBind")
return super.onUnbind(intent)
}
override fun onRebind(intent: Intent?) {
Log.d(TAG, "onRebind")
super.onRebind(intent)
}
override fun onDestroy() {
Log.d(TAG, "onRebind")
unbindService(aidlserviceConnection)
super.onDestroy()
}
fun isServiceStarted(): Boolean? {
return iColorAidl?.isServiceSatarted
}
fun getColor(): Int? {
return iColorAidl?.color
}
}
The LocalBinder
provides the getService()
method for clients to retrieve the current instance of LocalService
. This lets clients call public methods in the service.
6.Bound Service with MVVM
Implement the service connections methods and receive the Binder from the onServiceConnected() callback method and make calls to the bound service methods.
class MyViewModel: ViewModel() {
private val mBinder:MutableLiveData<LocalService.LocalBinder?> = MutableLiveData()
companion object{
private val TAG=MyViewModel::class.java.canonicalName
}
private val localServiceConnection:ServiceConnection=object:ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG,"Connected to LocalService")
val binder = service as LocalService.LocalBinder
mBinder.postValue(binder)
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.d(TAG,"Disconnected to LocalService")
mBinder.postValue(null)
}
}
fun getServiceConnection(): ServiceConnection {
return localServiceConnection
}
fun isServiceStarted(): Boolean? {
return mBinder.value?.service?.isServiceStarted()
}
fun getColor(): Int? {
return mBinder.value?.service?.getColor()
}
}
7.MainActivity
OnCreate Defined ViewModel variable ,Start The LocalService and bind it.
Initialize the views.
class MainActivity : AppCompatActivity() {
companion object {
private val TAG = MainActivity::class.java.canonicalName
}
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "onCreate")
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
Intent(this,LocalService::class.java)
.also{serviceIntent->
startService(serviceIntent)
bindService(serviceIntent, viewModel.getServiceConnection(), BIND_AUTO_CREATE)
}
val btn = findViewById<Button>(R.id.button)
val isServiceStart = findViewById<TextView>(R.id.isStart)
btn.setOnClickListener {
isServiceStart.text = viewModel.isServiceStarted().toString()
circularView1()
circularView2()
circularView3()
circularView4()
circularView6()
circularView7()
circularView8()
circularView9()
}
}
override fun onStart() {
super.onStart()
Log.d(TAG, "onStart")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
unbindService(viewModel.getServiceConnection())
}
private fun circularView1() {
val circleView = findViewById<CircleView>(R.id.circleView1)
circleView.apply {
// Set Color
circleColor = viewModel.getColor()!!
// or with gradient
circleColorStart = viewModel.getColor()!!
circleColorEnd = viewModel.getColor()!!
circleColorDirection = CircleView.GradientDirection.TOP_TO_BOTTOM
// Set Border
borderWidth = 10f
borderColor = viewModel.getColor()!!
// or with gradient
borderColorStart = viewModel.getColor()!!
borderColorEnd = viewModel.getColor()!!
borderColorDirection = CircleView.GradientDirection.TOP_TO_BOTTOM
// Add Shadow with default param
shadowEnable = true
// or with custom param
shadowRadius = 15f
shadowColor = viewModel.getColor()!!
shadowGravity = CircleView.ShadowGravity.CENTER
}
}
.
. //Do create same circular view methods for other views.
. // If not getting then full code is on github
}
Download Code
Server app: rishiroid/android-aidl_server (github.com)
Client app: rishiroid/android-aidl_client (github.com)