The best practices outlined here serve as a guide to developing AIDL interfaces effectively and with attention to flexibility of the interface, particularly when AIDL is used to define a stable, backward-compatible API.
AIDL can be used to define an API when apps need to interface with each other in a background process or need to interface with the system.
Stable AIDL with @VintfStability is used for HAL interfaces and allows clients
and servers to be updated independently. This requires backward
compatibility and structured data.
For more information about developing programming interfaces in apps with AIDL, see Android Interface Definition Language (AIDL). For examples of AIDL in practice, see AIDL for HALs and Stable AIDL.
Versioning
Every backward-compatible snapshot of an AIDL API corresponds to a version.
To take a snapshot, run m <module-name>-freeze-api. Whenever a client or
server of the API is released (for example, in a Mainline train), you need to
take a snapshot and make a new version. For system-to-vendor APIs, this should
happen with the yearly platform revision.
When an interface is frozen (saved in the versioned aidl_api directory), it
must never be modified. You can edit only the current directory. You can
safely add methods to the end of an interface, fields to the end of a
parcelable, enumerators to an enum, and members to a union.
Clients calling new methods on older servers receive an
UNKNOWN_TRANSACTION error, which should be gracefully handled by the client.
For more details and information about the type of changes that are allowed, see Versioning interfaces.
Build dependencies
Android modules can't depend on multiple, different versions of the generated
libraries from an aidl_interface. The different versions of the libraries
define the same types in the same namespaces. Android's aidl build system
identifies this problem and throws an error with each of the dependency graphs
that end in the mismatched versions of the libraries.
This can make it difficult to update one version of a common interface when a module contains many dependencies with their own dependencies.
Developers can use aidl_interface_defaults to declare a shared
interface's dependencies on other interfaces so they don't all need to be
updated independently.
We recommend using *_defaults modules (like rust_defaults,
cc_defaults, java_defaults) to organize the
dependencies on the generated libraries. It's common to have a default for the
latest version of the interfaces as well as defaults for previous versions
if they are still used.
Developers can make use of aidl_interface_defaults to declare a shared
interface's dependencies on other interfaces so they don't all need to be
updated independently.
API design guidelines
General
1. Document everything
- Document every method for its semantics, arguments, use of built-in exceptions, service specific exceptions, and return value.
- Document every interface for its semantics.
- Document the semantic meaning of enums and constants.
- Document whatever might be unclear to an implementer.
- Provide examples where relevant.
2. Casing
Use upper camel casing for types and lower camel casing for methods, fields and
arguments. For example, MyParcelable for a parcelable type and anArgument
for an argument. For acronyms, consider the acronym a word (NFC -> Nfc).
[-Wconst-name] Enum values and constants should be ENUM_VALUE and
CONSTANT_NAME
3. Avoid requiring global knowledge
APIs shouldn't assume developers have global knowledge of the entire codebase or specific domain expertise. When dealing with domain-specific identifiers (like device names, IDs, or handles):
- Be explicit and document where these identifiers come from and their format if it's important for both sides of the interface to know them.
- Alternatively, use interface-specific identifiers (such as binder objects or custom tokens) and have one side manage the mapping to the underlying values. This reduces collisions and avoids requiring users to understand implementation details outside their area.
4. All data is structured and backward compatible
Unstructured data like string, byte[], and shared memory must have a stable
format for their contents, or be opaque to one side of the interface.
For example, a string argument used as an error message for a result can be
received and logged for debugging, but it must not be parsed and interpreted
because the format and contents might not be backward
compatible. If the other side of the interface needs to know what the error is
at run time, use an enum, constant, or ServiceSpecificException.
Similarly, don't serialize objects into byte[] or shared memory unless they
are stable and backward compatible. In some cases, you can use the @FixedSize
annotation for sharing parcelables and unions in shared memory and Fast Message
Queues.
Interfaces
1. Naming
[-Winterface-name] An interface name should start with I like IFoo.
2. Avoid big interface with id-based "objects"
Prefer subinterfaces when there are many calls related to a specific API. This provides the following benefits:
- Makes client or server code easier to understand
- Makes the lifecycle of objects simpler
- Takes advantage of binders being unforgeable.
Not recommended: A single, large interface with ID-based objects
interface IManager {
int getFooId();
void beginFoo(int id); // clients in other processes can guess an ID
void opFoo(int id);
void recycleFoo(int id); // ownership not handled by type
}
Recommended: Individual interfaces
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. Don't mix one-way with two-way methods
[-Wmixed-oneway] Don't mix one-way with non-oneway methods, because it makes understanding the threading model complicated for clients and servers. Specifically, when reading client code of a particular interface, you need to look up for each method if that method will block or not.
4. Avoid returning status codes
Methods should avoid status codes as return values, since all AIDL methods have
an implicit status return code. See ServiceSpecificException or
EX_SERVICE_SPECIFIC. By convention, these values are defined as constants in
an AIDL interface. If a custom delay or unique error data is needed alongside
an error, that is the only time a custom response object should represent an
error. For more detailed information, see
Error handling.
5. Arrays as output parameters considered harmful
[-Wout-array] Methods having array output parameters, like
void foo(out String[] ret) are usually bad because the output array size must
be declared and allocated by the client in Java, and so the size of the array
output cannot be chosen by the server. This undesirable behavior happens because
of how arrays work in Java (they cannot be reallocated). Instead prefer APIs
like String[] foo().
6. Avoid inout parameters
[-Winout-parameter] This can confuse clients because even in parameters look
like out parameters.
7. Avoid out and inout @nullable non-array parameters
[-Wout-nullable] Since the Java backend doesn't handle @nullable annotation
while other backends do, out/inout @nullable T may lead inconsistent behavior
across backends. For example, non-Java backends can set an out @nullable
parameter to null (in C++, setting it as std::nullopt) but the Java client
can't read it as null.
8. Use unique requests and responses
Group all necessary parameters into a single input parcelable.
Create dedicated request and response parcelables for every interface method
instead of passing primitives (for example, use
ComputeResponse compute(in ComputeRequest request) instead of passing separate
variables). This allows new arguments to be added later without changing the
function signature. This pattern is strongly suggested when it is expected that
more parameters might be added in the future, or if a method already has more
than four parameters.
Methods that don't require additional inputs or outputs won't benefit from this suggestion. Explicitly thinking about each case and remaining flexible for future changes can lead to fewer deprecated methods and less complexity for backward-compatible code.
If a method wasn't created using this pattern, you can switch to this pattern by creating a new method with a request and response parcelable and deprecating the old method. For example:
void foo(int a, int b, int c); // original version, but deprecated in favor of the next version
void fooV2(in MyArg arg); // new version having int a, b, c, and d.
Structured parcelables
1. When to use
Use structured parcelables where you have multiple data types to send.
Or, when you have a single data type but you expect that you will need
to extend it in the future. For example, don't use String username. Use an
extendable parcelable, like the following:
parcelable User {
String username;
}
So that, in the future, you can extend it, as follows:
parcelable User {
String username;
int id;
}
2. Provide defaults explicitly
[-Wexplicit-default, -Wenum-explicit-default] Provide explicit defaults for fields. When new fields are added to a parcelable, old clients and servers drop them, but default values automatically fill in for new clients and servers.
3. Use ParcelableHolder for vendor extensions
If you define an AOSP parcelable that device implementers need to extend,
embed an instance of ParcelableHolder in your object. This acts as an
extension point without creating merge conflicts. This is similar to the
attached interface extensions
but allows implementers to include their proprietary parcelable alongside the
existing parcelable without creating their own interface and types.
4. Data structures
- Use arrays or
Listof parcelables to represent maps, as AIDL does not natively supportMaptypes that safely translate across all native backends (for example,FeatureToScoreEntry[]). - Use arrays of
parcelableobjects for repeated fields rather than arrays of primitives, to prevent the need for parallel arrays in the future. - Use strongly typed
parcelableobjects instead of serialized strings or JSON over IPC. - Use enums instead of booleans for states to allow for future expansion. For
bitmasks, use
const intrather thanenumtypes to avoid cumbersome casting in some backends.
Nonstructured parcelables
1. When to use
Nonstructured parcelables are available in Java with
@JavaOnlyStableParcelable and in the NDK backend with
@NdkOnlyStableParcelable. Usually, these are old and existing parcelables
that can't be structured.
Constants and enums
1. Bitfields should use constant fields
Bitfields should use constant fields (for example, const int FOO = 3; in an
interface).
2. Enums should be closed sets.
Enums should be closed sets. Note: only the interface owner can add enum elements. If vendors or OEMs need to extend these fields, an alternative mechanism is needed. Whenever possible, upstreaming vendor functionality should be preferred. However, in some cases, custom vendor values may be allowed through (though, vendors should have a mechanism in place to version this, perhaps AIDL itself, they shouldn't be able to conflict with each other, and these values shouldn't be exposed to 3rd party apps).
3. Avoid values like "NUM_ELEMENTS"
Since enums are versioned, values which indicate how many values are present
should be avoided. In C++, this can be worked around with, enum_range<>. For
Rust, use enum_values(). In Java, there's no solution yet.
Not recommended: Using numbered values
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Avoid redundant prefixes and suffixes
[-Wredundant-name] Avoid redundant or repetitive prefixes and suffixes in constants and enumerators.
Not recommended: Using a redundant prefix
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Recommended: Directly naming the enum
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] The use of FileDescriptor as an argument or the return
value of an AIDL interface method is highly discouraged. Especially, when the
AIDL is implemented in Java, this might cause file descriptor leak unless
carefully handled. Basically, if you accept a FileDescriptor, you need to
close it manually when it is no longer used.
For native backends, you are safe because FileDescriptor maps to unique_fd
which is auto-closeable. But regardless of the backend language you would use,
it is wise to NOT use FileDescriptor at all because this will limit your
freedom to change the backend language in the future.
Instead, use ParcelFileDescriptor, which is autocloseable.
Variable units
Make sure that variable units are included in the name so that their units are well-defined and understood without needed to reference documentation
Examples
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
Timestamps must indicate their reference
Timestamps (in fact, all units!) must clearly indicate their units and reference points.
Examples
/**
* Time since device boot in milliseconds
*/
long timestampMs;
/**
* UTC time received from the NTP server in units of milliseconds
* since January 1, 1970
*/
long utcTimeMs;
Concurrency and asynchronous operations
Handle long-running operations with an asynchronous (oneway)
interface to avoid blocking.
If a service doesn't trust its clients, any callbacks it receives from the
clients should be oneway interfaces. This prevents the clients from being able
to block the service indefinitely.
Structure asynchronous APIs consisting of a forward call, input arguments, and a callback interface to get results. See Use unique requests and responses for recommendations for arguments.