Бэкенд AIDL — это цель для генерации кода-заглушки. Всегда используйте файлы AIDL на определенном языке с определенной средой выполнения. В зависимости от контекста следует использовать различные бэкенды AIDL.
В следующей таблице стабильность поверхности API относится к возможности компилировать код на этой поверхности API таким образом, что код может быть доставлен независимо от двоичного файла system.img
libbinder.so
.
AIDL имеет следующие бэкэнды:
Бэкэнд | Язык | API поверхность | Системы сборки |
---|---|---|---|
Ява | Ява | SDK или SystemApi (стабильная*) | Все |
НДК | С++ | libbinder_ndk (стабильная*) | aidl_interface |
КПФ | С++ | libbinder (нестабильный) | Все |
Ржавчина | Ржавчина | libbinder_rs (стабильная*) | aidl_interface |
- Эти поверхности API стабильны, но многие из API, например, для управления службами, зарезервированы для внутреннего использования платформы и недоступны для приложений. Для получения дополнительной информации об использовании AIDL в приложениях см. Android Interface Definition Language (AIDL) .
- Бэкэнд Rust был представлен в Android 12; бэкэнд NDK доступен с Android 10.
- Rust crate построен на основе
libbinder_ndk
, что позволяет ему быть стабильным и переносимым. APEX используют binder crate стандартным образом на стороне системы. Часть Rust упакована в APEX и поставляется внутри него. Эта часть зависит отlibbinder_ndk.so
на системном разделе.
Системы сборки
В зависимости от бэкэнда, есть два способа компилировать AIDL в код-заглушку. Для получения более подробной информации о системах сборки см. Справочник модулей Soong .
Основная система сборки
В любом Android.bp module
cc_
или java_
Android.bp (или в их эквивалентах Android.mk
) можно указать файлы AIDL ( .aidl
) в качестве исходных файлов. В этом случае используются бэкэнды Java или CPP AIDL (не бэкэнд NDK), а классы для использования соответствующих файлов AIDL добавляются в модуль автоматически. В этих модулях в группе aidl:
можно указать такие параметры, как local_include_dirs
(который сообщает системе сборки корневой путь к файлам AIDL в этом модуле).
Бэкэнд Rust предназначен только для использования с Rust. Модули rust_
обрабатываются по-другому, поскольку файлы AIDL не указываются как исходные файлы. Вместо этого модуль aidl_interface
создает rustlib
с именем aidl_interface_name -rust
, с которым можно выполнить линковку. Подробности см. в примере Rust AIDL .
aidl_interface
Типы, используемые с системой сборки aidl_interface
, должны быть структурированы. Чтобы быть структурированными, parcelables должны содержать поля напрямую и не быть объявлениями типов, определенных напрямую в целевых языках. О том, как структурированный 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
, а тип 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. Бэкэнды NDK и Rust всегда используют строки UTF-8. Для получения дополнительной информации об аннотации utf8InCpp
см. utf8InCpp .
Обнуление
Вы можете аннотировать типы, которые могут быть null, с помощью @nullable
. Для получения дополнительной информации об аннотации nullable
см. nullable .
Индивидуальные посылки
Пользовательский parcelable — это parcelable, который вручную реализован в целевом бэкенде. Используйте пользовательские parcelables только тогда, когда вы пытаетесь добавить поддержку других языков для существующего пользовательского parcelable, который нельзя изменить.
Вот пример декларации 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 для объявления пользовательского 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);
Затем вы можете использовать этот parcelable как тип в файлах 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
инициализируются как поля со значением 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.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
или int
-backed enum
и не анализируются связующим компонентом.
В 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 |
КПФ | 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, вы можете безопасно переименовывать поля, сохраняя совместимость с протоколом. Вам может потребоваться обновить код, чтобы продолжить сборку, но любые уже построенные программы продолжат взаимодействовать.
Имена, которых следует избегать: