Серверные части AIDL

Бэкенд AIDL — это цель для генерации кода-заглушки. Всегда используйте файлы AIDL на определённом языке с определённой средой выполнения. В зависимости от контекста следует использовать разные бэкенды AIDL.

В следующей таблице стабильность поверхности API относится к возможности компилировать код на этой поверхности API таким образом, чтобы код мог быть доставлен независимо от двоичного файла system.img libbinder.so .

AIDL имеет следующие бэкэнды:

Бэкэнд Язык API поверхность Системы сборки
Ява Ява SDK или SystemApi (стабильная*) Все
НДК С++ libbinder_ndk (стабильная*) aidl_interface
КПП С++ libbinder (нестабильный) Все
Ржавчина Ржавчина libbinder_rs (стабильная*) aidl_interface
  • Эти API-интерфейсы стабильны, но многие из них, например, для управления сервисами, зарезервированы для внутреннего использования платформой и недоступны приложениям. Подробнее об использовании AIDL в приложениях см. в статье Android Interface Definition Language (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 ) в качестве исходных файлов. В этом случае используются бэкенды 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 , должны быть структурированными. Чтобы быть структурированными, парцелляционные объекты должны содержать поля напрямую, а не быть объявлениями типов, определённых непосредственно в целевых языках. О том, как структурированный 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

С помощью бэкенда CPP вы можете выбрать кодировку строк: UTF-8 или UTF-16. Объявите строки как @utf8InCpp String в AIDL, чтобы автоматически преобразовать их в UTF-8. Бэкенды NDK и Rust всегда используют строки UTF-8. Подробнее об аннотации utf8InCpp см. в статье utf8InCpp .

Обнуляемость

Типы, которые могут принимать значение NULL, можно аннотировать с помощью @nullable . Подробнее об аннотации nullable см. в разделе nullable .

Индивидуальные посылки

Пользовательский объект Parcelable — это объект Parcelable, который вручную реализуется в целевом бэкенде. Используйте пользовательские объекты Parcelable только в том случае, если вы пытаетесь добавить поддержку других языков для существующего пользовательского объекта 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. Предоставьте операторы < и == для пользовательских parcelable бэкенда 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 инициализируются полями со значением 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::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 требуются дополнительные значения ошибок, не охватываемые встроенными типами ошибок, можно использовать специальный встроенный тип error, специфичный для сервиса, который позволяет включить значение ошибки, специфичное для сервиса, определяемое пользователем. Эти ошибки, специфичные для сервиса, обычно определяются в интерфейсе 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
КПП 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 уже есть зарезервированные имена, вы можете безопасно переименовывать поля, сохраняя совместимость с протоколом. Для продолжения сборки может потребоваться обновить код, но все уже собранные программы продолжат взаимодействовать.

Имена, которых следует избегать: