Android's security model enforces strict application sandboxing — each app runs in its own process with its own memory space. By design, one application cannot read or write another application's memory directly. This isolation is fundamental to Android security.
But sometimes, two applications genuinely need to talk to each other. A music player service might need to expose playback controls to a companion app. A sensor management service might need to provide real-time data to multiple client apps. A system service might need to offer a structured API to third-party apps.
This is the problem that AIDL solves.
What is AIDL?
AIDL stands for Android Interface Definition Language. It is a lightweight IPC (Inter-Process Communication) mechanism that allows an Android application to expose a structured, type-safe interface to clients running in different processes.
+-------------------------+ Binder IPC +-------------------------+
| Client App |<------------------>| Server App |
| (Process A) | | (Process B) |
| | | |
| myService.getData(1) |----[marshalling]--->| getData() handler |
| | | |
| result = "Data" |<--[unmarshalling]--| return "Data for 1" |
+-------------------------+ +-------------------------+
AIDL uses syntax similar to Java and Kotlin to define the interface. The Android build system auto-generates the boilerplate code — the Stub (server side) and the Proxy (client side) — that handles the marshalling and unmarshalling of method calls across process boundaries.
How AIDL Works: The Big Picture
Before writing any code, it helps to understand the overall architecture:
Server Side Client Side
----------- -----------
1. Define AIDL interface 1. Copy or share the AIDL file
2. Build project (generates Stub) 2. Bind to the service
3. Implement the Stub 3. Get Proxy via asInterface()
4. Return Stub from onBind() 4. Call methods on Proxy
5. Declare service in Manifest 5. Results return across IPC
The Stub lives on the server. It receives raw IPC data and dispatches it to your implementation.
The Proxy lives on the client. It takes your method calls and serializes them for IPC.
Both are generated automatically — you only implement the actual logic.
Creating an AIDL Interface: Server Side
Step 1: Define the AIDL Interface
In your Android project, under src/main, create an aidl directory at the same level as the java directory. Inside it, create a package structure matching your app's package name (e.g., com.example.myapp), then create an .aidl file:
// IMyService.aidl
package com.example.myapp;
interface IMyService {
String getData(int id);
void setData(int id, String data);
}
AIDL syntax is intentionally Java-like. You declare methods in the interface, and AIDL handles the serialization. Supported parameter types include:
- Java primitives (
int,long,boolean,float,double,byte,char) StringandCharSequenceListandMap(with elements of supported types)- Custom
Parcelableobjects (requires a separate.aidldeclaration)
Step 2: Build the Project
When you build, Android Studio generates a Java interface from the .aidl file. This generated code (in the gen directory) contains the Stub abstract class (for your service to extend) and the Proxy class (for clients to use). You never edit this generated code directly.
Step 3: Implement the Bound Service
Create a service class that implements the generated Stub:
public class MyService extends Service {
// Implement the AIDL interface using the generated Stub
private final IMyService.Stub binder = new IMyService.Stub() {
@Override
public String getData(int id) throws RemoteException {
return "Data for ID: " + id;
}
@Override
public void setData(int id, String data) throws RemoteException {
// Store or process the data
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
}
The binder object is your implementation. When a client calls getData(1) across processes, AIDL routes the call through Binder IPC to this method.
Step 4: Declare the Service in the Manifest
Your service must be declared in AndroidManifest.xml with an intent-filter so clients can discover and bind to it:
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.myapp.IMyService" />
</intent-filter>
</service>
The android:exported="true" attribute is what allows clients from other apps to bind to this service. Without it, only components within the same app can bind.
Binding to the Service: Client Side
Step 1: Bind to the Service
In the client application (a different app or a different process), use bindService() with a ServiceConnection to establish the IPC connection:
private IMyService myService = null;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// Convert the raw IBinder to the typed AIDL interface
myService = IMyService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
myService = null;
}
};
private void bindToService() {
Intent intent = new Intent();
intent.setAction("com.example.myapp.IMyService");
intent.setPackage("com.example.myapp");
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
The critical line is IMyService.Stub.asInterface(service). This converts the raw IBinder returned by the service's onBind() into the typed IMyService proxy interface, handling whether the call is in-process (returns the Stub directly) or cross-process (wraps it in a Proxy).
Step 2: Make Remote Calls
Once bound (onServiceConnected has been called), you interact with the remote service exactly as if calling local methods — except that you must catch RemoteException:
if (myService != null) {
try {
String result = myService.getData(1);
myService.setData(1, "Updated Data");
} catch (RemoteException e) {
e.printStackTrace();
}
}
RemoteException is thrown when the remote process has died or the connection has been lost. Always handle it gracefully.
Step 3: Unbind When Done
Unbind from the service when your component is destroyed to prevent resource leaks:
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
Full Implementation Flow
SERVER SIDE CLIENT SIDE
----------- -----------
Define .aidl file
|
v
Build project
(generates Stub + Proxy)
|
v
Implement Stub
in MyService.onBind()
|
v
Declare in Manifest ---------> bindService()
|
v
onServiceConnected()
Stub.asInterface(binder)
|
v
Call interface methods
(getData / setData)
|
v
unbindService() on destroy
Important Considerations
Threading
AIDL methods run on the service's Binder thread pool, not the main thread. If your implementation does heavy computation or blocking I/O, move that work to a background thread. Blocking the Binder thread can cause the calling client to hang.
@Override
public String getData(int id) throws RemoteException {
// Heavy work should be dispatched to a background thread
// Return a result or use a callback mechanism
return processData(id);
}
Data Types
AIDL supports passing custom objects across processes, but those objects must implement the Parcelable interface (Android's serialization mechanism optimized for IPC). You also need to declare the Parcelable class in its own .aidl file.
// User.aidl (declares Parcelable to AIDL)
package com.example.myapp;
parcelable User;
Security
When android:exported="true" is set, any app on the device can attempt to bind to your service. Always validate callers:
- Check the calling package name using
getCallingUid()andPackageManager - Use Android permissions — declare a
uses-permissionrequirement in the manifest and check it in the service - Never expose sensitive operations to unverified callers
AIDL vs. Other IPC Mechanisms
| Mechanism | Cross-Process | Type Safety | Complexity | Best For |
|---|---|---|---|---|
| AIDL | Yes | Strong | Medium | Structured cross-app APIs |
| Messenger | Yes | Limited | Low | Simple message passing |
| ContentProvider | Yes | Medium | Medium | Structured data sharing |
| Broadcast | Yes | None | Low | One-way event broadcasting |
| Binder (direct) | Yes | Custom | High | Advanced system-level IPC |
AIDL is the right choice when you need a typed, method-call-style API between processes — especially when multiple clients need to call multiple methods with different parameters and return values.
Summary
- AIDL enables type-safe inter-process communication between Android apps
- The server defines an
.aidlinterface file; the build system generates theStubandProxyboilerplate - The server implements the
Stub; the client usesStub.asInterface()to get aProxy - AIDL methods run on the Binder thread — never block the calling thread
- Always handle
RemoteExceptionon the client side - Protect exported services with permission checks
AIDL is one of Android's more advanced concepts, but it is the foundation of how Android's own system services expose APIs to apps. Understanding it gives you deep insight into how the Android platform itself is architected.