I01 — AIDL calculator
Two processes, one interface: build a calculator service with AIDL, bind to it from a client, and look at the generated Stub and Proxy to see what a Binder call actually does.
AIDL is where Binder becomes something you can read. You write an interface file, the build
generates a Stub and a Proxy, and the two halves of a cross-process call appear in your own
project.
What you are building#
Two apps in one project: a service app that exposes an add and a multiply method, and a client
app that binds to it and calls them. The service runs in its own process, so the calls really do
cross a Binder boundary.
Steps#
- Create the interface
Create an
.aidlfile declaringint add(int a, int b)andint multiply(int a, int b). Put it in the source set that both the service and the client can see; in a two-module project, that is a shared module.1You should seeThe project generates a Java interface from it. - Add a parcelable type
Add a simple
Parcelableresult class and an.aidldeclaration for it. This is where you learn that AIDL only understands primitives, strings, lists, maps and parcelables.You should seeA second generated type. - Implement the service
Extend the generated
Stuband implement the two methods. Return a computed value. Log the calling UID so you can see which process made the call.You should seeThe service compiles with a generated Stub. - Declare the service with an intent filter
Add the service to the manifest with
android:exported="true"and an intent filter, or expose it by explicit component name. For a lab, explicit is simpler and safer.You should seeThe service is discoverable by name. - Bind from the client
Bind with an explicit intent and a
ServiceConnection. InonServiceConnected, cast the binder to the generatedIAddService.Stub.asInterfacetype.You should seeonServiceConnected fires. - Call across the boundary
Call
addand log the result. Then check the client's logcat for the service's log line: the two PIDs are different, which is the proof that the call crossed a process boundary.You should seeThe result comes back from the other process. - Read the generated code
Open the generated files and find
asInterface,asBinder,transactandonTransact. The Proxy writes the method code and the arguments into aParcel; the Stub reads them and calls your implementation.You should seeA Proxy that parcels and a Stub that unparcels. - Add a oneway method
Add a
oneway void log(String message)and observe that the client does not wait. That is the difference between a synchronous transaction and a queued one.You should seeThe call returns immediately.
The interface file is small:
// IAddService.aidl
package com.example.aidlservice;
interface IAddService {
int add(int a, int b);
int multiply(int a, int b);
}The service side implements the generated Stub:
class AddService : Service() {
private val binder = object : IAddService.Stub() {
override fun add(a: Int, b: Int): Int = a + b
override fun multiply(a: Int, b: Int): Int = a * b
}
override fun onBind(intent: Intent): IBinder = binder
}adb shell ps -A | grep -E "aidlservice|aidlclient"
adb logcat -s AidlService:AidlClient
adb shell dumpsys activity services com.example.aidlserviceExample output
Illustrative — PIDs and package names differ.Verification#
The two PIDs in the logs should differ. The service's log line should appear after the client's call,
with the client's UID as the caller. If both PIDs are the same, the service is running in the
client's process: check the android:process attribute and the binding.
What you learned#
- AIDL is the declaration, the generated code is the mechanism. The
Stub/Proxypair is the Binder call in two halves. - A bound service is a client-server relationship, not a background task. The binding determines the lifetime.
- Synchronous and oneway calls have different shapes and different failure modes.
- A process boundary is observable. Two PIDs and a log line are enough to prove it.
Read AIDL for the type rules, and Binder overview for why the transaction is designed this way.
Extend it#
- Add a callback interface so the service can call back into the client, and handle the case where the client dies.
- Add a large payload and observe the transaction size limit; then move the payload to a file descriptor or a shared memory buffer.
- Add a permission to the service declaration and watch the call fail without it.
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsAndroid Interface Definition Language (AIDL) | Background workdeveloper.android.comaccessed 2026-10-27↩
- docsBinder overviewsource.android.comaccessed 2026-10-27↩
- docsServices overview | Background workdeveloper.android.comaccessed 2026-10-27↩
- docsApp manifest overviewdeveloper.android.comaccessed 2026-10-27↩
- docsBinder threadingsource.android.comaccessed 2026-10-27↩