The Danger of Shifting Transaction IDs
Android devices ship with code split across multiple partitions. The system partition contains the core OS framework. The vendor partition holds hardware-specific drivers. Project Treble forces these partitions to update independently. A device might run a vendor partition built for Android 11 alongside a system partition updated to Android 14.
Before Treble, these pieces compiled together. Standard AIDL assigns a sequential transaction ID to every method in an interface. Google sometimes added a new method to a system service interface. This addition shifted the transaction IDs down for all subsequent methods.
A shift breaks any compiled client trying to communicate with that service. The framework calls the wrong method index on the vendor implementation. Mismatched transaction IDs across the partition boundary cause immediate crashes.
The platform team introduced Stable AIDL to fix this. Stable AIDL guarantees that the binary layout and transaction IDs of an interface never change once locked. This explicit contract is mandatory for all Inter-Process Communication crossing the system-vendor boundary.
The following sequence diagram shows how Stable AIDL secures cross-partition communication. This visualization helps you understand why static IDs are critical for backward compatibility. Watch how the updated framework safely calls methods on an older vendor implementation using locked transaction IDs.
Every request includes the exact transaction ID for each method. The vendor implementation receives the expected ID and processes the call correctly. This strict mapping ensures the two partitions always speak the same language.
Evolving Interfaces with Append-Only Versioning
The system and vendor partitions communicate safely across OS updates using this mechanism. Restricting changes entirely is not practical. Platform engineers still need to add new hardware features over time. An interface must evolve without breaking existing clients.
Developers cannot modify the signature of an existing method. You also cannot insert a new method in the middle of the file. Doing either alters the transaction IDs that older clients expect.
Stable AIDL solves this problem through explicit append-only versioning. You append new functionality to the very end of the interface. This placement ensures that all existing transaction IDs remain untouched. Engineers mark an interface as stable by using the @VintfStability annotation in the source file.
You then bump the version number and freeze the API. Existing clients interacting with the older version continue calling the methods they know about. They remain completely unaware that new methods exist below them.
package android.hardware.foo;
@VintfStability
interface IFoo {
// Version 1 method
void doFoo();
// Version 2 method added later
void doBar();
}
The @VintfStability annotation acts as a signal to the compilation toolchain. Writing the AIDL file is only the first step. The build system must generate the actual communication bindings.
Generating Bindings with the Interface Build Rule
A standard shared library rule cannot handle the complexity of cross-partition IPC. The platform needs bindings for Java, C++, and Rust. All three languages must agree exactly on the memory layout and transaction mechanics.
Writing these bindings manually is a massive source of errors. AOSP provides the aidl_interface module type in Android.bp to handle this generation. This build rule takes your versioned AIDL file and automatically generates the client and server stubs for all requested languages simultaneously.
aidl_interface {
name: "android.hardware.foo",
vendor_available: true,
srcs: [
"android/hardware/foo/IFoo.aidl",
],
stability: "vintf",
backend: {
java: {
sdk_version: "module_current",
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
},
}
The rule strictly enforces the stability requirements during compilation. Setting the stability: "vintf" property is the most critical line in this block. This line tells the build system to apply the strictest compatibility checks. Forgetting this tag causes the build system to treat the interface as internal to a single partition. That mistake destroys the cross-version guarantees.
Enforcing the Contract with API Freezes
Human error remains a factor even with the build system generating bindings. A developer might accidentally modify an older method signature instead of appending a new one. The platform needs a mechanical way to catch these mistakes before they merge into the codebase.
The build system computes a cryptographic hash of your .aidl files to prevent accidental modifications. Creating or updating an aidl_interface generates an API dump in an api/ directory. This dump serves as the absolute source of truth for that specific version of the interface.
Platform engineers freeze the API when they finalize an Android release. Any subsequent changes to that versioned interface cause the build system to detect a hash mismatch. The compiler immediately rejects the change and fails the build.
Engineers manage this state using two specific build commands. The first command aligns the build system with your local edits. Running the second command locks those edits permanently.
# Update the current API dump to match your edits
m android.hardware.foo-update-api
# Freeze the API version permanently
m android.hardware.foo-freeze-api
Common Mistake: Developers often try to fix a typo in a frozen AIDL file. The build system rejects this edit because fixing a typo changes the file hash. You must leave the typo in the frozen version and fix it in a new version.
Once frozen, the build system locks that specific version of the interface forever. You must create a new version of the interface and append your updates there if you need to make changes. This rigid system guarantees that a device shipped today will communicate safely with a system partition compiled five years from now. But defining the interface is only half the battle. We still have to explore how the system actually discovers and registers these hardware services during boot.