Backendy AIDL

Backend AIDL jest miejscem docelowym generowania kodu pośredniego. Podczas korzystania z plików AIDL zawsze używać ich w określonym języku w konkretnym środowisku wykonawczym. W zależności od w kontekście, należy używać różnych backendów AIDL.

W tabeli poniżej stabilność interfejsu API odnosi się do możliwości kompilowania kodu w sposób umożliwiający jego dostarczanie niezależnie od binarnego pliku system.img libbinder.so.

AIDL ma następujące backendy:

Backend Język Interfejs API Systemy kompilacji
Java Java SDK/SystemApi (stabilny*) wszystkie
NDK C++ libbinder_ndk (stabilna*) interfejs AIDl
CPP C++ libbinder (niestabilny) wszystkie
Rust Rust libbinder_rs (stabilna wersja*) interfejs AIDl
  • Te interfejsy API są stabilne, ale wiele z nich, np. interfejsy API do zarządzania usługami, jest zarezerwowanych do użytku wewnętrznego platformy i niedostępnych dla aplikacji. Więcej informacji o korzystaniu z AIDL w aplikacjach znajdziesz tutaj: dokumentację dla programistów.
  • Backend Rust został wprowadzony w Androidzie 12, a backend NDK jest dostępny od Androida 10.
  • Skrzynka Rust jest zbudowana na konstrukcji libbinder_ndk, dzięki czemu może być stabilny i przenośny. APEX korzysta z Binder Crate w taki sam sposób jak każda inna osoba po stronie systemu. Część Rust jest zapakowana w APEX i wysyłana w jej wnętrzu. To zależy od libbinder_ndk.so na partycji systemowej.

Systemy kompilacji

W zależności od backendu możesz skompilować AIDL w postaci kodu stuba na 2 sposoby. Więcej informacji o systemach kompilacji: Odniesienie do modułu utworu.

Podstawowy system kompilacji

W dowolnym module cc_ lub java_ w pliku Android.bp (lub w ich odpowiednikach Android.mk) jako pliki źródłowe można określić pliki .aidl. W tym przypadku kod Java/CPP używane są backendy AIDL (nie backend NDK), a klasy do użycia funkcji odpowiednie pliki AIDL są automatycznie dodawane do modułu. Opcje takie jak local_include_dirs, które informują system kompilacji o ścieżce do katalogu głównego plików AIDL w tym module, można określić w tych modułach w grupie aidl:. Pamiętaj, że backend Rust jest do użytku tylko z Rust. rust_ moduł obsługiwane w inny sposób, ponieważ pliki AIDL nie są określone jako pliki źródłowe. Zamiast tego moduł aidl_interface tworzy rustlib o nazwie <aidl_interface name>-rust, które mogą być połączone. Więcej informacji: przykład Rust AIDL.

interfejs AIDl

Typy używane w tym systemie kompilacji muszą być uporządkowane. Aby były strukturyzowane, obiekty parcelable muszą zawierać pola bezpośrednio, a nie być deklaracją typów zdefiniowanych bezpośrednio w językach docelowych. Informacje o tym, jak uporządkowana wersja AIDL różni się od stabilnej wersji AIDL, znajdziesz w artykule Uporządkowana i stabilna wersja AIDL.

Typy

Kompilator aidl możesz traktować jako referencyjną implementację typów. Podczas tworzenia interfejsu wywołaj funkcję aidl --lang=<backend> ..., aby wyświetlić wynikowy plik interfejsu. Korzystając z modułu aidl_interface, możesz zobaczyć danych wyjściowych w narzędziu out/soong/.intermediates/<path to module>/.

Typ Java/AIDL Typ C++ Typ NDK Rodzaj rdzy
wartość logiczna bool wartość logiczna wartość logiczna
bajt int8_t int8_t i8
char znak16_t char16_t U16
int int32_t int32_t i32
długi int64_t int64_t i64
liczba zmiennoprzecinkowa liczba zmiennoprzecinkowa float f32
podwójny podwójny podwójny F64
Ciąg znaków android::Ciąg16 std::string Ciąg znaków
android.os.Parcelable, android::Parcelable Nie dotyczy Nie dotyczy
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T>, Wejście: &[T]
Wyjście: wektor<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 Przy: &[u8]
Wyjście: Vec<u8>
Lista<T> std::vector<T>2 std::vector<T>3 Wejście: &[T]4
Wyjście: Vec<T>
Deskryptor pliku android::base::Unique_fd Nie dotyczy binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
typ interfejsu (T), android::sp<T> std::shared_ptr<T>7 binder::Silne
typ parcelable (T) T T T
typ zjednoczenia (T)5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. W Androidzie 12 lub nowszym tablice bajtów używają typu uint8_t zamiast int8_t ze względów zgodności.

2. Backend C++ obsługuje język List<T>, gdzie T to wartość z wartości String, IBinder, ParcelFileDescriptor lub przesyłka. Na Androidzie 13 lub wyższy, T może być dowolnym typem niepodstawowym (w tym typów interfejsów), z wyjątkiem tablic. AOSP zaleca używają typów tablic, takich jak T[], ponieważ działają one we wszystkich backendach.

3. Backend NDK obsługuje List<T>, gdzie T to jeden z tych typów: String, ParcelFileDescriptor lub parcelable. Na Androidzie 13 lub wyższym, T może być dowolnym typem niepodstawowym z wyjątkiem tablic.

4. Typy przekazywane w kodzie Rust są różne w zależności od tego, czy są danymi wejściowymi (argumentami) lub danymi wyjściowymi (wartość zwrócona).

5. Typy sumowania są obsługiwane w Androidzie 12 oraz wyżej. .

6. Na Androidzie 13 i nowszych tablice o stałym rozmiarze są obsługiwane. Tablice o stałym rozmiarze mogą mieć wiele wymiarów (np. int[3][4]). W backendzie Java tablice o stałym rozmiarze są reprezentowane jako typy tablic.

7. Aby utworzyć instancję obiektu binder SharedRefBase, użyj instrukcji SharedRefBase::make\<My\>(... args ...). Ta funkcja tworzy obiekt std::shared_ptr\<T\>, którym również zarządza się wewnętrznie, jeśli binder jest własnością innego procesu. Tworzenie obiektu w inny sposób powoduje podwójne przypisanie własności.

Kierunek (wejście/wyjście/wyjście)

Określając typy argumentów funkcji, możesz określić je jako in, out lub inout. To ustawienie określa kierunek przekazywania informacji w ramach wywołania IPC. Domyślnym kierunkiem jest in, który wskazuje, że dane są przekazywane z rozmówcy do rozmówcy. out oznacza, że dane są przekazywane z do dzwoniącego. inout to połączenie obu tych elementów. Jednak Zespół Androida odradza korzystanie ze specyfikatora argumentów inout. Jeśli używasz inout z interfejsem z wersją i starszym wywoływanym elementem, dodatkowe pola, które są obecne tylko w wywoływanym elemencie, zostaną zresetowane do wartości domyślnych. W przypadku języka Rust normalny typ inout otrzymuje &mut Vec<T> oraz typ listy inout otrzymuje &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

W backendzie CPP możesz wybrać, czy ciągi mają być w formacie utf-8 czy utf-16. Zadeklaruj ciągi znaków jako @utf8InCpp String w AIDL, aby automatycznie konwertować je na UTF-8. Backendy NDK i Rust zawsze używają ciągów znaków utf-8. Więcej informacji o annotacjach utf8InCpp znajdziesz w artykule Annotacje w pliku AIDL.

Dopuszczalność wartości null

Typy, które mogą być puste, możesz oznaczyć za pomocą @nullable. Więcej informacji o annotacjach nullable znajdziesz w artykule Annotacje w pliku AIDL.

Obiekty parcelable niestandardowe

Niestandardowy obiekt do zapakowania to obiekt do zapakowania, który jest implementowany ręcznie w docelowym backendzie. Używaj niestandardowych obiektów Parcelable tylko wtedy, gdy chcesz dodać obsługę innych języków dla istniejącego niestandardowego obiektu Parcelable, którego nie można zmienić.

Aby zadeklarować działkę niestandardową, o której wie AIDL, deklaracja parcelable wygląda w ten sposób:

    package my.pack.age;
    parcelable Foo;

Domyślnie deklaruje to obiekt Java parcelable, gdzie my.pack.age.Foo jest klasą Java implementującą interfejs Parcelable.

Aby zadeklarować niestandardowy backend w C++ w formacie parcelable w pliku AIDL, użyj: cpp_header:

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

Implementacja w C++ w my/pack/age/Foo.h wygląda tak:

    #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);
    };

Aby zadeklarować niestandardowy obiekt NDK w pliku AIDL, użyj elementu ndk_header:

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

Implementacja NDK w android/pack/age/Foo.h wygląda tak:

    #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);
    };

W Androidzie 15 deklaracja niestandardowego pakietu Rust w AIDL: rust_type:

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

Implementacja Rust w rust_crate/src/lib.rs wygląda tak:

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);

Następnie możesz użyć tego typu w plikach AIDL, ale nie będzie on generowany przez AIDL. Podaj operatory < i == dla backendu CPP/NDK niestandardowe parcelable, aby używać ich w union.

Wartości domyślne

Strukturalne obiekty z możliwością dzielenia na części mogą deklarować wartości domyślne dla poszczególnych pól dla typów prymitywnych,String i tablic tych typów.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

W backendzie Java, gdy brakuje wartości domyślnych, pola są inicjowane jako wartości zerowe w przypadku typów prymitywnych i null w przypadku typów nieprzymitywów.

W innych backendach pola są inicjowane za pomocą wartości domyślnych, jeśli te nie są zdefiniowane. Na przykład w backendzie C++ pola String są inicjowane jako puste ciągi znaków, a pola List<T> jako puste vector<T>. Pola @nullable są inicjowane jako pola o wartości null.

Obsługa błędów

System operacyjny Android udostępnia wbudowane typy błędów, których usługi mogą używać do raportowania . Są one używane przez segregator i mogą być używane przez wszystkie usługi implementujące za pomocą interfejsu Binder. Ich użycie jest dobrze udokumentowane w definicji AIDL i nie wymaga żadnego zdefiniowanego przez użytkownika stanu ani typu zwracanych danych.

Parametry wyjściowe z błędami

Gdy funkcja AIDL zgłasza błąd, może nie inicjować ani modyfikować parametrów wyjściowych. W szczególności parametry wyjściowe mogą ulec zmianie, jeśli błąd wystąpi podczas rozpakowywania, a nie podczas przetwarzania samej transakcji. Ogólnie, gdy funkcja AIDL zwraca błąd, wszystkie parametry inoutout, a także wartość zwracana (która w niektórych backendach działa jak parametr out) powinny być uważane za nieokreślone.

Jakich wartości błędów należy użyć

Wiele wbudowanych wartości błędów można zastosować w dowolnym interfejsie AIDL, ale niektóre są traktowane w specjalny sposób. Na przykład EX_UNSUPPORTED_OPERATIONEX_ILLEGAL_ARGUMENT można używać do opisu stanu błędu, ale nie można używać EX_TRANSACTION_FAILED, ponieważ jest on traktowany w szczególny sposób przez infrastrukturę podstawową. Więcej informacji o tych wbudowanych wartościach znajdziesz w definicjach na zapleczu.

Jeśli interfejs AIDL wymaga dodatkowych wartości błędów, które nie są objęte wbudowanymi typami błędów, można użyć specjalnego wbudowanego błędu związanego z danym serwisem, który umożliwia uwzględnienie wartości błędu określonej przez użytkownika. Błędy związane z poszczególnymi usługami są zwykle zdefiniowane w sekcji interfejsu AIDL jako enum wspierany przez const int lub int i nie są analizowane przez separatora.

W Javie błędy są mapowane na wyjątki, takie jak android.os.RemoteException. W przypadku wyjątków związanych z usługą Java używa wartości android.os.ServiceSpecificException oraz błędu zdefiniowanego przez użytkownika.

Kod natywny na Androida nie używa wyjątków. Backend CPP korzysta z android::binder::Status. Backend NDK używa ndk::ScopedAStatus. Każda metoda wygenerowana przez AIDL zwraca jeden z tych stanów, który reprezentuje stan metody. Backend Rust używa tych samych wartości kodu wyjątku co NDK, ale konwertuje je na natywne błędy Rusta (StatusCode, ExceptionCode) przed i dostarczaniu ich użytkownikowi. W przypadku błędów dotyczących konkretnej usługi zwracany jest błąd Status lub ScopedAStatus używa EX_SERVICE_SPECIFIC razem z zdefiniowany przez użytkownika.

Typy błędów wbudowanych znajdziesz w tych plikach:

Backend Definicja
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

Używanie różnych backendów

Te instrukcje dotyczą kodu platformy Android. W tych przykładach użyto zdefiniowanego typu my.package.IFoo. Instrukcje dotyczące korzystania z back-endu Rust znajdziesz na stronie Rust AIDL example (przykład Rust AIDL) w sekcji Rust Patterns for Android (wzorce Rust dla Androida).

Typy importu

Niezależnie od tego, czy zdefiniowany typ to interfejs, parcelable czy union, to w Javie:

import my.package.IFoo;

Lub na zapleczu CPP:

#include <my/package/IFoo.h>

Możesz też użyć backendu NDK (zwróć uwagę na dodatkową przestrzeń nazw aidl):

#include <aidl/my/package/IFoo.h>

Lub w backendzie Rust:

use my_package::aidl::my::package::IFoo;

Chociaż można zaimportować typ zagnieżdżony w Javie, w backendach CPP/NDK musisz dołącz nagłówek dla typu katalogu głównego. Na przykład podczas importowania typu zagnieżdżonego Bar jest zdefiniowany w my/package/IFoo.aidl (IFoo to typ główny funkcji ), musisz dodać <my/package/IFoo.h> dla backendu CPP (lub <aidl/my/package/IFoo.h> dla backendu NDK).

Wdrażanie usług

Aby wdrożyć usługę, musisz odziedziczyć ją z natywnej klasy wersji pośredniej. Ta klasa odczytuje polecenia z sterownika bindera i wykonuje zaimplementowane przez Ciebie metody. Wyobraź sobie, że masz plik AIDL podobny do tego:

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

W języku Java musisz rozszerzyć tę klasę:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

W backendzie CPP:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

W backendzie NDK (zwróć uwagę na dodatkową domenę aidl):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

W backendzie 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(())
        }
    }

Lub z asynkronicznym Rustem:

    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(())
        }
    }

Zarejestruj się i pobierz usługi

Usługi na platformie Android są zwykle rejestrowane za pomocą procesu servicemanager. Oprócz wymienionych interfejsów API niektóre interfejsy API sprawdzają usługę (co oznacza, że zwracają dane natychmiast, jeśli usługa jest niedostępna). Szczegółowe informacje znajdziesz w odpowiednim interfejsie servicemanager. Te operacje można wykonywać tylko podczas kompilowania danych bezpośrednio na platformie Android.

W Javie:

    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"));

W backendzie 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"));

W backendzie NDK (zwróć uwagę na dodatkową domenę 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")));

W backendzie 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()
}

W asynchronicznym backendzie Rust ze środowiskiem wykonawczym jednowątkowym:

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
}

Jedną ważną różnicą w stosunku do innych opcji jest to, że nie wywołujemy funkcji join_thread_pool, gdy używamy asynchronicznego Rusta i jednowątkowego środowiska wykonawczego. To jest ponieważ musisz dać Tokio wątek, w którym będzie mógł wykonywać wygenerowane zadania. W w tym przykładzie, wątek główny będzie służyć do tego. Wszystkie zadania utworzone za pomocą funkcji tokio::spawn będą wykonywane w wątku głównym.

W asynchronicznym backendzie Rust z wielowątkowym środowiskiem wykonawczym:

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();
    });
}

W wielowątkowym środowisku wykonawczym Tokio wygenerowane zadania nie są wykonywane w głównym w wątku. Dlatego lepiej wywoływać metodę join_thread_pool w domenie głównej aby wątek główny nie był tylko nieaktywny. Aby opuścić kontekst asynchroniczny, musisz zakończyć wywołanie za pomocą funkcji block_in_place.

Możesz poprosić o powiadomienie, gdy usługa hostująca binder przestanie działać. Pomoże to uniknąć wycieku danych w przypadku wywołania zwrotnego lub pomoże w naprawieniu błędów. Wykonuj te wywołania na obiektach serwera proxy powiązań.

  • W języku Java użyj android.os.IBinder::linkToDeath.
  • Na backendzie CPP użyj android::IBinder::linkToDeath.
  • W backendzie NDK użyj AIBinder_linkToDeath.
  • W backendzie Rust utwórz obiekt DeathRecipient, a następnie wywołaj funkcję my_binder.link_to_death(&mut my_death_recipient). Pamiętaj, że ponieważ Wywołanie zwrotne należy do DeathRecipient. Musisz pozostawić ten obiekt aktywny tak długo, chcesz otrzymywać powiadomienia.

Informacje o rozmówcy

Po otrzymaniu wywołania funkcji Binder jądra informacje o rozmówcy są dostępne w kilka interfejsów API. PID (lub identyfikator procesu) odnosi się do identyfikatora procesu Linux który wysyła transakcję. Identyfikator UID (lub User ID) odnosi się do Identyfikator użytkownika Linuksa. W przypadku połączenia jednokierunkowego identyfikator PID wywołania ma wartość 0. Kiedy poza kontekstem transakcji powiązanej, funkcje te zwracają PID i UID w bieżącym procesie.

W backendzie Java:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

W backendzie CPP:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

W backendzie NDK:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

Podczas implementowania interfejsu w backendzie Rust określ te opcje (zamiast korzystania z wartości domyślnych):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Raporty o błędach i interfejs API debugowania usług

Gdy raporty o błędach są uruchamiane (np. za pomocą adb bugreport), zbierają informacje z całego systemu, aby ułatwić debugowanie różnych problemów. W przypadku usług AIDL zgłoszenia błędów używają pliku binarnego dumpsys we wszystkich usługach zarejestrowanych u menedżera usługi w celu umieszczania swoich danych w raport o błędzie. Aby uzyskać informacje z usługi z użyciem dumpsys SERVICE [ARGS], możesz też użyć dumpsys w wierszu poleceń. W backendach C++ i Javy za pomocą dodatkowych argumentów może kontrolować kolejność, w jakiej usługi są zapisywane do addService. Za pomocą parametru dumpsys --pid SERVICE możesz też uzyskać identyfikator PID podczas debugowania.

Aby dodać niestandardowe dane wyjściowe do usługi, możesz zastąpić metodę dump w obiekcie serwera, tak jak implementujesz dowolną inną metodę IPC zdefiniowaną w pliku AIDL. W tym celu należy ograniczyć zrzuty do uprawnień aplikacji android.permission.DUMP lub do konkretnych identyfikatorów UID.

W backendzie w Javie:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

W backendzie CPP:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

W backendzie NDK:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

Podczas implementowania interfejsu w backendzie Rust określ te opcje (zamiast korzystania z wartości domyślnych):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

Używaj słabych wskaźników

Możesz przechowywać słabe odwołanie do obiektu wiązania.

Java obsługuje język WeakReference, ale nie obsługuje słabych odwołań do powiązań w warstwie natywnej.

W backendzie CPP typ słaby to wp<IFoo>.

W backendzie NDK użyj polecenia ScopedAIBinder_Weak:

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

W backendzie Rust używasz WpIBinder lub Weak<IFoo>:

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Dynamiczne pobieranie deskryptora interfejsu

Deskryptor interfejsu określa typ interfejsu. To jest przydatne podczas debugowania lub nieznanego powiązania.

W języku Java możesz uzyskać opis interfejsu za pomocą kodu takiego jak:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

W backendzie CPP:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

Backendy NDK i Rust nie obsługują tej funkcji.

Statyczne pobieranie deskryptora interfejsu

Czasami (na przykład podczas rejestrowania usług @VintfStability) trzeba statyczny deskryptor interfejsu. W Javie możesz uzyskać deskryptor, dodając kod, np.

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

W backendzie CPP:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

W backendzie NDK (zwróć uwagę na dodatkową domenę aidl):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

W backendzie Rust:

    aidl::my::package::BnFoo::get_descriptor()

Zakres typu wyliczeniowego

W natywnych backendach możesz przejść przez możliwe wartości typu wyliczeniowego. Ze względu na rozmiar kodu ta funkcja nie jest obsługiwana w Javie.

W przypadku typu wyliczenia MyEnum zdefiniowanego w pliku AIDL iteracja jest podawana w ten sposób:

W backendzie CPP:

    ::android::enum_range<MyEnum>()

W backendzie NDK:

   ::ndk::enum_range<MyEnum>()

W backendzie Rust:

    MyEnum::enum_values()

Zarządzanie wątkami

Każda instancja libbinder w procesie utrzymuje jeden pulę wątków. W większości przypadków powinien to być dokładnie jeden zbiornik wątków współdzielony przez wszystkie backendy. Jedynym wyjątkiem jest sytuacja, gdy kod dostawcy może wczytać kolejną kopię libbinder, aby rozmawiać z /dev/vndbinder. Ponieważ jest to oddzielny węzeł bindera, pula wątków nie jest współdzielona.

W backendzie Java pula wątków może się tylko zwiększać (ponieważ już rozpoczęte):

    BinderInternal.setMaxThreads(<new larger value>);

W przypadku backendu CPP dostępne są te operacje:

    // 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();

Podobnie w backendzie NDK:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

W backendzie Rust:

    binder::ProcessState::start_thread_pool();
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    binder::ProcessState::join_thread_pool();

W asynchronicznym backendzie Rust potrzebujesz 2 pul wątków: Binder i Tokio. Oznacza to, że aplikacje korzystające z asynchronicznego środowiska Rust wymagają szczególnych zwłaszcza jeśli chodzi o korzystanie z tagu join_thread_pool. Przeczytaj sekcję na temat rejestracji usług, by dowiedzieć się więcej na ten temat.

Zarezerwowane nazwy

C++, Java i Rust zarezerwowały niektóre nazwy jako słowa kluczowe lub do użycia w konkretnym języku. Chociaż AIDL nie wymusza ograniczeń na podstawie reguł językowych, nazw pól lub typów, które pasują do zarezerwowanej nazwy, mogą spowodować kompilację w przypadku języka C++ lub Javy. W przypadku języka Rust nazwa pola lub typu jest zmieniana za pomocą metody „nieprzetworzony identyfikator” składnia jest dostępna za pomocą prefiksu r#.

Zalecamy, aby w miarę możliwości unikać używania zarezerwowanych nazw w definicjach AIDL, aby uniknąć nieergonomicznych powiązań lub całkowitego niepowodzenia kompilacji.

Jeśli masz już zarezerwowane nazwy w definicjach AIDL, możesz bezpiecznie zmienić nazwy pól, zachowując zgodność z protokołem. Aby kontynuować kompilowanie, być może trzeba będzie zaktualizować kod, ale już skompilowane programy będą nadal ze sobą współpracować.

Nazwy, których należy unikać: