Серверная часть AIDL является целью для генерации кода-заглушки. Всегда используйте файлы AIDL на определенном языке с определенной средой выполнения. В зависимости от контекста вам следует использовать разные серверные части AIDL.
В следующей таблице стабильность поверхности API означает возможность компилировать код с этой поверхностью API таким образом, чтобы код мог быть доставлен независимо от двоичного файла system.img
libbinder.so
.
AIDL имеет следующие серверные части:
Бэкэнд | Язык | поверхность API | Системы сборки |
---|---|---|---|
Ява | Ява | SDK или SystemApi (стабильная*) | Все |
НДК | С++ | libbinder_ndk (стабильная*) | aidl_interface |
CPP | С++ | libbinder (нестабильный) | Все |
Ржавчина | Ржавчина | libbinder_rs (стабильная*) | aidl_interface |
- Эти поверхности API стабильны, но многие из API, например для управления службами, зарезервированы для внутреннего использования платформы и недоступны приложениям. Дополнительные сведения о том, как использовать AIDL в приложениях, см. в разделе Язык определения интерфейса Android (AIDL) .
- Бэкэнд Rust был представлен в Android 12; Серверная часть NDK доступна начиная с Android 10.
- Крейт Rust построен на основе
libbinder_ndk
, что делает его стабильным и переносимым. APEX использует ящик для подшивок стандартным образом на стороне системы. Часть Rust упакована в APEX и поставляется внутри него. Эта часть зависит отlibbinder_ndk.so
в системном разделе.
Системы сборки
В зависимости от серверной части существует два способа компиляции AIDL в код-заглушку. Более подробную информацию о системах сборки см. в разделе «Справочник по модулям Soong» .
Основная система сборки
В любом Android.bp module
cc_
или java_
Android.bp (или их эквивалентах Android.mk
) вы можете указать файлы AIDL ( .aidl
) в качестве исходных файлов. В этом случае используются серверные части AIDL Java или CPP (а не серверная часть NDK), а классы для использования соответствующих файлов AIDL добавляются в модуль автоматически. Вы можете указать такие параметры, как local_include_dirs
(который сообщает системе сборки корневой путь к файлам AIDL в этом модуле) в этих модулях в группе aidl:
:.
Бэкэнд Rust предназначен только для использования с Rust. Модули rust_
обрабатываются по-другому: файлы AIDL не указываются в качестве исходных файлов. Вместо этого aidl_interface
создает rustlib
с именем aidl_interface_name -rust
, с которым можно скомпоноваться. Подробности смотрите в примере Rust AIDL .
helpl_interface
Типы, используемые в системе сборки aidl_interface
, должны быть структурированы. Чтобы быть структурированными, посылки должны содержать поля напрямую, а не быть объявлениями типов, определенных непосредственно в целевых языках. О том, как структурированный AIDL сочетается со стабильным AIDL, см. в разделе Структурированный и стабильный AIDL .
Типы
Рассматривайте компилятор aidl
как эталонную реализацию типов. Когда вы создаете интерфейс, вызовите aidl --lang=<backend> ...
чтобы просмотреть полученный файл интерфейса. Когда вы используете модуль aidl_interface
, вы можете просмотреть выходные данные в out/soong/.intermediates/ <path to module> /
.
Тип Java или AIDL | Тип С++ | Тип НДК | Тип ржавчины |
---|---|---|---|
boolean | bool | bool | bool |
byte 8 | 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 | В: &str Выход: String |
android.os.Parcelable | android::Parcelable | Н/Д | Н/Д |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | В: &[T] Вышел: Vec<T> |
byte[] | std::vector | std::vector 1 | В: &[u8] Вышел: Vec<u8> |
List<T> | std::vector<T> 2 | std::vector<T> 3 | Вышел: In: &[T] 4Вышел: Vec<T> |
FileDescriptor | android::base::unique_fd | Н/Д | Н/Д |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
Тип интерфейса ( T ) | android::sp<T> | std::shared_ptr<T> 7 | binder::Strong |
Тип посылки ( T ) | T | T | T |
Тип соединения ( T ) 5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. В Android 12 или более поздних версиях массивы байтов используют uint8_t
вместо int8_t
по соображениям совместимости.
2. Серверная часть C++ поддерживает List<T>
, где T
— одно из String
, IBinder
, ParcelFileDescriptor
или Parcelable. В Android 13 или более поздних версиях T
может быть любым непримитивным типом (включая типы интерфейсов), кроме массивов. AOSP рекомендует использовать такие типы массивов, как T[]
, поскольку они работают во всех бэкэндах.
3. Серверная часть NDK поддерживает List<T>
, где T
— одно из String
, ParcelFileDescriptor
или Parcelable. В Android 13 или более поздней версии T
может быть любым непримитивным типом, кроме массивов.
4. Типы для кода Rust передаются по-разному в зависимости от того, являются ли они входными (аргументы) или выходными (возвращаемое значение).
5. Типы объединений поддерживаются в Android 12 и более поздних версиях.
6. В Android 13 и более поздних версиях поддерживаются массивы фиксированного размера. Массивы фиксированного размера могут иметь несколько измерений (например, int[3][4]
). В серверной части Java массивы фиксированного размера представлены как типы массивов.
7. Чтобы создать экземпляр объекта привязки SharedRefBase
, используйте SharedRefBase::make\<My\>(... args ...)
. Эта функция создает объект std::shared_ptr\<T\>
, который также управляется внутренним образом, если связующее принадлежит другому процессу. Создание объекта другими способами приводит к двойному владению.
8. См. также тип Java или AIDL byte[]
.
Направленность (внутрь, наружу и внутрь)
При указании типов аргументов функций вы можете указать их как in
, out
или inout
. Это контролирует направление передачи информации для вызова IPC.
Спецификатор аргумента
in
указывает, что данные передаются от вызывающего объекта к вызываемому. Спецификаторin
является направлением по умолчанию, но если типы данных также могут бытьout
, то необходимо указать направление.Спецификатор аргумента
out
означает, что данные передаются от вызывающего абонента к вызывающему.Спецификатор аргумента
inout
представляет собой комбинацию обоих из них. Однако мы рекомендуем избегать использования спецификатора аргументаinout
. Если вы используетеinout
с интерфейсом с поддержкой версий и более старым вызываемым объектом, дополнительные поля, присутствующие только в вызывающем объекте, сбрасываются до значений по умолчанию. Что касается Rust, обычныйinout
тип получает&mut T
, аinout
тип списка получает&mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8 и UTF-16
С помощью серверной части CPP вы можете выбрать, будут ли строки иметь формат UTF-8 или UTF-16. Объявляйте строки как @utf8InCpp String
в AIDL, чтобы автоматически преобразовать их в UTF-8. Серверные части NDK и Rust всегда используют строки UTF-8. Дополнительные сведения об аннотации utf8InCpp
см. в разделе utf8InCpp .
Обнуляемость
Вы можете аннотировать типы, которые могут иметь значение NULL, с помощью @nullable
. Дополнительные сведения об аннотации, nullable
, см . в разделе nullable .
Индивидуальные посылки
Пользовательский объект — это объект, который вручную реализуется в целевой серверной части. Используйте пользовательские участки только в том случае, если вы пытаетесь добавить поддержку других языков для существующего пользовательского участка, который нельзя изменить.
Вот пример разделяемого объявления AIDL:
package my.pack.age;
parcelable Foo;
По умолчанию здесь объявляется пакетный объект Java, где my.pack.age.Foo
— это класс Java, реализующий интерфейс Parcelable
.
Для объявления пользовательского бэкэнда CPP, который можно упаковать в AIDL, используйте cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
Реализация C++ в my/pack/age/Foo.h
выглядит следующим образом:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
Для объявления пользовательского NDK, подлежащего пакетированию в AIDL, используйте ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
Реализация NDK в android/pack/age/Foo.h
выглядит следующим образом:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
В Android 15 для объявления пользовательского пакета Rust в AIDL rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
Реализация Rust в rust_crate/src/lib.rs
выглядит следующим образом:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
Затем вы можете использовать этот объект в качестве типа в файлах AIDL, но AIDL не будет генерировать его. Предоставьте операторы <
и ==
для пользовательских пакетов серверной части CPP и NDK, чтобы использовать их в union
.
Значения по умолчанию
Структурированные посылки могут объявлять значения по умолчанию для каждого поля для примитивов, String
полей и массивов этих типов.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
В серверной части Java, когда значения по умолчанию отсутствуют, поля инициализируются как нулевые значения для примитивных типов и null
для непримитивных типов.
В других серверных модулях поля инициализируются значениями по умолчанию, если значения по умолчанию не определены. Например, в серверной части C++ поля String
инициализируются как пустая строка, а поля List<T>
инициализируются как пустой vector<T>
. Поля @nullable
инициализируются как поля с нулевым значением.
Союзы
Объединения AIDL помечены тегами, и их функции одинаковы во всех бэкэндах. Они создаются по значению по умолчанию для первого поля и имеют специфичный для языка способ взаимодействия с ними:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Пример Java
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setSringField("abc"); // setter
Пример C++ и NDK
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Пример ржавчины
В Rust объединения реализованы как перечисления и не имеют явных методов получения и установки.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
Обработка ошибок
ОС Android предоставляет встроенные типы ошибок, которые службы могут использовать при сообщении об ошибках. Они используются связующими и могут использоваться любыми службами, реализующими интерфейс связующего. Их использование хорошо документировано в определении AIDL, и они не требуют какого-либо определяемого пользователем статуса или типа возвращаемого значения.
Выходные параметры с ошибками
Когда функция AIDL сообщает об ошибке, функция может не инициализировать или не изменить выходные параметры. В частности, выходные параметры могут быть изменены, если ошибка возникает во время распаковки, а не во время обработки самой транзакции. В общем, при получении ошибки от функции AIDL все inout
и out
параметры, а также возвращаемое значение (которое в некоторых бэкэндах действует как out
параметр) следует считать находящимися в неопределенном состоянии.
Какие значения ошибок использовать
Многие из встроенных значений ошибок можно использовать в любых интерфейсах AIDL, но некоторые обрабатываются особым образом. Например, EX_UNSUPPORTED_OPERATION
и EX_ILLEGAL_ARGUMENT
можно использовать, когда они описывают состояние ошибки, но EX_TRANSACTION_FAILED
нельзя использовать, поскольку он обрабатывается специальной инфраструктурой. Дополнительную информацию об этих встроенных значениях см. в определениях серверной части.
Если интерфейс AIDL требует дополнительных значений ошибок, которые не включены во встроенные типы ошибок, они могут использовать специальную встроенную ошибку для конкретной службы, которая позволяет включать значение ошибки для конкретной службы, определенное пользователем. Эти ошибки, специфичные для службы, обычно определяются в интерфейсе AIDL как const int
или enum
с поддержкой int
и не анализируются связующим устройством.
В Java ошибки сопоставляются с исключениями, такими как android.os.RemoteException
. Для исключений, специфичных для службы, Java использует android.os.ServiceSpecificException
вместе с определяемой пользователем ошибкой.
Нативный код Android не использует исключений. Серверная часть CPP использует android::binder::Status
. Серверная часть NDK использует ndk::ScopedAStatus
. Каждый метод, сгенерированный AIDL, возвращает один из них, представляющий состояние метода. Бэкэнд Rust использует те же значения кодов исключений, что и NDK, но преобразует их в собственные ошибки Rust ( StatusCode
, ExceptionCode
) перед доставкой пользователю. Для ошибок, специфичных для службы, возвращаемый Status
или ScopedAStatus
использует EX_SERVICE_SPECIFIC
вместе с определяемой пользователем ошибкой.
Встроенные типы ошибок можно найти в следующих файлах:
Бэкэнд | Определение |
---|---|
Ява | android/os/Parcel.java |
CPP | binder/Status.h |
НДК | android/binder_status.h |
Ржавчина | android/binder_status.h |
Используйте различные бэкэнды
Эти инструкции относятся к коду платформы Android. В этих примерах используется определенный тип my.package.IFoo
. Инструкции по использованию бэкэнда Rust см. в примере Rust AIDL в шаблонах Android Rust .
Типы импорта
Независимо от того, является ли определенный тип интерфейсом, пакетным объектом или объединением, вы можете импортировать его в Java:
import my.package.IFoo;
Или в бэкэнде CPP:
#include <my/package/IFoo.h>
Или в бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl
):
#include <aidl/my/package/IFoo.h>
Или в бэкэнде Rust:
use my_package::aidl::my::package::IFoo;
Хотя в Java можно импортировать вложенный тип, в серверных модулях CPP и NDK необходимо включить заголовок для его корневого типа. Например, при импорте вложенного типа Bar
, определенного в my/package/IFoo.aidl
( IFoo
— корневой тип файла), вы должны включить <my/package/IFoo.h>
для серверной части CPP (или <aidl/my/package/IFoo.h>
для серверной части NDK).
Реализация интерфейса
Чтобы реализовать интерфейс, вы должны наследовать собственный класс-заглушку. Реализация интерфейса часто называется службой , когда она регистрируется в диспетчере служб или android.app.ActivityManager
, и вызывается обратным вызовом , когда она регистрируется клиентом службы. Однако для описания реализаций интерфейса используются различные имена в зависимости от конкретного использования. Класс-заглушка считывает команды из драйвера связывателя и выполняет реализуемые вами методы. Представьте, что у вас есть такой файл AIDL:
package my.package;
interface IFoo {
int doFoo();
}
В Java вы должны расширить сгенерированный класс- Stub
:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
В бэкэнде CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
В бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl
):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
В бэкэнде Rust:
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(())
}
}
Или с асинхронным Rust:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Зарегистрируйтесь и получайте услуги
Службы на платформе Android обычно регистрируются в процессе servicemanager
. Помимо следующих API, некоторые API проверяют службу (то есть они немедленно возвращаются, если служба недоступна). Точную информацию можно найти в соответствующем интерфейсе servicemanager
. Вы можете выполнять эти операции только при компиляции для платформы Android.
На Яве:
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"));
В бэкэнде CPP:
#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"));
В бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl
):
#include <android/binder_manager.h>
// registering
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::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(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));
В бэкэнде Rust:
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()
}
В асинхронном бэкэнде Rust с однопоточной средой выполнения:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks run on this thread.
std::future::pending().await
}
Одним из важных отличий от других вариантов является то, что вы не вызываете join_thread_pool
при использовании асинхронного Rust и однопоточной среды выполнения. Это потому, что вам нужно предоставить Tokio поток, в котором она сможет выполнять порожденные задачи. В следующем примере этой цели служит основной поток. Любые задачи, созданные с помощью tokio::spawn
выполняются в основном потоке.
В асинхронном бэкэнде Rust с многопоточной средой выполнения:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
В многопоточной среде выполнения Tokio создаваемые задачи не выполняются в основном потоке. Поэтому имеет смысл вызывать join_thread_pool
в основном потоке, чтобы основной поток не простаивал. Вы должны обернуть вызов в block_in_place
, чтобы выйти из асинхронного контекста.
Ссылка на смерть
Вы можете запросить уведомление о прекращении службы, в которой размещается связующее. Это может помочь избежать утечки прокси-серверов обратного вызова или помочь в устранении ошибок. Выполните эти вызовы для прокси-объектов связывателя.
- В Java используйте
android.os.IBinder::linkToDeath
. - В серверной части CPP используйте
android::IBinder::linkToDeath
. - В бэкэнде NDK используйте
AIBinder_linkToDeath
. - В бэкэнде Rust создайте объект
DeathRecipient
, затем вызовитеmy_binder.link_to_death(&mut my_death_recipient)
. Обратите внимание: посколькуDeathRecipient
владеет обратным вызовом, вы должны поддерживать этот объект в рабочем состоянии до тех пор, пока хотите получать уведомления.
Информация о вызывающем абоненте
При получении вызова связывателя ядра информация о вызывающем абоненте доступна в нескольких API. Идентификатор процесса (PID) относится к идентификатору процесса Linux, отправляющего транзакцию. Идентификатор пользователя (UI) относится к идентификатору пользователя Linux. При получении одностороннего вызова PID вызова равен 0. Вне контекста транзакции связывания эти функции возвращают PID и UID текущего процесса.
В бэкэнде Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
В бэкэнде CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
В бэкэнде NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
В бэкэнде Rust при реализации интерфейса укажите следующее (вместо того, чтобы использовать значение по умолчанию):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Отчеты об ошибках и API отладки для сервисов
Когда запускаются отчеты об ошибках (например, с помощью adb bugreport
), они собирают информацию со всей системы, чтобы помочь в отладке различных проблем. Для служб AIDL в отчетах об ошибках используются двоичные dumpsys
всех служб, зарегистрированных у менеджера служб, для сброса их информации в отчет об ошибках. Вы также можете использовать dumpsys
в командной строке, чтобы получить информацию от службы с помощью dumpsys SERVICE [ARGS]
. В серверных модулях C++ и Java вы можете контролировать порядок, в котором службы выгружаются, используя дополнительные аргументы addService
. Вы также можете использовать dumpsys --pid SERVICE
, чтобы получить PID службы во время отладки.
Чтобы добавить пользовательский вывод в службу, переопределите метод dump
в объекте сервера, как если бы вы реализовали любой другой метод IPC, определенный в файле AIDL. При этом ограничьте выгрузку разрешением приложения android.permission.DUMP
или ограничьте выгрузку определенными UID.
В бэкэнде Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
В бэкэнде CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
В бэкэнде NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
В бэкэнде Rust при реализации интерфейса укажите следующее (вместо того, чтобы использовать значение по умолчанию):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Используйте слабые указатели
Вы можете хранить слабую ссылку на объект привязки.
Хотя Java поддерживает WeakReference
, она не поддерживает ссылки на слабые привязки на собственном уровне.
В серверной части CPP слабым типом является wp<IFoo>
.
В бэкэнде NDK используйте ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
В бэкэнде Rust используйте WpIBinder
или Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Динамическое получение дескриптора интерфейса
Дескриптор интерфейса определяет тип интерфейса. Это полезно при отладке или при наличии неизвестного связующего.
В Java вы можете получить дескриптор интерфейса с помощью такого кода:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
В бэкэнде CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Серверные части NDK и Rust не поддерживают эту возможность.
Статически получить дескриптор интерфейса
Иногда (например, при регистрации сервисов @VintfStability
) вам необходимо знать статический дескриптор интерфейса. В Java вы можете получить дескриптор, добавив такой код, как:
import my.package.IFoo;
... IFoo.DESCRIPTOR
В бэкэнде CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
В бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl
):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
В бэкэнде Rust:
aidl::my::package::BnFoo::get_descriptor()
Диапазон перечисления
В собственных бэкэндах вы можете перебирать возможные значения, которые может принимать перечисление. Из соображений размера кода это не поддерживается в Java.
Для перечисления MyEnum
, определенного в AIDL, итерация осуществляется следующим образом.
В бэкэнде CPP:
::android::enum_range<MyEnum>()
В бэкэнде NDK:
::ndk::enum_range<MyEnum>()
В бэкэнде Rust:
MyEnum::enum_values()
Управление потоками
Каждый экземпляр libbinder
в процессе поддерживает один пул потоков. В большинстве случаев это должен быть ровно один пул потоков, общий для всех серверных частей. Единственное исключение — если код поставщика загружает другую копию libbinder
для взаимодействия с /dev/vndbinder
. Это отдельный узел связывания, поэтому пул потоков не является общим.
Для серверной части Java размер пула потоков может только увеличиваться (поскольку он уже запущен):
BinderInternal.setMaxThreads(<new larger value>);
Для серверной части CPP доступны следующие операции:
// 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();
Аналогично в бэкэнде NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
В бэкэнде Rust:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
Для асинхронного бэкэнда Rust вам понадобятся два пула потоков: Binder и Tokio. Это означает, что приложения, использующие асинхронный Rust, требуют особого внимания, особенно когда речь идет об использовании join_thread_pool
. Подробнее об этом читайте в разделе о регистрации услуг .
Зарезервированные имена
C++, Java и Rust зарезервировали некоторые имена в качестве ключевых слов или для использования в конкретных языках. Хотя AIDL не применяет ограничений, основанных на языковых правилах, использование имен полей или типов, соответствующих зарезервированному имени, может привести к сбою компиляции для C++ или Java. В Rust поле или тип переименовываются с использованием синтаксиса необработанного идентификатора, доступного по префиксу r#
.
Мы рекомендуем по возможности избегать использования зарезервированных имен в определениях AIDL, чтобы избежать неэргономичных привязок или полного сбоя компиляции.
Если у вас уже есть зарезервированные имена в определениях AIDL, вы можете безопасно переименовывать поля, сохраняя при этом совместимость с протоколом. Возможно, вам придется обновить код, чтобы продолжить сборку, но любые уже созданные программы продолжат взаимодействовать.
Имена, которых следует избегать:
,Серверная часть AIDL является целью для генерации кода-заглушки. Всегда используйте файлы AIDL на определенном языке с определенной средой выполнения. В зависимости от контекста вам следует использовать разные серверные части AIDL.
В следующей таблице стабильность поверхности API означает возможность компилировать код с этой поверхностью API таким образом, чтобы код мог быть доставлен независимо от двоичного файла system.img
libbinder.so
.
AIDL имеет следующие серверные части:
Бэкэнд | Язык | поверхность API | Системы сборки |
---|---|---|---|
Ява | Ява | SDK или SystemApi (стабильная*) | Все |
НДК | С++ | libbinder_ndk (стабильная*) | aidl_interface |
CPP | С++ | libbinder (нестабильный) | Все |
Ржавчина | Ржавчина | libbinder_rs (стабильная*) | aidl_interface |
- Эти поверхности API стабильны, но многие из API, например для управления службами, зарезервированы для внутреннего использования платформы и недоступны приложениям. Дополнительные сведения о том, как использовать AIDL в приложениях, см. в разделе Язык определения интерфейса Android (AIDL) .
- Бэкэнд Rust был представлен в Android 12; Серверная часть NDK доступна начиная с Android 10.
- Крейт Rust построен на основе
libbinder_ndk
, что делает его стабильным и переносимым. APEX использует ящик для подшивок стандартным образом на стороне системы. Часть Rust упакована в APEX и поставляется внутри него. Эта часть зависит отlibbinder_ndk.so
в системном разделе.
Системы сборки
В зависимости от серверной части существует два способа компиляции AIDL в код-заглушку. Более подробную информацию о системах сборки см. в разделе «Справочник по модулям Soong» .
Основная система сборки
В любом Android.bp module
cc_
или java_
Android.bp (или их эквивалентах Android.mk
) вы можете указать файлы AIDL ( .aidl
) в качестве исходных файлов. В этом случае используются серверные части AIDL Java или CPP (а не серверная часть NDK), а классы для использования соответствующих файлов AIDL добавляются в модуль автоматически. Вы можете указать такие параметры, как local_include_dirs
(который сообщает системе сборки корневой путь к файлам AIDL в этом модуле) в этих модулях в группе aidl:
:.
Бэкэнд Rust предназначен только для использования с Rust. Модули rust_
обрабатываются по-другому: файлы AIDL не указываются в качестве исходных файлов. Вместо этого aidl_interface
создает rustlib
с именем aidl_interface_name -rust
, с которым можно скомпоноваться. Подробности смотрите в примере Rust AIDL .
helpl_interface
Типы, используемые в системе сборки aidl_interface
, должны быть структурированы. Чтобы быть структурированными, посылки должны содержать поля напрямую, а не быть объявлениями типов, определенных непосредственно в целевых языках. О том, как структурированный AIDL сочетается со стабильным AIDL, см. в разделе Структурированный и стабильный AIDL .
Типы
Рассматривайте компилятор aidl
как эталонную реализацию типов. Когда вы создаете интерфейс, вызовите aidl --lang=<backend> ...
чтобы просмотреть полученный файл интерфейса. Когда вы используете модуль aidl_interface
, вы можете просмотреть выходные данные в out/soong/.intermediates/ <path to module> /
.
Тип Java или AIDL | Тип С++ | Тип НДК | Тип ржавчины |
---|---|---|---|
boolean | bool | bool | bool |
byte 8 | 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 | В: &str Выход: String |
android.os.Parcelable | android::Parcelable | Н/Д | Н/Д |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | В: &[T] Вышел: Vec<T> |
byte[] | std::vector | std::vector 1 | В: &[u8] Вышел: Vec<u8> |
List<T> | std::vector<T> 2 | std::vector<T> 3 | Вышел: In: &[T] 4Вышел: Vec<T> |
FileDescriptor | android::base::unique_fd | Н/Д | Н/Д |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
Тип интерфейса ( T ) | android::sp<T> | std::shared_ptr<T> 7 | binder::Strong |
Тип посылки ( T ) | T | T | T |
Тип соединения ( T ) 5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. В Android 12 или более поздних версиях массивы байтов используют uint8_t
вместо int8_t
по соображениям совместимости.
2. Серверная часть C++ поддерживает List<T>
, где T
— одно из String
, IBinder
, ParcelFileDescriptor
или Parcelable. В Android 13 или более поздних версиях T
может быть любым непримитивным типом (включая типы интерфейсов), кроме массивов. AOSP рекомендует использовать такие типы массивов, как T[]
, поскольку они работают во всех бэкэндах.
3. Серверная часть NDK поддерживает List<T>
, где T
— одно из String
, ParcelFileDescriptor
или Parcelable. В Android 13 или более поздней версии T
может быть любым непримитивным типом, кроме массивов.
4. Типы для кода Rust передаются по-разному в зависимости от того, являются ли они входными (аргументы) или выходными (возвращаемое значение).
5. Типы объединений поддерживаются в Android 12 и более поздних версиях.
6. В Android 13 и более поздних версиях поддерживаются массивы фиксированного размера. Массивы фиксированного размера могут иметь несколько измерений (например, int[3][4]
). В серверной части Java массивы фиксированного размера представлены как типы массивов.
7. Чтобы создать экземпляр объекта привязки SharedRefBase
, используйте SharedRefBase::make\<My\>(... args ...)
. Эта функция создает объект std::shared_ptr\<T\>
, который также управляется внутренним образом, если связующее принадлежит другому процессу. Создание объекта другими способами приводит к двойному владению.
8. См. также тип Java или AIDL byte[]
.
Направленность (внутрь, наружу и внутрь)
При указании типов аргументов функций вы можете указать их как in
, out
или inout
. Это контролирует направление передачи информации для вызова IPC.
Спецификатор аргумента
in
указывает, что данные передаются от вызывающего объекта к вызываемому. Спецификаторin
является направлением по умолчанию, но если типы данных также могут бытьout
, то необходимо указать направление.Спецификатор аргумента
out
означает, что данные передаются от вызывающего абонента к вызывающему.Спецификатор аргумента
inout
представляет собой комбинацию обоих из них. Однако мы рекомендуем избегать использования спецификатора аргументаinout
. Если вы используетеinout
с интерфейсом с поддержкой версий и более старым вызываемым объектом, дополнительные поля, присутствующие только в вызывающем объекте, сбрасываются до значений по умолчанию. Что касается Rust, обычныйinout
тип получает&mut T
, аinout
тип списка получает&mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8 и UTF-16
С помощью серверной части CPP вы можете выбрать, будут ли строки иметь формат UTF-8 или UTF-16. Объявляйте строки как @utf8InCpp String
в AIDL, чтобы автоматически преобразовать их в UTF-8. Серверные части NDK и Rust всегда используют строки UTF-8. Дополнительные сведения об аннотации utf8InCpp
см. в разделе utf8InCpp .
Обнуляемость
Вы можете аннотировать типы, которые могут иметь значение NULL, с помощью @nullable
. Дополнительные сведения об аннотации, nullable
, см . в разделе nullable .
Индивидуальные посылки
Пользовательский объект — это объект, который вручную реализуется в целевой серверной части. Используйте пользовательские участки только в том случае, если вы пытаетесь добавить поддержку других языков для существующего пользовательского участка, который нельзя изменить.
Вот пример разделяемого объявления AIDL:
package my.pack.age;
parcelable Foo;
По умолчанию здесь объявляется пакетный объект Java, где my.pack.age.Foo
— это класс Java, реализующий интерфейс Parcelable
.
Для объявления пользовательского бэкэнда CPP, который можно упаковать в AIDL, используйте cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
Реализация C++ в my/pack/age/Foo.h
выглядит следующим образом:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
Для объявления пользовательского NDK, подлежащего пакетированию в AIDL, используйте ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
Реализация NDK в android/pack/age/Foo.h
выглядит следующим образом:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
В Android 15 для объявления пользовательского пакета Rust в AIDL rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
Реализация Rust в rust_crate/src/lib.rs
выглядит следующим образом:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
Затем вы можете использовать этот объект в качестве типа в файлах AIDL, но AIDL не будет генерировать его. Предоставьте операторы <
и ==
для пользовательских пакетов серверной части CPP и NDK, чтобы использовать их в union
.
Значения по умолчанию
Структурированные посылки могут объявлять значения по умолчанию для каждого поля для примитивов, String
полей и массивов этих типов.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
В серверной части Java, когда значения по умолчанию отсутствуют, поля инициализируются как нулевые значения для примитивных типов и null
для непримитивных типов.
В других серверных модулях поля инициализируются значениями по умолчанию, если значения по умолчанию не определены. Например, в серверной части C++ поля String
инициализируются как пустая строка, а поля List<T>
инициализируются как пустой vector<T>
. Поля @nullable
инициализируются как поля с нулевым значением.
Союзы
Объединения AIDL помечены тегами, и их функции одинаковы во всех бэкэндах. Они создаются по значению по умолчанию для первого поля и имеют специфичный для языка способ взаимодействия с ними:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Пример Java
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setSringField("abc"); // setter
Пример C++ и NDK
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Пример ржавчины
В Rust объединения реализованы как перечисления и не имеют явных методов получения и установки.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
Обработка ошибок
ОС Android предоставляет встроенные типы ошибок, которые службы могут использовать при сообщении об ошибках. Они используются связующими и могут использоваться любыми службами, реализующими интерфейс связующего. Их использование хорошо документировано в определении AIDL, и они не требуют какого-либо определяемого пользователем статуса или типа возвращаемого значения.
Выходные параметры с ошибками
Когда функция AIDL сообщает об ошибке, функция может не инициализировать или не изменить выходные параметры. В частности, выходные параметры могут быть изменены, если ошибка возникает во время распаковки, а не во время обработки самой транзакции. В общем, при получении ошибки от функции AIDL все inout
и out
параметры, а также возвращаемое значение (которое в некоторых бэкэндах действует как out
параметр) следует считать находящимися в неопределенном состоянии.
Какие значения ошибок использовать
Многие из встроенных значений ошибок можно использовать в любых интерфейсах AIDL, но некоторые обрабатываются особым образом. Например, EX_UNSUPPORTED_OPERATION
и EX_ILLEGAL_ARGUMENT
- это нормально использовать, когда они описывают условие ошибки, но EX_TRANSACTION_FAILED
нельзя использовать, потому что он специально рассматривается основной инфраструктурой. Проверьте бэкэнд конкретные определения для получения дополнительной информации об этих встроенных значениях.
Если интерфейс AIDL требует дополнительных значений ошибок, которые не охватывают встроенные типы ошибок, они могут использовать специальную встроенную ошибку, специфичную для конкретной службы, которая позволяет включать значение ошибки, определяемое пользователем. Эти ошибки, специфичные для обслуживания, обычно определяются в интерфейсе AIDL как enum
const int
или int
и не проанализированы с помощью связующего.
В Java ошибки отображают исключения, такие как android.os.RemoteException
. Для исключений для конкретного обслуживания Java использует android.os.ServiceSpecificException
вместе с определенной пользовательской ошибкой.
Настоящий код в Android не использует исключения. В бэкэнд CPP используется android::binder::Status
. В бэкэнд NDK используется ndk::ScopedAStatus
. Каждый метод, генерируемый AIDL, возвращает один из них, представляющий статус метода. Бэкэнд ржавчины использует те же значения кода исключения, что и NDK, но преобразует их в собственные ошибки ржавчины ( StatusCode
, ExceptionCode
), прежде чем доставить их пользователю. Для ошибок, специфичных для обслуживания, возвращаемый Status
или ScopedAStatus
использует EX_SERVICE_SPECIFIC
вместе с определенной пользовательской ошибкой.
Встроенные типы ошибок можно найти в следующих файлах:
Бэкэнд | Определение |
---|---|
Ява | android/os/Parcel.java |
CPP | binder/Status.h |
НДК | android/binder_status.h |
Ржавчина | android/binder_status.h |
Используйте различные бэкэнды
Эти инструкции специфичны для кода платформы Android. Эти примеры используют определенный тип, my.package.IFoo
. Инструкции о том, как использовать бэкэнд Rust, см. Пример Rust Aidl в узорах Android Rust .
Типы импорта
Независимо от того, является ли определенный тип интерфейсом, приподнятым или союзом, вы можете импортировать его в Java:
import my.package.IFoo;
Или в бэкэнде CPP:
#include <my/package/IFoo.h>
Или в бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <aidl/my/package/IFoo.h>
Или в бэкэнде ржавчины:
use my_package::aidl::my::package::IFoo;
Хотя вы можете импортировать вложенный тип в Java, в бэкэнды CPP и NDK вы должны включить заголовок для его корня. Например, при импорте вложенной Bar
типов, определенной в my/package/IFoo.aidl
( IFoo
- это тип корня файла), вы должны включить <my/package/IFoo.h>
для бэкэнда CPP (или <aidl/my/package/IFoo.h>
для бэкэнд NDK).
Реализовать интерфейс
Чтобы реализовать интерфейс, вы должны унаследовать от нативного класса Stub. Реализация интерфейса часто называется службой , когда он зарегистрирован в Service Manager или android.app.ActivityManager
и называется обратным вызовом , когда он зарегистрирован клиентом службы. Тем не менее, различные имена используются для описания реализаций интерфейса в зависимости от точного использования. Класс STUB считывает команды от драйвера связующего и выполняет методы, которые вы реализуете. Представьте, что у вас есть файл AIDL, как это:
package my.package;
interface IFoo {
int doFoo();
}
В Java вы должны простираться от сгенерированного класса Stub
:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
В бэкэнде CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
В бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
В бэкэнд ржавчины:
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(())
}
}
Или с асинхронной ржавкой:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Зарегистрировать и получить услуги
Услуги в платформе Android обычно зарегистрированы в процессе servicemanager
. В дополнение к следующим API, некоторые API проверяют услугу (то есть они возвращаются немедленно, если служба недоступна). Проверьте соответствующий интерфейс servicemanager
для точных подробностей. Вы можете выполнить эти операции только при компиляции против платформы Android.
В 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"));
В бэкэнде CPP:
#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"));
В бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <android/binder_manager.h>
// registering
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::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(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));
В бэкэнд ржавчины:
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()
}
В асинхронном бэкэнге ржавчины со временем выполнения:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks run on this thread.
std::future::pending().await
}
Одно важное отличие от других вариантов заключается в том, что вы не вызываете join_thread_pool
при использовании Async rust и однопользованного времени выполнения. Это потому, что вам нужно дать Токио поток, где он может выполнять порожденные задачи. В следующем примере основной поток служит этой цели. Любые задачи порождены, используя tokio::spawn
Execute в главном потоке.
В асинхронном бэкэнд с ржавчиной с многопоточной средой выполнения:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
С многопоточной средой выполнения токио, порожденные задачи не выполняются в основном потоке. Следовательно, имеет смысл вызовать join_thread_pool
в главном потоке, чтобы основной поток не был простаиванием. Вы должны обернуть вызов в block_in_place
, чтобы покинуть асинхронный контекст.
Ссылка на смерть
Вы можете попросить получить уведомление о том, когда умирает сервис, хостинг переплет. Это может помочь избежать утечки прокси -вызовов или помочь в восстановлении ошибки. Сделайте эти вызовы на прокси -объектах Binder.
- В Java используйте
android.os.IBinder::linkToDeath
. - В бэкэнд CPP используйте
android::IBinder::linkToDeath
. - В бэкэнд NDK используйте
AIBinder_linkToDeath
. - В бэкэнде ржавчины создайте объект
DeathRecipient
, затем позвонитеmy_binder.link_to_death(&mut my_death_recipient)
. Обратите внимание, что, посколькуDeathRecipient
владеет обратным вызовом, вы должны сохранить этот объект до тех пор, пока вы хотите получить уведомления.
Абонент информация
При получении звонка с ядром связывание, информация о вызывающем абоненте доступна в нескольких API. Идентификатор процесса (PID) относится к идентификатору процесса Linux процесса, который отправляет транзакцию. Идентификатор пользователя (UI) относится к идентификатору пользователя Linux. При получении одностороннего вызова вызывающий пид составляет 0. За пределами контекста транзакции переплетения эти функции возвращают PID и UID текущего процесса.
В бэкэнге Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
В бэкэнде CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
В бэкэнд NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
В бэкэнде ржавчины при реализации интерфейса укажите следующее (вместо того, чтобы позволить ему по умолчанию):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Отчеты об ошибках и отладка API для услуг
Когда запускаются отчеты об ошибках (например, с adb bugreport
), они собирают информацию со всей системы, чтобы помочь отладки различных вопросов. Для AIDL Services отчеты об ошибках используют бинарные dumpsys
во всех службах, зарегистрированных в Manage Manager, чтобы сбросить их информацию в отчет об ошибках. Вы также можете использовать dumpsys
в командной строке, чтобы получить информацию из службы с помощью dumpsys SERVICE [ARGS]
. В бэкэндах C ++ и Java вы можете контролировать заказ, в котором службы сбрасываются, используя дополнительные аргументы для addService
. Вы также можете использовать dumpsys --pid SERVICE
, чтобы получить пакет услуги во время отладки.
Чтобы добавить пользовательский вывод в свой сервис, переопределите метод dump
в объекте вашего сервера, как вы реализуете любой другой метод IPC, определенный в файле AIDL. При этом ограничивайте сброс в приложении разрешения android.permission.DUMP
или ограничивайте сброс на конкретные UID.
В бэкэнге Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
В бэкэнде CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
В бэкэнд NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
В бэкэнде ржавчины при реализации интерфейса укажите следующее (вместо того, чтобы позволить ему по умолчанию):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Используйте слабые указатели
Вы можете сохранить слабую ссылку на связующий объект.
В то время как Java поддерживает WeakReference
, она не поддерживает слабые ссылки на переплет на местном уровне.
В бэкэнде CPP слабый тип - wp<IFoo>
.
В бэкэнд NDK используйте ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
В бэкэнге ржавчины используйте WpIBinder
или Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Динамически получить дескриптор интерфейса
Дескриптор интерфейса идентифицирует тип интерфейса. Это полезно при отладке или когда у вас неизвестный связующий.
В Java вы можете получить дескриптор интерфейса с кодом, таким как:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
В бэкэнде CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Backends NDK и Rust не поддерживают эту возможность.
Статически получить дескриптор интерфейса
Иногда (например, при регистрации услуг @VintfStability
) вам нужно знать, каково дескриптор интерфейса статически. В Java вы можете получить дескриптор, добавив код, такой как:
import my.package.IFoo;
... IFoo.DESCRIPTOR
В бэкэнде CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
В бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
В бэкэнд ржавчины:
aidl::my::package::BnFoo::get_descriptor()
Enum диапазон
В родных бэкэндах вы можете использовать возможные значения, которые может принять перечисление. Из -за соображений размера кода это не поддерживается в Java.
Для Enum MyEnum
определенной в AIDL, итерация предоставляется следующим образом.
В бэкэнде CPP:
::android::enum_range<MyEnum>()
В бэкэнд NDK:
::ndk::enum_range<MyEnum>()
В бэкэнд ржавчины:
MyEnum::enum_values()
Управление потоком
Каждый экземпляр libbinder
в процессе поддерживает одну тренас. Для большинства вариантов использования это должно быть ровно одной тренасовой, разделенной во всех бэкэндах. Единственное исключение - если код поставщика загружает другую копию libbinder
, чтобы поговорить с /dev/vndbinder
. Это на отдельном узел связующего, поэтому потоковая точка не совместно используется.
Для бэкэнда Java трюк может увеличиться только в размере (потому что она уже началась):
BinderInternal.setMaxThreads(<new larger value>);
Для бэкэнда CPP доступны следующие операции:
// 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();
Точно так же в бэкэнде NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
В бэкэнд ржавчины:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
С Async Rust Backend вам нужны два нить: Binder и Tokio. Это означает, что приложения, использующие Async Rust, нуждаются в специальных соображениях, особенно когда речь идет об использовании join_thread_pool
. См. Раздел о регистрации служб для получения дополнительной информации об этом.
Зарезервированные имена
C ++, Java и Rust оставляют некоторые имена в виде ключевых слов или для конкретного языка. В то время как AIDL не обеспечивает соблюдение ограничений на основе языковых правил, использование имен поля или типов, соответствующих зарезервированному имени, может привести к сбое компиляции для C ++ или Java. Для ржавчины поле или тип переименованы в синтаксис идентификатора RAW, доступный с использованием префикса r#
.
Мы рекомендуем избегать использования зарезервированных имен в ваших определениях AIDL, где это возможно, чтобы избежать негономических привязков или откровенного сбоя компиляции.
Если у вас уже есть зарезервированные имена в ваших определениях AIDL, вы можете безопасно переименовать поля, оставаясь совместимым с протоколом. Возможно, вам придется обновить свой код, чтобы продолжить строительство, но любые уже созданные программы продолжают взаимодействовать.
Имена, которых следует избегать:
,Бэкэнд AIDL является целью для генерации кода заглушки. Всегда используйте файлы AIDL на определенном языке с определенным средством выполнения. В зависимости от контекста, вы должны использовать различные бэкэнды AIDL.
В следующей таблице стабильность поверхности API относится к возможности компилировать код против этой поверхности API таким образом libbinder.so
чтобы код мог быть доставлен независимо от system.img
.
У Aidl есть следующие бэкэнды:
Бэкэнд | Язык | Поверхность API | Строительные системы |
---|---|---|---|
Ява | Ява | SDK или SystemApi (стабильный*) | Все |
НДК | C ++ | libbinder_ndk (стабильный*) | aidl_interface |
CPP | C ++ | libbinder (нестабильный) | Все |
Ржавчина | Ржавчина | libbinder_rs (стабильный*) | aidl_interface |
- Эти поверхности API являются стабильными, но многие из API, например, для управления услугами, зарезервированы для внутреннего использования платформы и недоступны для приложений. Для получения дополнительной информации о том, как использовать AIDL в приложениях, см. Язык определения интерфейса Android (AIDL) .
- Бэкэнд ржавчины был введен в Android 12; Бэкэнд NDK был доступен как Android 10.
- Руст ящик построен на вершине
libbinder_ndk
, что позволяет быть стабильным и портативным. Чексы используют ящик связующего стандартным способом на стороне системы. Ржавчатая порция объединяется в вершину и отправляется внутри него. Эта часть зависит отlibbinder_ndk.so
на системном разделе.
Строительные системы
В зависимости от бэкэнда, есть два способа составить AIDL в код STUB. Для получения более подробной информации о системах сборки см. Ссылку на модули SOONG .
Система основной сборки
В любом Android.bp module
cc_
или java_
Android.bp (или в их эквивалентах Android.mk
) вы можете указать файлы AIDL ( .aidl
) в качестве исходных файлов. В этом случае используются бэкэнд AIDL Java или CPP (не бэкэнд NDK), а классы для использования соответствующих файлов AIDL добавляются к модулю автоматически. Вы можете указать такие параметры, как local_include_dirs
(который сообщает системе сборки корневой путь к файлам AIDL в этом модуле) в этих модулях в группе aidl:
:.
Бэкэнд ржавчины предназначен только для использования с ржавчиной. Модули rust_
обрабатываются по -разному в том, что файлы AIDL не указаны в качестве исходных файлов. Вместо этого модуль aidl_interface
создает rustlib
называемый aidl_interface_name -rust
, с которым можно связать. Для получения подробной информации см. Пример Rust Aidl .
aidl_interface
Типы, используемые с системой сборки aidl_interface
, должны быть структурированы. Чтобы быть структурированными, посредники должны содержать поля напрямую и не быть объявлениями типов, определенных непосредственно на целевых языках. Для того, как структурированный AIDL вписывается в стабильный AIDL, см. Структурированный и стабильный AIDL .
Типы
Рассмотрим компилятор aidl
как справочную реализацию для типов. Когда вы создаете интерфейс, вызовите aidl --lang=<backend> ...
чтобы увидеть полученный файл интерфейса. Когда вы используете модуль aidl_interface
, вы можете просмотреть выходные данные в out/soong/.intermediates/ <path to module> /
.
Java или aidl -тип | Тип C ++ | Тип NDK | Тип ржавчины |
---|---|---|---|
boolean | bool | bool | bool |
byte 8 | 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 | В: &str Out: String |
android.os.Parcelable | android::Parcelable | Н/Д | Н/Д |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | В: &[T] OUT: Vec<T> |
byte[] | std::vector | std::vector 1 | В: &[u8] OUT: Vec<u8> |
List<T> | std::vector<T> 2 | std::vector<T> 3 | В: In: &[T] 4OUT: Vec<T> |
FileDescriptor | android::base::unique_fd | Н/Д | Н/Д |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
Тип интерфейса ( T ) | android::sp<T> | std::shared_ptr<T> 7 | binder::Strong |
Приподнятый тип ( T ) | T | T | T |
Тип союза ( T ) 5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. В Android 12 или выше байтовые массивы используют uint8_t
вместо int8_t
по причинам совместимости.
2. Бэкэнд C ++ поддерживает List<T>
, где T
- один из String
, IBinder
, ParcelFileDescriptor
или посылок. В Android 13 или выше T
может быть любой непримидный тип (включая типы интерфейсов), кроме массивов. AOSP рекомендует использовать типы массивов, такие как T[]
, потому что они работают во всех бэкэндах.
3. Бэкэнд NDK Поддерживает List<T>
, где T
- один из String
, ParcelFileDescriptor
или посылок. В Android 13 или выше T
может быть любой непримидный тип, кроме массивов.
4. Типы передаются по -разному для кода ржавчины в зависимости от того, являются ли они входными (аргумент) или выходом (возвращенное значение).
5. Типы союзов поддерживаются в Android 12 и выше.
6. В Android 13 или выше поддерживаются массивы фиксированного размера. Массивы с фиксированным размером могут иметь несколько измерений (например, int[3][4]
). В бэкэнде Java массивы фиксированного размера представлены в виде типов массива.
7. Чтобы создать экземпляр объекта связующего SharedRefBase
, используйте SharedRefBase::make\<My\>(... args ...)
. Эта функция создает объект std::shared_ptr\<T\>
, который также управляется внутри, в случае, если связывание принадлежит другому процессу. Создание объекта другие способы вызывает двойное владение.
8. См. Также Java или Aidl Type byte[]
.
Направленность (в, вне, и не в курсе)
При указании типов аргументов функций вы можете указать их как in
, out
или inout
. Это управляет направлением, в котором информация передается для вызова IPC.
Спецификатор
in
аргументе указывает, что данные передаются от вызывающего абонента в Callee.in
Specifier - это направление по умолчанию, но если типы данных также могутout
, то вы должны указать направление.Спецификатор
out
означает, что данные передаются из Callee Caller.Спецификатор
inout
аргументации - это комбинация обоих из них. Тем не менее, мы рекомендуем избегать использованияinout
аргумента. Если вы используетеinout
с интерфейсом версии и более старой Callee, дополнительные поля, которые присутствуют только в вызывающем абоненте, получают сброс до значений по умолчанию. Что касается ржавчины, то нормальный типinout
получает&mut T
, а также тип Listinout
&mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8 и UTF-16
С помощью бэкэнда CPP вы можете выбрать, являются ли строки UTF-8 или UTF-16. Объявите строки как @utf8InCpp String
в AIDL, чтобы автоматически конвертировать их в UTF-8. Backends NDK и Rust всегда используют строки UTF-8. Для получения дополнительной информации об аннотации utf8InCpp
см. UTF8incpp .
Нуль
Вы можете аннотировать типы, которые могут быть нулевыми с @nullable
. Для получения дополнительной информации о nullable
аннотации см. Nullable .
Пользовательские посылки
Пользовательская посылка - это посылка, которая реализована вручную в бэкэнде цели. Используйте пользовательские посылки только тогда, когда вы пытаетесь добавить поддержку в другие языки для существующего пользовательского посылки, который нельзя изменить.
Вот пример посылок AIDL:
package my.pack.age;
parcelable Foo;
По умолчанию это объявляет Java Parcelable, где my.pack.age.Foo
- это класс Java, реализующий Parcelable
интерфейс.
Для объявления пользовательского бэкэнда CPP, подготовленного в AIDL, используйте cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
Реализация C ++ в my/pack/age/Foo.h
выглядит так:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
Для объявления пользовательского приподнята NDK в AIDL используйте ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
Реализация NDK в android/pack/age/Foo.h
выглядит так:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
В Android 15, для объявления пользовательской ржавчины, используемой в AIDL, используйте rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
Реализация ржавчины в rust_crate/src/lib.rs
выглядит так:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
Затем вы можете использовать этот посылок в качестве типа в файлах AIDL, но он не будет сгенерирован AIDL. Предоставьте <
и ==
операторы для CPP и NDK Backend Custom Parcelables, чтобы использовать их в union
.
Значения по умолчанию
Структурные посылки могут объявлять значения по умолчанию для примитивах, String
полей и массивов этих типов.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
В бэкэнге Java, когда значения по умолчанию отсутствуют, поля инициализируются как нулевые значения для примитивных типов и null
для непримизных типов.
В других бэкэндах поля инициализируются с помощью инициализированных значений по умолчанию, когда значения по умолчанию не определены. Например, в бэкэнде C ++ String
поля инициализируются как пустая строка, а поля List<T>
инициализируются как пустой vector<T>
. @nullable
поля инициализируются как поля с нулевой стоимостью.
Профсоюзы
Союзы AIDL помечены, и их функции схожи во всех бэкэндах. Они построены до значения по умолчанию первого поля, и у них есть специфичный для языка способ взаимодействия с ними:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Пример Java
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setSringField("abc"); // setter
Пример C ++ и NDK
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Ржавчина пример
В ржавчине профсоюзы реализованы как перечисления и не имеют явных сеттеров и сеттеров.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
Обработка ошибок
ОС Android предоставляет встроенные типы ошибок для использования служб при отчетности. Они используются привязками и могут использоваться любыми службами, внедряющими интерфейс связующего. Их использование хорошо задокументировано в определении AIDL, и они не требуют определенного пользовательского статуса или типа возврата.
Выходные параметры с ошибками
Когда функция AIDL сообщает об ошибке, функция может не инициализировать и не изменять выходные параметры. В частности, выходные параметры могут быть изменены, если ошибка возникает во время бессмысленного вылета, в отличие от происходящего во время обработки самой транзакции. В целом, при получении ошибки из функции AIDL все параметры inout
и out
, а также возвратное значение (которое действует как параметр out
в некоторых бэкэндах) следует считать в неопределенном состоянии.
Какие значения ошибки использовать
Многие из встроенных значений ошибок могут использоваться в любых интерфейсах AIDL, но некоторые обрабатываются особым образом. Например, EX_UNSUPPORTED_OPERATION
и EX_ILLEGAL_ARGUMENT
- это нормально использовать, когда они описывают условие ошибки, но EX_TRANSACTION_FAILED
нельзя использовать, потому что он специально рассматривается основной инфраструктурой. Проверьте бэкэнд конкретные определения для получения дополнительной информации об этих встроенных значениях.
Если интерфейс AIDL требует дополнительных значений ошибок, которые не охватывают встроенные типы ошибок, они могут использовать специальную встроенную ошибку, специфичную для конкретной службы, которая позволяет включать значение ошибки, определяемое пользователем. Эти ошибки, специфичные для обслуживания, обычно определяются в интерфейсе AIDL как enum
const int
или int
и не проанализированы с помощью связующего.
В Java ошибки отображают исключения, такие как android.os.RemoteException
. Для исключений для конкретного обслуживания Java использует android.os.ServiceSpecificException
вместе с определенной пользовательской ошибкой.
Настоящий код в Android не использует исключения. В бэкэнд CPP используется android::binder::Status
. В бэкэнд NDK используется ndk::ScopedAStatus
. Каждый метод, генерируемый AIDL, возвращает один из них, представляющий статус метода. Бэкэнд ржавчины использует те же значения кода исключения, что и NDK, но преобразует их в собственные ошибки ржавчины ( StatusCode
, ExceptionCode
), прежде чем доставить их пользователю. Для ошибок, специфичных для обслуживания, возвращаемый Status
или ScopedAStatus
использует EX_SERVICE_SPECIFIC
вместе с определенной пользовательской ошибкой.
Встроенные типы ошибок можно найти в следующих файлах:
Бэкэнд | Определение |
---|---|
Ява | android/os/Parcel.java |
CPP | binder/Status.h |
НДК | android/binder_status.h |
Ржавчина | android/binder_status.h |
Используйте различные бэкэнды
Эти инструкции специфичны для кода платформы Android. Эти примеры используют определенный тип, my.package.IFoo
. Инструкции о том, как использовать бэкэнд Rust, см. Пример Rust Aidl в узорах Android Rust .
Типы импорта
Независимо от того, является ли определенный тип интерфейсом, приподнятым или союзом, вы можете импортировать его в Java:
import my.package.IFoo;
Или в бэкэнде CPP:
#include <my/package/IFoo.h>
Или в бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <aidl/my/package/IFoo.h>
Или в бэкэнде ржавчины:
use my_package::aidl::my::package::IFoo;
Хотя вы можете импортировать вложенный тип в Java, в бэкэнды CPP и NDK вы должны включить заголовок для его корня. Например, при импорте вложенной Bar
типов, определенной в my/package/IFoo.aidl
( IFoo
- это тип корня файла), вы должны включить <my/package/IFoo.h>
для бэкэнда CPP (или <aidl/my/package/IFoo.h>
для бэкэнд NDK).
Реализовать интерфейс
Чтобы реализовать интерфейс, вы должны унаследовать от нативного класса Stub. Реализация интерфейса часто называется службой , когда он зарегистрирован в Service Manager или android.app.ActivityManager
и называется обратным вызовом , когда он зарегистрирован клиентом службы. Тем не менее, различные имена используются для описания реализаций интерфейса в зависимости от точного использования. Класс STUB считывает команды от драйвера связующего и выполняет методы, которые вы реализуете. Представьте, что у вас есть файл AIDL, как это:
package my.package;
interface IFoo {
int doFoo();
}
В Java вы должны простираться от сгенерированного класса Stub
:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
В бэкэнде CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
В бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
В бэкэнд ржавчины:
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(())
}
}
Или с асинхронной ржавкой:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Зарегистрировать и получить услуги
Услуги в платформе Android обычно зарегистрированы в процессе servicemanager
. В дополнение к следующим API, некоторые API проверяют услугу (то есть они возвращаются немедленно, если служба недоступна). Проверьте соответствующий интерфейс servicemanager
для точных подробностей. Вы можете выполнить эти операции только при компиляции против платформы Android.
В 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"));
В бэкэнде CPP:
#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"));
В бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <android/binder_manager.h>
// registering
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::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(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));
В бэкэнд ржавчины:
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()
}
В асинхронном бэкэнге ржавчины со временем выполнения:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks run on this thread.
std::future::pending().await
}
Одно важное отличие от других вариантов заключается в том, что вы не вызываете join_thread_pool
при использовании Async rust и однопользованного времени выполнения. Это потому, что вам нужно дать Токио поток, где он может выполнять порожденные задачи. В следующем примере основной поток служит этой цели. Любые задачи порождены, используя tokio::spawn
Execute в главном потоке.
В асинхронном бэкэнд с ржавчиной с многопоточной средой выполнения:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
С многопоточной средой выполнения токио, порожденные задачи не выполняются в основном потоке. Следовательно, имеет смысл вызовать join_thread_pool
в главном потоке, чтобы основной поток не был простаиванием. Вы должны обернуть вызов в block_in_place
, чтобы покинуть асинхронный контекст.
Ссылка на смерть
Вы можете попросить получить уведомление о том, когда умирает сервис, хостинг переплет. Это может помочь избежать утечки прокси -вызовов или помочь в восстановлении ошибки. Сделайте эти вызовы на прокси -объектах Binder.
- В Java используйте
android.os.IBinder::linkToDeath
. - В бэкэнд CPP используйте
android::IBinder::linkToDeath
. - В бэкэнд NDK используйте
AIBinder_linkToDeath
. - В бэкэнде ржавчины создайте объект
DeathRecipient
, затем позвонитеmy_binder.link_to_death(&mut my_death_recipient)
. Обратите внимание, что, посколькуDeathRecipient
владеет обратным вызовом, вы должны сохранить этот объект до тех пор, пока вы хотите получить уведомления.
Абонент информация
При получении звонка с ядром связывание, информация о вызывающем абоненте доступна в нескольких API. Идентификатор процесса (PID) относится к идентификатору процесса Linux процесса, который отправляет транзакцию. Идентификатор пользователя (UI) относится к идентификатору пользователя Linux. При получении одностороннего вызова вызывающий пид составляет 0. За пределами контекста транзакции переплетения эти функции возвращают PID и UID текущего процесса.
В бэкэнге Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
В бэкэнде CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
В бэкэнд NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
В бэкэнде ржавчины при реализации интерфейса укажите следующее (вместо того, чтобы позволить ему по умолчанию):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Отчеты об ошибках и отладка API для услуг
Когда запускаются отчеты об ошибках (например, с adb bugreport
), они собирают информацию со всей системы, чтобы помочь отладки различных вопросов. Для служб AIDL отчеты об ошибках используют бинарные dumpsys
во всех службах, зарегистрированных в Manage Manager, чтобы сбросить их информацию в отчет об ошибках. Вы также можете использовать dumpsys
в командной строке, чтобы получить информацию из службы с помощью dumpsys SERVICE [ARGS]
. В бэкэндах C ++ и Java вы можете контролировать заказ, в котором службы сбрасываются, используя дополнительные аргументы для addService
. Вы также можете использовать dumpsys --pid SERVICE
, чтобы получить пакет услуги во время отладки.
Чтобы добавить пользовательский вывод в свой сервис, переопределите метод dump
в объекте вашего сервера, как вы реализуете любой другой метод IPC, определенный в файле AIDL. При этом ограничивайте сброс в приложении разрешения android.permission.DUMP
или ограничивайте сброс на конкретные UID.
В бэкэнге Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
В бэкэнде CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
В бэкэнд NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
В бэкэнде ржавчины при реализации интерфейса укажите следующее (вместо того, чтобы позволить ему по умолчанию):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Используйте слабые указатели
Вы можете сохранить слабую ссылку на связующий объект.
В то время как Java поддерживает WeakReference
, она не поддерживает слабые ссылки на переплет на местном уровне.
В бэкэнде CPP слабый тип - wp<IFoo>
.
В бэкэнд NDK используйте ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
В бэкэнге ржавчины используйте WpIBinder
или Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Динамически получить дескриптор интерфейса
Дескриптор интерфейса идентифицирует тип интерфейса. Это полезно при отладке или когда у вас неизвестный связующий.
В Java вы можете получить дескриптор интерфейса с кодом, таким как:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
В бэкэнде CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Backends NDK и Rust не поддерживают эту возможность.
Статически получить дескриптор интерфейса
Иногда (например, при регистрации услуг @VintfStability
) вам нужно знать, каково дескриптор интерфейса статически. В Java вы можете получить дескриптор, добавив код, такой как:
import my.package.IFoo;
... IFoo.DESCRIPTOR
В бэкэнде CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
В бэкэнде NDK (обратите внимание на пространство имен aidl
):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
В бэкэнд ржавчины:
aidl::my::package::BnFoo::get_descriptor()
Enum диапазон
В родных бэкэндах вы можете использовать возможные значения, которые может принять перечисление. Из -за соображений размера кода это не поддерживается в Java.
Для Enum MyEnum
определенной в AIDL, итерация предоставляется следующим образом.
В бэкэнде CPP:
::android::enum_range<MyEnum>()
В бэкэнд NDK:
::ndk::enum_range<MyEnum>()
В бэкэнд ржавчины:
MyEnum::enum_values()
Управление потоком
Каждый экземпляр libbinder
в процессе поддерживает одну тренас. Для большинства вариантов использования это должно быть ровно одной тренасовой, разделенной во всех бэкэндах. Единственное исключение - если код поставщика загружает другую копию libbinder
, чтобы поговорить с /dev/vndbinder
. Это на отдельном узел связующего, поэтому потоковая точка не совместно используется.
Для бэкэнда Java трюк может увеличиться только в размере (потому что она уже началась):
BinderInternal.setMaxThreads(<new larger value>);
Для бэкэнда CPP доступны следующие операции:
// 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();
Точно так же в бэкэнде NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
В бэкэнд ржавчины:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
С Async Rust Backend вам нужны два нить: Binder и Tokio. Это означает, что приложения, использующие Async Rust, нуждаются в специальных соображениях, особенно когда речь идет об использовании join_thread_pool
. См. Раздел о регистрации служб для получения дополнительной информации об этом.
Зарезервированные имена
C ++, Java и Rust оставляют некоторые имена в виде ключевых слов или для конкретного языка. В то время как AIDL не обеспечивает соблюдение ограничений на основе языковых правил, использование имен поля или типов, соответствующих зарезервированному имени, может привести к сбое компиляции для C ++ или Java. Для ржавчины поле или тип переименованы в синтаксис идентификатора RAW, доступный с использованием префикса r#
.
Мы рекомендуем избегать использования зарезервированных имен в ваших определениях AIDL, где это возможно, чтобы избежать негономических привязков или откровенного сбоя компиляции.
Если у вас уже есть зарезервированные имена в ваших определениях AIDL, вы можете безопасно переименовать поля, оставаясь совместимым с протоколом. Возможно, вам придется обновить свой код, чтобы продолжить строительство, но любые уже созданные программы продолжают взаимодействовать.
Имена, которых следует избегать:
,Бэкэнд AIDL является целью для генерации кода заглушки. Always use AIDL files in a particular language with a specific runtime. Depending on the context, you should use different AIDL backends.
In the following table, the stability of the API surface refers to the ability to compile code against this API surface in a way that the code can be delivered independently from the system.img
libbinder.so
binary.
AIDL has the following backends:
Бэкэнд | Язык | API surface | Build systems |
---|---|---|---|
Ява | Ява | SDK or SystemApi (stable*) | Все |
NDK | C++ | libbinder_ndk (stable*) | aidl_interface |
CPP | C++ | libbinder (unstable) | Все |
Ржавчина | Ржавчина | libbinder_rs (stable*) | 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 Android Interface Definition Language (AIDL) .
- 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
, which lets it be stable and portable. APEXes use the binder crate in the standard way on the system side. The Rust portion is bundled into an APEX and shipped inside it. This portion 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 Soong Modules Reference .
Core build system
In any cc_
or java_
Android.bp module
(or in their Android.mk
equivalents), you can specify AIDL ( .aidl
) files as source files. In this case, the Java or 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. You can specify options such as local_include_dirs
(which tells the build system the root path to AIDL files in that module) in these modules under an aidl:
group.
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 details, see the Rust AIDL example .
aidl_interface
Types used with the aidl_interface
build system must be structured. In order to be structured, parcelables must contain fields directly and not be declarations of types defined directly in target languages. For how structured AIDL fits in with stable AIDL, see Structured versus stable AIDL .
Типы
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 or AIDL type | C++ type | NDK type | Rust type |
---|---|---|---|
boolean | bool | bool | bool |
byte 8 | 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 | In: &str Out: String |
android.os.Parcelable | android::Parcelable | Н/Д | Н/Д |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | In: &[T] Out: Vec<T> |
byte[] | std::vector | std::vector 1 | In: &[u8] Out: Vec<u8> |
List<T> | std::vector<T> 2 | std::vector<T> 3 | In: In: &[T] 4Out: Vec<T> |
FileDescriptor | android::base::unique_fd | Н/Д | Н/Д |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
Interface type ( T ) | android::sp<T> | std::shared_ptr<T> 7 | 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 nonprimitive type (including interface types) except arrays. AOSP recommends using array types like T[]
, because 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 nonprimitive 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 (for example, int[3][4]
). In the Java backend, fixed-size arrays are represented as array types.
7. To instantiate a binder SharedRefBase
object, use SharedRefBase::make\<My\>(... args ...)
. This function creates a std::shared_ptr\<T\>
object, which is also managed internally, in case the binder is owned by another process. Creating the object other ways causes double ownership.
8. See also Java or AIDL type byte[]
.
Directionality (in, out, and inout)
When specifying the types of the arguments to functions, you can specify them as in
, out
, or inout
. This controls the direction that information is passed for an IPC call.
The
in
argument specifier indicates data is passed from the caller to the callee. Thein
specifier is the default direction, but if data types can also beout
, then you must specify the direction.The
out
argument specifier means that data is passed from the callee to the caller.The
inout
argument specifier is the combination of both of these. However, we recommend avoiding using the argument specifierinout
. If you useinout
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 normalinout
type receives&mut T
, and a listinout
type receives&mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8 and UTF-16
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 use UTF-8 strings. For more information about the utf8InCpp
annotation, see utf8InCpp .
Nullability
You can annotate types that can be null with @nullable
. For more information about the nullable
annotation, see nullable .
Custom parcelables
A custom parcelable is a parcelable that's implemented manually in a target backend. Use custom parcelables only when you're trying to add support to other languages for an existing custom parcelable which can't be changed.
Here's an example of an AIDL parcelable declaration:
package my.pack.age;
parcelable Foo;
By default, this declares a Java parcelable where my.pack.age.Foo
is a Java class implementing the Parcelable
interface.
For a declaration of a custom CPP backend parcelable in AIDL, use cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
The C++ implementation in my/pack/age/Foo.h
looks like this:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
For a declaration of a custom NDK parcelable in AIDL, use ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
The NDK implementation in android/pack/age/Foo.h
looks like this:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
In Android 15, for declaration of a custom Rust parcelable in AIDL, use rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
The Rust implementation in rust_crate/src/lib.rs
looks like this:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
Then you can use this parcelable as a type in AIDL files, but it won't be generated by AIDL. Provide <
and ==
operators for CPP and NDK backend custom parcelables to use them in union
.
Default values
Structured parcelables can declare per-field default values for primitives, String
fields, 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 nonprimitive types.
In other backends, fields are initialized with default initialized values when default values aren't 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.
Unions
AIDL unions are tagged and their features are similar in all backends. They're constructed to the first field's default value and they have a language-specific way to interact with them:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Java example
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setSringField("abc"); // setter
C++ and NDK example
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Rust example
In Rust, unions are implemented as enums and don't have explicit getters and setters.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
Error handling
The Android OS provides built-in error types for services to use when reporting errors. These are used by binders 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 might not initialize or modify output parameters. Specifically, output parameters might 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's treated specially 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, they can 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:
Бэкэнд | Определение |
---|---|
Ява | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Ржавчина | android/binder_status.h |
Use 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 in Android Rust patterns .
Import 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 and 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).
Implement an interface
To implement an interface, you must inherit from the native stub class. An implementation of an interface is often called a service when it's registered with the service manager or android.app.ActivityManager
and called a callback when it's registered by a client of a service. However, a variety of names are used to describe interface implementations depending on the exact usage. The stub 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 the generated Stub
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(())
}
}
Or with async Rust:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Register and get services
Services in platform Android are usually registered with the servicemanager
process. In addition to the following APIs, some APIs check the service (meaning they return immediately if the service isn't available). Check the corresponding servicemanager
interface for exact details. You can perform these operations only 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
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::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(ndk::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()
}
In the async Rust backend, with a single-threaded runtime:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks run on this thread.
std::future::pending().await
}
One important difference from the other options is that you don't call join_thread_pool
when using async Rust and a single-threaded runtime. This is because you need to give Tokio a thread where it can execute spawned tasks. In the following example, the main thread serves that purpose. Any tasks spawned using tokio::spawn
execute on the main thread.
In the async Rust backend, with a multithreaded runtime:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
With the multithreaded Tokio runtime, spawned tasks don't execute on the main thread. Therefore, it makes more sense to call join_thread_pool
on the main thread so that the main thread isn't idle. You must wrap the call in block_in_place
to leave the async context.
Link 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 becauseDeathRecipient
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 process ID (PID) refers to the Linux process ID of the process that's sending a transaction. The user ID (UI) refers to the Linux user ID. When receiving a one-way call, the calling PID is 0. 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 bug reports run (for example, with adb bugreport
), they collect information from all around the system to aid with debugging various issues. For AIDL services, bug reports use the binary dumpsys
on all services registered with the service manager to dump their information into the bug report. You can also use dumpsys
on the command line 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, override the dump
method in your server object like you're implementing any other IPC method defined in an AIDL file. When doing this, 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<()>
Use weak pointers
You can hold a weak reference to a binder object.
While Java supports WeakReference
, it doesn't support weak binder references at the native layer.
In the CPP backend, the weak type is wp<IFoo>
.
In the NDK backend, use ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
In the Rust backend, use WpIBinder
or Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Dynamically get 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 capability.
Statically get 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.
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_values()
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 is if vendor code loads another copy of libbinder
to talk to /dev/vndbinder
. This is on a separate binder node, so the threadpool isn't shared.
For the Java backend, the threadpool can only increase in size (because it's 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();
With the async Rust backend, you need two threadpools: binder and Tokio. This means that apps using async Rust need special considerations, especially when it comes to the use of join_thread_pool
. See the section on registering services for more information on this.
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 match a reserved name can 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 avoiding 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 might need to update your code to continue building, but any already built programs continue to interoperate.
Names to avoid: