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 an API or interact with API surfaces.
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. 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.
For more details and information about the type of changes that are allowed, see Versioning interfaces.
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
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. More detailed information is in the
Error handling section of AIDL
backends.
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.
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.
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;