AIDL API guidelines

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 List of parcelables to represent maps, as AIDL does not natively support Map types that safely translate across all native backends (for example, FeatureToScoreEntry[]).
  • Use arrays of parcelable objects for repeated fields rather than arrays of primitives, to prevent the need for parallel arrays in the future.
  • Use strongly typed parcelable objects instead of serialized strings or JSON over IPC.
  • Use enums instead of booleans for states to allow for future expansion. For bitmasks, use const int rather than enum types 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.