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 |
- These API surfaces are stable, but many of the APIs, such as those for service management, are reserved for internal platform use and are not available to apps. For more information on how to use AIDL in apps, see developer documentation.
The NDK backend is new in Android 10.
Build systems
Depending on the backend, there are two different ways to compile AIDL into stub code. More detailed documentation on the build systems can be seen in 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 automatically to the module. 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. For more details, see specific documentation on the build system in the
Soong Module Reference.
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 cannot be used.
Types
The aidl
compiler can be considered a reference implementation for types. When
creating an interface, you can see the resulting interface file by invoking
aidl --lang=<backend> ...
. When using the aidl_interface
module, the output
can also be seen in out/soong/.intermediates/<path to module>/
.
Java/AIDL Type | C++ Type | NDK Type |
---|---|---|
boolean | bool | bool |
byte | int8_t | int8_t |
char | char16_t | char16_t |
int | int32_t | int32_t |
long | int64_t | int64_t |
float | float | float |
double | double | double |
String | android::String16 | std::string |
android.os.Parcelable | android::Parcelable | N/A |
IBinder | android::IBinder | ndk::SpAIBinder |
T[] | std::vector<T> | std::vector<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t>1 |
List<T> | vector<T>2 | N/A |
FileDescriptor | android::base::unique_fd | N/A |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor |
interface type (T) | android::sp<T> | std::shared_ptr<T> |
parcelable type (T) | T | T |
union type (T)3 | T | T |
1. In Android S (AOSP experimental) or higher, byte arrays use uint8_t instead of int8_t for compatibility reasons.
2. The C++ backend only supports List<String>
and List<IBinder>
. In
general, it is recommended to use array types like T[]
, since they work in
all backends.
3. Union types are supported in Android S (AOSP experimental) or higher.
UTF8/UTF16
The C++ backend lets you to 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 backend always uses utf-8 strings. For more information about the
utf8InCpp
annotation, see
Annotations in AIDL.
Nullability
For types which can be null in Java, they can be annotated with @nullable
to
expose null values to C++/NDK. By default native servers reject null values.
The only exceptions to this are interface and IBinder types which can always be
null. 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 is implemented manually in a target backend (in C++ or in Java).
package my.package.Foo;
parcelable Foo;
or with C++ header declaration:
package my.package.Foo;
parcelable Foo cpp_header "my/package/Foo.h";
Then, you can use this parcelable as a type in AIDL files, but it will not be generated by AIDL.
Exceptions
In Java, exceptions are used to propagate errors between services, such as
android.os.RemoteException
. However, native code in Android doesn't use
exceptions, so the CPP backend uses android::Status
, and the NDK backend uses
ndk::ScopedAStatus
to represent the exceptions that a method can
return. Every method generated by AIDL returns one of these, representing the
status of the method which is returned.
Using various backends
These instructions are specific to Android platform code. These examples use a
defined type, my.package.IFoo
.
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>
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;
}
Registering and getting services
Services in platform Android are usually registered with the servicemanager
process. In addition to the APIs below, there are some APIs which 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);
// getting
myService = IFoo.Stub.asInterface(ServiceManager.getService("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);
// getting
status_t err = getService<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");
// getting
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
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 C++ 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;
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 backend doesn'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
Thread management
Every instance of libbinder in a process maintains one threadpool. For most
usecases, 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 C++ 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();