An AIDL backend is a target for stub code generation. When using AIDL files, you always use them in a particular language with a specific runtime. Depending on the context, you should use different AIDL backends.
AIDL has the following backends:
Backend | Language | API surface | Build systems |
---|---|---|---|
Java | Java | SDK/SystemApi (stable*) | all |
NDK | C++ | libbinder_ndk (stable*) | aidl_interface |
CPP | C++ | libbinder (unstable) | all |
Rust | Rust | libbinder_rs (unstable) | aidl_interface |
- These API surfaces are stable, but many of the APIs, such as those for service management, are reserved for internal platform use and aren't available to apps. For more information on how to use AIDL in apps, see developer documentation.
- The Rust backend was introduced in Android 12; the NDK backend has been available as of Android 10.
- The Rust crate is built on top of
libbinder_ndk
. APEXes use the binder crate the same way as does anyone else on the system side. The Rust portion is bundled into an APEX and shipped inside it. It depends on thelibbinder_ndk.so
on the system partition.
Build systems
Depending on the backend, there are two ways to compile AIDL into stub code. For more details on the build systems, see the Soong Module Reference.
Core build system
In any cc_
or java_
Android.bp module (or in their Android.mk
equivalents),
.aidl
files can be specified as source files. In this case, the Java/CPP
backends of AIDL are used (not the NDK backend), and the classes to use the
corresponding AIDL files are added to the module automatically. Options
such as local_include_dirs
, which tells the build system the root path to
AIDL files in that module can be specified in these modules under an aidl:
group. Note that the Rust backend is only for use with Rust. rust_
modules are
handled differently in that AIDL files aren’t specified as source files.
Instead, the aidl_interface
module produces a rustlib
called
<aidl_interface name>-rust
which can be linked against. For more details, see
the Rust AIDL example.
aidl_interface
See Stable AIDL. Types used with this build system must be structured; that is, expressed in AIDL directly. This means that custom parcelables can't be used.
Types
You can consider the aidl
compiler as a reference implementation for types. When
you create an interface, invoke aidl --lang=<backend> ...
to see the resulting
interface file. When you use the aidl_interface
module, you can view the output
in out/soong/.intermediates/<path to module>/
.
Java/AIDL Type | C++ Type | NDK Type | Rust Type |
---|---|---|---|
boolean | bool | bool | bool |
byte | int8_t | int8_t | i8 |
char | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | i64 |
float | float | float | f32 |
double | double | double | f64 |
String | android::String16 | std::string | String |
android.os.Parcelable | android::Parcelable | N/A | N/A |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | In: &[T] Out: Vec<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t>1 | In: &[u8] Out: Vec<u8> |
List<T> | std::vector<T>2 | std::vector<T>3 | In: &[T]4 Out: Vec<T> |
FileDescriptor | android::base::unique_fd | N/A | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
interface type (T) | android::sp<T> | std::shared_ptr<T> | binder::Strong |
parcelable type (T) | T | T | T |
union type (T)5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. In Android 12 or higher, byte arrays use uint8_t instead of int8_t for compatibility reasons.
2. The C++ backend supports List<T>
where T
is one of String
,
IBinder
, ParcelFileDescriptor
or parcelable. In Android
13 or higher, T
can be any non-primitive type
(including interface types) except arrays. AOSP recommends that you
use array types like T[]
, since they work in all backends.
3. The NDK backend supports List<T>
where T
is one of String
,
ParcelFileDescriptor
or parcelable. In Android 13
or higher, T
can be any non-primitive type except arrays.
4. Types are passed differently for Rust code depending on whether they are input (an argument), or an output (a returned value).
5. Union types are supported in Android 12 and higher.
6. In Android 13 or higher, fixed-size arrays are
supported. Fixed-size arrays can have multiple dimensions (e.g. int[3][4]
).
In the Java backend, fixed-size arrays are represented as array types.
Directionality (in/out/inout)
When specifying the types of the arguments to functions, you can specify
them as in
, out
, or inout
. This controls in which direction information is
passed for an IPC call. in
is the default direction, and it indicates data is
passed from the caller to the callee. out
means that data is passed from the
callee to the caller. inout
is the combination of both of these. However, the
Android team recommends that you avoid using the argument specifier inout
.
If you use inout
with a versioned interface and an older callee, the
additional fields that are present only in the caller get reset to their default
values. With respect to Rust, a normal inout
type receives &mut Vec<T>
, and
a list inout
type receives &mut Vec<T>
.
UTF8/UTF16
With the CPP backend you can choose whether strings are utf-8 or utf-16. Declare
strings as @utf8InCpp String
in AIDL to automatically convert them to utf-8.
The NDK and Rust backends always uses utf-8 strings. For more information about
the utf8InCpp
annotation, see Annotations in AIDL.
Nullability
You can annotate types that can be null in the Java backend with @nullable
to expose null values to the CPP and NDK backends. In the Rust backend these
@nullable
types are exposed as Option<T>
. Native servers reject null values
by default. The only exceptions to this are interface
and IBinder
types,
which can always be null for NDK reads and CPP/NDK writes. For more information
about the nullable
annotation, see
Annotations in AIDL.
Custom Parcelables
In the C++ and Java backends in the core build system, you can declare a parcelable that's implemented manually in a target backend (in C++ or in Java).
package my.package;
parcelable Foo;
or with C++ header declaration:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
Then you can use this parcelable as a type in AIDL files, but it won't be generated by AIDL.
Rust doesn't support custom parcelables.
Default values
Structured parcelables can declare per-field default values for primitives,
String
s, and arrays of these types.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
In the Java backend when default values are missing, fields are initialized as
zero values for primitive types and null
for non-primitive types.
In other backends, fields are initialized with default initialized values when
default values are not defined. For example, in the C++ backend, String
fields
are initialized as an empty string and List<T>
fields are initialized as an
empty vector<T>
. @nullable
fields are initialized as null-value fields.
Error Handling
The Android OS provides built-in error types for services to use when reporting errors. These are used by binder and can be used by any services implementing a binder interface. Their use is well-documented in the AIDL definition and they don't require any user-defined status or return type.
Output parameters with errors
When an AIDL function reports an error, the function may not initialize or
modify output parameters. Specifically, output parameters may be modified if the
error occurs during unparceling as opposed to happening during the processing
of the transaction itself. In general, when getting an error from an AIDL
function, all inout
and out
parameters as well as the return value (which
acts like an out
parameter in some backends) should be considered to be in
an indefinite state.
Which error values to use
Many of the built-in error values can be used in any AIDL interfaces, but some
are treated in a special way. For example, EX_UNSUPPORTED_OPERATION
and
EX_ILLEGAL_ARGUMENT
are OK to use when they describe the error condition, but
EX_TRANSACTION_FAILED
must not be used because it is treated special by the
underlying infrastructure. Check the backend specific definitions for more
information on these built-in values.
If the AIDL interface requires additional error values that aren't covered by
the built-in error types, then they may use the special service-specific built-in
error that allows the inclusion of a service-specific error value that's
defined by the user. These service-specific errors are typically defined in the
AIDL interface as a const int
or int
-backed enum
and aren't parsed by
binder.
In Java, errors map to exceptions, such as android.os.RemoteException
. For
service-specific exceptions, Java uses android.os.ServiceSpecificException
along with the user-defined error.
Native code in Android doesn't use exceptions. The CPP backend uses
android::binder::Status
. The NDK backend uses ndk::ScopedAStatus
. Every
method generated by AIDL returns one of these, representing the status of the
method. The Rust backend uses the same exception code values as the NDK, but
converts them into native Rust errors (StatusCode
, ExceptionCode
) before
delivering them to the user. For service-specific errors, the returned
Status
or ScopedAStatus
uses EX_SERVICE_SPECIFIC
along with the
user-defined error.
The built-in error types can be found in the following files:
Backend | Definition |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
Using various backends
These instructions are specific to Android platform code. These examples use a
defined type, my.package.IFoo
. For instructions on how to use the Rust backend,
see the Rust AIDL example
on the Android Rust Patterns
page.
Importing types
Whether the defined type is an interface, parcelable, or union, you can import it in Java:
import my.package.IFoo;
Or in the CPP backend:
#include <my/package/IFoo.h>
Or in the NDK backend (notice the extra aidl
namespace):
#include <aidl/my/package/IFoo.h>
Or in the Rust backend:
use my_package::aidl::my::package::IFoo;
Although you can import a nested type in Java, in the CPP/NDK backends you must
include the header for its root type. For example, when importing a nested type
Bar
defined in my/package/IFoo.aidl
(IFoo
is the root type of the
file) you must include <my/package/IFoo.h>
for the CPP backend (or
<aidl/my/package/IFoo.h>
for the NDK backend).
Implementing services
To implement a service, you must inherit from the native stub class. This class reads commands from the binder driver and executes the methods that you implement. Imagine that you have an AIDL file like this:
package my.package;
interface IFoo {
int doFoo();
}
In Java, you must extend from this class:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
In the CPP backend:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
In the NDK backend (notice the extra aidl
namespace):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
In the Rust backend:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
impl IFoo for MyFoo {
fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Registering and getting services
Services in platform Android are usually registered with the servicemanager
process. In addition to the APIs below, some APIs check the
service (meaning they return immediately if the service isn't available).
Check the corresponding servicemanager
interface for exact details. These
operations can only be done when compiling against platform Android.
In Java:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// return if service is started now
myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
// waiting until service comes up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));
In the CPP backend:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// return if service is started now
status_t err = checkService<IFoo>(String16("service-name"), &myService);
// waiting until service comes up (new in Android 11)
myService = waitForService<IFoo>(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = waitForDeclaredService<IFoo>(String16("service-name"));
In the NDK backend (notice the extra aidl
namespace):
#include <android/binder_manager.h>
// registering
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_checkService("service-name")));
// is a service declared in the VINTF manifest
// VINTF services have the type in the interface instance name.
bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
// wait until a service is available (if isDeclared or you know it's available)
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));
In the Rust backend:
use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Does not return - spawn or perform any work you mean to do before this call.
binder::ProcessState::join_thread_pool()
}
Linking to death
You can request to get a notification for when a service hosting a binder dies. This can help to avoid leaking callback proxies or assist in error recovery. Make these calls on binder proxy objects.
- In Java, use
android.os.IBinder::linkToDeath
. - In the CPP backend, use
android::IBinder::linkToDeath
. - In the NDK backend, use
AIBinder_linkToDeath
. - In the Rust backend, create a
DeathRecipient
object, then callmy_binder.link_to_death(&mut my_death_recipient)
. Note that because theDeathRecipient
owns the callback, you must keep that object alive as long as you want to receive notifications.
Caller Information
When receiving a kernel binder call, caller information is available in several APIs. The PID (or Process ID) refers to the Linux process ID of the process which is sending a transaction. The UID (or User ID) refers to the Linux user ID. When receiving a oneway call, the calling PID is 0. When outside of a binder transaction context, these functions return the PID and UID of the current process.
In the Java backend:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
In the CPP backend:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
In the NDK backend:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Bug reports and debugging API for services
When bugreports run (for example, with adb bugreport
), they collect
information from all around the system to aid with debugging various issues.
For AIDL services, bugreports use the binary dumpsys
on all services
registered with the service manager to dump their information into the
bugreport. You can also use dumpsys
on the commandline to get information
from a service with dumpsys SERVICE [ARGS]
. In the C++ and Java backends, you
can control the order in which services get dumped by using additional arguments
to addService
. You can also use dumpsys --pid SERVICE
to get the PID of a
service while debugging.
To add custom output to your service, you can override the dump
method in your server object like you are implementing any other IPC method
defined in an AIDL file. When doing this, you should restrict dumping to the app
permission android.permission.DUMP
or restrict dumping to specific UIDs.
In the Java backend:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
In the CPP backend:
status_t dump(int, const android::android::Vector<android::String16>&) override;
In the NDK backend:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Dynamically getting interface descriptor
The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.
In Java, you can get the interface descriptor with code such as:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
In the CPP backend:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
The NDK and Rust backends don't support this functionality.
Statically getting interface descriptor
Sometimes (such as when registering @VintfStability
services), you need to
know what the interface descriptor is statically. In Java, you can get the
descriptor by adding code such as:
import my.package.IFoo;
... IFoo.DESCRIPTOR
In the CPP backend:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
In the NDK backend (notice the extra aidl
namespace):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
In the Rust backend:
aidl::my::package::BnFoo::get_descriptor()
Enum Range
In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java currently.
For an enum MyEnum
defined in AIDL, iteration is provided as follows.
In the CPP backend:
::android::enum_range<MyEnum>()
In the NDK backend:
::ndk::enum_range<MyEnum>()
In the Rust backend:
MyEnum::enum_range()
Thread management
Every instance of libbinder
in a process maintains one threadpool. For most
use cases, this should be exactly one threadpool, shared across all backends. The
only exception to this is when vendor code might load another copy of libbinder
to talk to /dev/vndbinder
. Since this is on a separate binder node, the
threadpool isn't shared.
For the Java backend, the threadpool can only increase in size (since it is already started):
BinderInternal.setMaxThreads(<new larger value>);
For the CPP backend, the following operations are available:
// set max threadpool count (default is 15)
status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
// create threadpool
ProcessState::self()->startThreadPool();
// add current thread to threadpool (adds thread to max thread count)
IPCThreadState::self()->joinThreadPool();
Similarly, in the NDK backend:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
In the Rust backend:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();
Reserved Names
C++, Java, and Rust reserve some names as keywords or for language-specific
use. While AIDL doesn't enforce restrictions based on language rules, using
field or type names that matching a reserved name might result in a compilation
failure for C++ or Java. For Rust, the field or type is renamed using the
"raw identifier" syntax, accessible using the r#
prefix.
We recommend that you avoid using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.
If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible; you may need to update your code to continue building, but any already built programs will continue to interoperate.
Names to avoid: * C++ Keywords * Java Keywords * Rust Keywords