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

Серверная часть AIDL является целью для генерации кода-заглушки. При использовании файлов AIDL вы всегда используете их на определенном языке с определенной средой выполнения. В зависимости от контекста вам следует использовать разные серверные части AIDL.

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

AIDL имеет следующие серверные части:

Бэкэнд Язык поверхность API Системы сборки
Ява Ява SDK/SystemApi (стабильная*) все
НДК С++ libbinder_ndk (стабильная*) helpl_interface
CPP С++ libbinder (нестабильный) все
Ржавчина Ржавчина libbinder_rs (стабильная*) helpl_interface
  • Эти поверхности API стабильны, но многие из API, например для управления службами, зарезервированы для внутреннего использования платформы и недоступны приложениям. Дополнительную информацию о том, как использовать AIDL в приложениях, см. в документации для разработчиков .
  • Бэкэнд Rust был представлен в Android 12; Серверная часть NDK доступна начиная с Android 10.
  • Крейт Rust построен на основе libbinder_ndk , что делает его стабильным и переносимым. APEX используют ящик для подшивок так же, как и все остальные на стороне системы. Часть Rust упакована в APEX и поставляется внутри него. Это зависит от libbinder_ndk.so в системном разделе.

Системы сборки

В зависимости от серверной части существует два способа компиляции AIDL в код-заглушку. Более подробную информацию о системах сборки см. в справочнике по модулю Soong .

Основная система сборки

В любом модуле cc_ или java_ Android.bp (или их эквивалентах Android.mk ) файлы .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 сочетается со стабильным AIDL, см. в разделе Структурированный и стабильный AIDL .

Типы

Компилятор aidl можно рассматривать как эталонную реализацию типов. Когда вы создаете интерфейс, вызовите aidl --lang=<backend> ... чтобы просмотреть полученный файл интерфейса. Когда вы используете модуль aidl_interface , вы можете просмотреть выходные данные в out/soong/.intermediates/<path to module>/ .

Тип Java/AIDL Тип С++ Тип НДК Тип ржавчины
логическое значение логическое значение логическое значение логическое значение
байт 8 int8_t int8_t i8
голец char16_t char16_t u16
интервал int32_t int32_t я32
длинный int64_t int64_t я64
плавать плавать плавать ф32
двойной двойной двойной ф64
Нить андроид::String16 станд::строка В: &str
Выход: строка
android.os.Parcelable Android::Parcelable Н/Д Н/Д
IBinder Android::IBinder ndk::SpAIBinder связыватель::SpIBinder
Т[] std::vector<T> std::vector<T> В: &[Т]
Вышел: Век<T>
байт[] std::vector<uint8_t> std::vector<int8_t> 1 В: &[u8]
Вышел: Век<u8>
Список<T> std::vector<T> 2 std::vector<T> 3 Вышел: &[T] 4
Вышел: Век<T>
Файловыйдескриптор Android::base::unique_fd Н/Д Н/Д
Дескриптор файла посылки android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor связыватель::посылка::ParcelFileDescriptor
тип интерфейса (Т) андроид::sp<T> std::shared_ptr<T> 7 связующее::Сильный
парцеллируемый тип (Т) Т Т Т
тип соединения (Т) 5 Т Т Т
Т[Н] 6 std::array<T, N> std::array<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 это направление по умолчанию, которое указывает на то, что данные передаются от вызывающего объекта к вызываемому. out означает, что данные передаются от вызываемого абонента к вызывающему. inout представляет собой комбинацию обоих из них. Однако команда Android рекомендует избегать использования спецификатора аргумента inout . Если вы используете inout с интерфейсом с поддержкой версий и более старым вызываемым объектом, дополнительные поля, присутствующие только в вызывающем объекте, сбрасываются до значений по умолчанию. Что касается Rust, обычный inout тип получает &mut Vec<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);
}

UTF8/UTF16

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

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

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

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

Пользовательский объект — это объект, который вручную реализуется в целевой серверной части. Используйте пользовательские участки только в том случае, если вы пытаетесь добавить поддержку других языков для существующего пользовательского участка, который нельзя изменить.

Чтобы объявить пользовательскую посылку, чтобы AIDL знал о ней, объявление 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).

Внедрение услуг

Чтобы реализовать службу, вы должны наследовать от собственного класса-заглушки. Этот класс считывает команды из драйвера связывателя и выполняет реализуемые вами методы. Представьте, что у вас есть такой файл AIDL:

    package my.package;
    interface IFoo {
        int doFoo();
    }

В Java вы должны выйти из этого класса:

    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 will 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, который отправляет транзакцию. UID (или идентификатор пользователя) относится к идентификатору пользователя 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, вы можете безопасно переименовывать поля, сохраняя при этом совместимость с протоколом; вам может потребоваться обновить код, чтобы продолжить сборку, но любые уже созданные программы продолжат взаимодействовать.

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