AIDL-бэкенд — это целевая среда для генерации заглушечного кода. Всегда используйте AIDL-файлы на конкретном языке с конкретной средой выполнения. В зависимости от контекста следует использовать разные AIDL-бэкенды.
В приведенной ниже таблице стабильность API-интерфейса относится к возможности компиляции кода с использованием этого API-интерфейса таким образом, чтобы код мог распространяться независимо от бинарного файла system.img libbinder.so .
В AIDL используются следующие бэкэнды:
| Бэкенд | Язык | Поверхность API | Системы сборки |
|---|---|---|---|
| Java | Java | SDK или SystemApi (стабильная версия*) | Все |
| НДК | C++ | libbinder_ndk (стабильная*) | aidl_interface |
| CPP | C++ | libbinder (нестабильная версия) | Все |
| Ржавчина | Ржавчина | libbinder_rs (стабильная*) | aidl_interface |
- Эти API-интерфейсы стабильны, но многие из них, например, для управления сервисами, зарезервированы для внутреннего использования платформой и недоступны для приложений. Для получения дополнительной информации об использовании AIDL в приложениях см. раздел «Язык определения интерфейса Android (AIDL)» .
- Бэкенд на языке Rust был представлен в Android 12; бэкенд на языке NDK доступен начиная с Android 10.
- Rust-библиотека построена на основе
libbinder_ndk, что обеспечивает её стабильность и переносимость. APEX-приложения используют библиотеку binder стандартным образом на системном уровне. Rust-часть включается в APEX-приложение и поставляется внутри него. Эта часть зависит отlibbinder_ndk.soрасположенного в системном разделе.
Системы сборки
В зависимости от используемой серверной части, существует два способа компиляции AIDL в заглушечный код. Более подробную информацию о системах сборки см. в справочнике по модулям Soong .
Базовая система сборки
В любом Android.bp module cc_ или java_ (или в их эквивалентах Android.mk ) можно указать файлы AIDL ( .aidl ) в качестве исходных файлов. В этом случае используются бэкенды AIDL на Java или C++ (а не на NDK), и классы, использующие соответствующие файлы AIDL, автоматически добавляются в модуль. В этих модулях в группе aidl: можно указать такие параметры, как local_include_dirs (который указывает системе сборки корневой путь к файлам AIDL в этом модуле).
Бэкенд Rust предназначен только для использования с Rust. Модули rust_ обрабатываются иначе: файлы AIDL не указываются в качестве исходных файлов. Вместо этого модуль aidl_interface создает библиотеку rustlib с именем aidl_interface_name -rust , с которой можно выполнить компоновку. Подробности см. в примере Rust AIDL .
aidl_interface
Типы, используемые в системе сборки aidl_interface должны быть структурированными. Для того чтобы быть структурированными, парселблейбы должны содержать поля напрямую, а не быть объявлениями типов, определенных непосредственно в целевых языках. О том, как структурированный AIDL соотносится со стабильным AIDL, см. раздел «Структурированный AIDL против стабильного AIDL» .
Типы
Рассматривайте компилятор aidl как эталонную реализацию типов. При создании интерфейса вызовите команду aidl --lang=<backend> ... чтобы увидеть результирующий файл интерфейса. При использовании модуля aidl_interface вы можете просмотреть вывод в out/soong/.intermediates/ <path to module> / .
| Java или тип AIDL | тип C++ | Тип НДК | Тип ржавчины |
|---|---|---|---|
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
При использовании бэкенда C++ вы можете выбрать, будут ли строки в кодировке 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-объект 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, реализующего интерфейс Parcelable в 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 Parcelable в 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 .
Значения по умолчанию
Структурированные объекты Parcelable могут задавать значения по умолчанию для каждого поля для примитивных типов данных, String полей и массивов этих типов.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
В Java-бэкенде, если значения по умолчанию отсутствуют, поля инициализируются нулевыми значениями для примитивных типов и null для непримитивных типов.
В других бэкендах поля инициализируются значениями по умолчанию, если значения по умолчанию не определены. Например, в бэкенде C++ поля String инициализируются пустой строкой, а поля List<T> — пустым vector<T> . Поля с аннотацией @nullable инициализируются значениями null.
Профсоюзы
Объединения 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.setStringField("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::makeFoo::s>tringField("<abc>")); // maketag(value)
Пример на Rust
В Rust объединения реализованы как перечисления (enums) и не имеют явных геттеров и сеттеров.
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::String>Field(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 исключения не используются. В бэкенде на C++ используется android::binder::Status . В бэкенде NDK используется ndk::ScopedAStatus . Каждый метод, сгенерированный AIDL, возвращает один из этих объектов, представляющих статус метода. В бэкенде Rust используются те же значения кодов исключений, что и в NDK, но они преобразуются в нативные ошибки Rust ( StatusCode , ExceptionCode ) перед передачей пользователю. Для ошибок, специфичных для сервиса, возвращаемый Status или ScopedAStatus использует EX_SERVICE_SPECIFIC вместе с ошибкой, определенной пользователем.
Встроенные типы ошибок можно найти в следующих файлах:
| Бэкенд | Определение |
|---|---|
| Java | 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;
Или в бэкэнде на языке C++:
#include <my/package/IFoo.h>
Или в бэкэнде NDK (обратите внимание на дополнительное пространство имен aidl ):
#include <aidl/my/package/IFoo.h>
Или в бэкенде на Rust:
use my_package::aidl::my::package::IFoo;
Хотя в Java можно импортировать вложенные типы, в бэкендах C++ и NDK необходимо включать заголовочный файл для их корневого типа. Например, при импорте вложенного типа Bar определенного в my/package/IFoo.aidl ( IFoo является корневым типом файла), необходимо включить <my/package/IFoo.h> для бэкенда C++ (или <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() { ... }
}
В бэкэнде C++:
#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.
На языке 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"));
В бэкэнде C++:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// return if service is started now
status_t err = ch<eckS>erviceIFoo(String16("s&ervice-name"), myService);
// waiting until service comes up (new in Android 11)
myServ<ice >= waitForServiceIFoo(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
mySe<rvic>e = waitForDeclaredServiceIFoo(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
}
Одно важное отличие от других вариантов заключается в том, что при использовании асинхронного Rust и однопоточной среды выполнения не требуется вызывать join_thread_pool . Это связано с тем, что 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 .
Ссылка на смерть
Вы можете запросить уведомление о прекращении работы службы, использующей Binder. Это поможет избежать утечки прокси-объектов обратного вызова или упростит восстановление после ошибок. Выполняйте эти вызовы через прокси-объекты Binder.
- В Java используйте
android.os.IBinder::linkToDeath. - В бэкенде на C++ используйте
android::IBinder::linkToDeath. - В бэкенде NDK используйте
AIBinder_linkToDeath. Всегда используйтеAIBinder_DeathRecipient_setOnUnlinkedдля управления временем жизни cookie-файла получателя смерти. - В бэкенде Rust создайте объект
DeathRecipient, затем вызовитеmy_binder.link_to_death(&mut my_death_recipient). Обратите внимание, что посколькуDeathRecipientявляется владельцем функции обратного вызова, вы должны поддерживать этот объект в активном состоянии до тех пор, пока хотите получать уведомления.
Информация о звонящем
При получении вызова ядра через Binder информация о вызывающем процессе доступна в нескольких API. Идентификатор процесса (PID) относится к идентификатору процесса Linux, отправляющего транзакцию. Идентификатор пользователя (UI) относится к идентификатору пользователя Linux. При получении одностороннего вызова PID вызывающего процесса равен 0. Вне контекста транзакции Binder эти функции возвращают PID и UID текущего процесса.
В бэкенде на Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
В бэкэнде C++:
... = 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 в объекте сервера так же, как и любой другой метод межпроцессного взаимодействия, определенный в файле AIDL. При этом ограничьте вывод только разрешениями приложения android.permission.DUMP или ограничьте вывод определенными UID.
В бэкенде на Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
В бэкэнде C++:
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 , она не поддерживает слабые ссылки на связывающие объекты на нативном уровне.
В бэкенде на языке C++ слабым типом является 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();
В бэкэнде C++:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Бэкенды NDK и Rust не поддерживают эту возможность.
Статически получить дескриптор интерфейса
Иногда (например, при регистрации сервисов @VintfStability ) необходимо статически знать дескриптор интерфейса. В Java получить дескриптор можно, добавив следующий код:
import my.package.IFoo;
... IFoo.DESCRIPTOR
В бэкэнде C++:
#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, итерация предоставляется следующим образом.
В бэкэнде C++:
::android::enum_range<MyEnum>()
В бэкэнде NDK:
::ndk::enum_range<MyEnum>()
В бэкенде на Rust:
MyEnum::enum_values()
Управление потоками
Каждый экземпляр libbinder в процессе поддерживает один пул потоков. В большинстве случаев это должен быть ровно один пул потоков, общий для всех бэкендов. Единственное исключение — если код поставщика загружает еще одну копию libbinder для взаимодействия с /dev/vndbinder . В этом случае пул потоков находится на отдельном узле binder, поэтому он не является общим.
В Java-бэкенде размер пула потоков может только увеличиваться (поскольку он уже запущен):
BinderInternal.setMaxThreads(<new larger value>);
Для бэкенда на языке C++ доступны следующие операции:
// 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 уже есть зарезервированные имена, вы можете безопасно переименовывать поля, сохраняя при этом совместимость с протоколом. Возможно, вам потребуется обновить код для продолжения сборки, но уже собранные программы будут продолжать взаимодействовать друг с другом.
Имена, которых следует избегать: