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 poniższej tabeli stabilność platformy interfejsu API odnosi się do zdolności
skompilować kod na tej powierzchni interfejsu API,
niezależnie od pliku binarnego 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 (stabilny*) | interfejs AIDl |
CPP | C++ | libbinder (niestabilny) | wszystkie |
Rust | Rust | libbinder_rs (stabilny*) | interfejs AIDl |
- Te interfejsy API są stabilne, ale wiele interfejsów API, na przykład usług do zarządzania, są zarezerwowane do użytku wewnętrznego na platformie i nie są dostępne aplikacji. Więcej informacji o korzystaniu z AIDL w aplikacjach znajdziesz tutaj: dokumentację dla programistów.
- Backend Rust został wprowadzony w Androidzie 12. Backend NDK jest dostępny w Androidzie 10.
- Skrzynka Rust jest zbudowana na konstrukcji
libbinder_ndk
, dzięki czemu może być stabilny i przenośny. APEX używa segregatorów tak samo jak inni po stronie systemu. Część Rust jest łączona w APEX i wysyłana w środku. Zależy to od elementulibbinder_ndk.so
na partycji systemowej.
Systemy kompilacji
W zależności od backendu istnieją dwa sposoby skompilowania AIDL do wersji pośredniej w kodzie. Więcej informacji o systemach kompilacji: Odniesienie do modułu utworu.
Podstawowy system kompilacji
W dowolnym module Android.bp cc_
lub java_
(lub w jego odpowiedniku Android.mk
):
Pliki .aidl
można określić jako pliki źródłowe. 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
na przykład local_include_dirs
, który informuje system kompilacji, gdzie znajduje się ścieżka główna
Pliki AIDL w tym module można określić w tych modułach za pomocą tagu aidl:
grupy reklam. 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 struktura była uporządkowany, Obiekty parcelable muszą zawierać pola bezpośrednio i nie mogą być deklaracjami typów bezpośrednio w językach docelowych. Jeśli ustrukturyzowany AIDL pasuje do stabilnej wersji AIDL, patrz Uporządkowane a stabilne AIDL.
Typy
Kompilator aidl
możesz traktować jako referencyjną implementację typów.
Po utworzeniu interfejsu wywołaj aidl --lang=<backend> ...
, aby zobaczyć
utworzony 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 | wartość logiczna | wartość logiczna | wartość logiczna |
bajt | int8_t | int8_t | i8 |
znak | znak16_t | znak16_t | U16 |
int | int32_t, | int32_t, | i32 |
długi | int64_t, | int64_t, | i64 |
liczba zmiennoprzecinkowa | liczba zmiennoprzecinkowa | liczba zmiennoprzecinkowa | 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::Z możliwością pakietu | Nie dotyczy | Nie dotyczy |
IBinder | android::IBinder, | ndk::SpAIBinder | binder::SpIBinder |
K[] | std::vector<T>, | std::vector<T>, | Za: &[T] Wyjście: Vec<T> |
bajt[] | 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 | Przy: &[T]4 Wyjście: Vec<T> |
Deskryptor pliku | android::base::Unique_fd | Nie dotyczy | binder::parcel::ParcelFileDescriptor |
Deskryptor pliku 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 sumy (T)5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. Na Androidzie 12 lub nowszym tablice bajtów używają uint8_t zamiast int8_t. .
2. Backend C++ obsługuje język List<T>
, gdzie T
to jedna z wartości String
,
IBinder
, ParcelFileDescriptor
lub przesyłka. Na Androidzie
13 lub więcej, 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 dyrektywę List<T>
, gdzie T
to wartość z wartości String
.
ParcelFileDescriptor
lub przesyłka. 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 sum są obsługiwane w Androidzie 12 i 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ę wiązania SharedRefBase
, użyj
SharedRefBase::make\<My\>(... args ...)
Ta funkcja tworzy
std::shared_ptr\<T\>
obiekt
jest również zarządzany wewnętrznie, na wypadek gdyby segregator należy do innego
proces tworzenia konta. Utworzenie obiektu w inny sposób powoduje podwójne prawa 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, w którym kierunku
dla 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 w wersji i starszą wywoływaną funkcją,
dodatkowe pola występujące tylko w elemencie wywołującym są resetowane 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 tekstowe to 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 UTF-8. Więcej informacji na temat:
adnotacja utf8InCpp
przeczytaj Adnotacje w AIDL.
Dopuszczalność wartości null
W backendzie Java możesz dodawać adnotacje do typów, które mogą mieć wartość null, za pomocą funkcji @nullable
w celu udostępnienia wartości null backendom CPP i NDK. W backendzie Rust
@nullable
typu jest wyświetlane jako Option<T>
. Serwery natywne odrzucają puste wartości
domyślnie. Jedynym wyjątkiem od tej reguły są typy interface
i IBinder
,
które zawsze może mieć wartość null w przypadku odczytów NDK i zapisów CPP/NDK. Więcej informacji na temat konfiguracji
na temat adnotacji nullable
, zobacz
Adnotacje w AIDL.
Działki niestandardowe
Niestandardowy papierowy obiekt to parcelable, implementowany ręcznie w środowisku docelowym z backendem. Używaj niestandardowych pakietów tylko wtedy, gdy chcesz dodać obsługę do innych języki dla istniejącego obszaru niestandardowego, 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 pakiet Java, gdzie my.pack.age.Foo
to język Java
klasę implementującą interfejs Parcelable
.
Aby zadeklarować obsługę niestandardowego backendu CPP w AIDL, użyj cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
Implementacja języka C++ w języku 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 zgłosić deklarację niestandardowego pakietu NDK w AIDL, użyj 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 (eksperymentalny AOSP) w celu deklaracji niestandardowej reguły Rust
parcelable w AIDL, należy użyć rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
Implementacja w języku 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);
Możesz następnie użyć tej paczki jako typu w plikach AIDL, ale nie będzie
generowanych przez AIDL. Podaj operatory <
i ==
dla backendu CPP/NDK
niestandardowe parcelable, aby używać ich w union
.
Wartości domyślne
Obiekty uporządkowane mogą deklarować domyślne wartości dla poszczególnych pól,
String
oraz tablice tego typu.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Gdy brakuje wartości domyślnych, w backendzie Java pola są inicjowane jako
zerowe dla typów podstawowych i null
w przypadku typów innych niż podstawowe.
W innych backendach pola są inicjowane z użyciem domyślnych wartości zainicjowanych, gdy
wartości domyślne nie są zdefiniowane. Na przykład w backendzie w języku C++ pola String
są zainicjowane jako pusty ciąg znaków, a pola List<T>
są zainicjowane 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 wymagają zdefiniowanego przez użytkownika stanu ani typu zwrotu.
Parametry wyjściowe z błędami
Gdy funkcja AIDL zgłasza błąd, może nie zostać zainicjowana lub
modyfikować parametry wyjściowe. W szczególności parametry wyjściowe mogą być modyfikowane, jeśli
które występują podczas wyodrębniania, a nie podczas przetwarzania
samej transakcji. Ogólnie rzecz biorąc, gdy pojawia się błąd z AIDL
wszystkie parametry inout
i out
oraz zwracaną wartość (która
działa jak parametr out
w niektórych backendach) należy uznać za
w stanie nieokreślonym.
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_OPERATION
i
EX_ILLEGAL_ARGUMENT
można używać, gdy opisują stan błędu, ale
Nie można używać wartości EX_TRANSACTION_FAILED
, ponieważ jest ona traktowana specjalnie w atrybutach
infrastruktury bazowej. Aby dowiedzieć się więcej, przeczytaj definicje backendu
informacje o tych wartościach wbudowanych.
Jeśli interfejs AIDL wymaga dodatkowych wartości błędów, których nie obejmuje
na temat wbudowanych typów błędów, mogą oni używać specjalnych wbudowanych typów
pozwala uwzględnić wartość błędu dotyczącą konkretnej usługi, która jest
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
. Dla:
wyjątki specyficzne dla danej usługi, w języku Java używa protokołu android.os.ServiceSpecificException
wraz z błędem zdefiniowanym przez użytkownika.
Kod natywny na Androidzie nie używa wyjątków. Backend CPP używa
android::binder::Status
Backend NDK używa ndk::ScopedAStatus
. Co
generowana przez AIDL zwraca jeden z tych, reprezentujący stan
. 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 Androida. W poniższych przykładach użyto
zdefiniowanego typu: my.package.IFoo
. Aby dowiedzieć się, jak korzystać z backendu Rust,
zobacz przykład Rusta AIDL
na temat wzorców rdzennych Androida
stronę.
Typy importu
Niezależnie od tego, czy zdefiniowany typ to interfejs, parcelable, lub union, to w Javie:
import my.package.IFoo;
Lub w backendzie CPP:
#include <my/package/IFoo.h>
Lub w backendzie 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. Te zajęcia odczytuje polecenia ze sterownika Binder i wykonuje metody do wdrożenia. Wyobraź sobie, że masz plik AIDL podobny do tego:
package my.package;
interface IFoo {
int doFoo();
}
W Javie musisz wychodzić z tej klasy:
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ą przestrzeń nazw 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 za pomocą asynchronicznego środowiska 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(())
}
}
Zarejestruj się i pobierz usługi
Usługi na platformie Android są zwykle zarejestrowane w servicemanager
proces tworzenia konta. Oprócz wymienionych poniżej interfejsów API niektóre z nich sprawdzają
(co oznacza, że wracają natychmiast, gdy 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ą przestrzeń nazw 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
}
Ważną różnicą od pozostałych opcji jest to, że nie dzwonimy
join_thread_pool
w przypadku korzystania z asynchronicznego środowiska Rust i środowiska wykonawczego jednowątkowego. To jest
ponieważ musisz utworzyć wątek, w którym Tokio będzie mógł wykonać wygenerowane zadania. W
w tym przykładzie, wątek główny będzie służyć do tego. Wszystkie zadania wygenerowane za pomocą
Narzędzie tokio::spawn
zostanie wykonane 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. Musisz zakończyć rozmowę
block_in_place
, aby opuścić kontekst asynchroniczny.
Link do śmierci
Możesz poprosić o otrzymanie powiadomienia, gdy usługa hostująca segregator wygaśnie. Pomaga to uniknąć wycieku serwerów proxy wywołań zwrotnych i ułatwia usuwanie błędów. Wykonuj te wywołania na obiektach serwera proxy powiązań.
- W Javie użyj
android.os.IBinder::linkToDeath
. - W 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łajmy_binder.link_to_death(&mut my_death_recipient)
Pamiętaj, że ponieważ Wywołanie zwrotne należy doDeathRecipient
. 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ą identyfikatory 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: (zamiast zezwalać na ustawienie domyślne):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Raporty o błędach i interfejs API debugowania usług
Przy generowaniu raportów o błędach (np. w narzędziu adb bugreport
) zbierane są
informacji z całego systemu, które ułatwiają 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, możesz też użyć wiersza poleceń dumpsys
z usługi w linii dumpsys SERVICE [ARGS]
. 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ć do usługi niestandardowe dane wyjściowe, możesz zastąpić zasadę dump
w obiekcie serwera tak samo jak implementujesz dowolną inną metodę IPC
zdefiniowane w pliku AIDL. Jeśli chcesz to zrobić, ogranicz zapisywanie tylko do aplikacji
android.permission.DUMP
lub ogranicz zapisywanie do konkretnych identyfikatorów UID.
W backendzie Java:
@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: (zamiast zezwalać na ustawienie domyślne):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Dynamiczne pobieranie deskryptora interfejsu
Deskryptor interfejsu określa typ interfejsu. To jest przydatne podczas debugowania lub nieznanego powiązania.
W Javie możesz uzyskać deskryptor interfejsu zawierający kod podobny do tego:
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.
Statycznie pobieranie deskryptora interfejsu
Czasami (na przykład podczas rejestrowania usług @VintfStability
) trzeba
określić statycznie deskryptor interfejsu. W Javie możesz pobrać
przez dodanie takiego kodu:
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ą przestrzeń nazw aidl
):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
W backendzie Rust:
aidl::my::package::BnFoo::get_descriptor()
Zakres wyliczenia
W natywnych backendach możesz iterować możliwe wartości, które może przyjąć wyliczenie włącz. Ze względu na rozmiar kodu ta funkcja nie jest obsługiwana w Javie.
Oto iteracja MyEnum
zdefiniowanej w AIDL.
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żde wystąpienie elementu libbinder
w procesie utrzymuje 1 pulę wątków. Większość
powinna to być dokładnie 1 pula wątków współdzielona przez wszystkie backendy.
Jedynym wyjątkiem jest sytuacja, w której kod dostawcy może wczytać kolejną kopię libbinder
aby porozmawiać z: /dev/vndbinder
. Jest to oddzielny węzeł powiązania, więc
pula wątków nie jest udostępniana.
W backendzie Java pula wątków może się tylko zwiększać (ponieważ już rozpoczęte):
BinderInternal.setMaxThreads(<new larger value>);
W backendzie 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 gdy 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 rezerwują niektóre nazwy jako słowa kluczowe lub na potrzeby różnych języków
i ich używanie. 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#
.
Odradzamy używanie nazw zarezerwowanych w definicjach AIDL w miarę możliwości, aby uniknąć nieergonomicznego wiązania lub całkowitej nieudanej kompilacji.
Jeśli w definicjach AIDL masz już zarezerwowane nazwy, możesz zmienianie nazw pól przy zachowaniu zgodności z protokołem; może być konieczne zaktualizowanie kod, żeby kontynuować tworzenie, ale wszystkie programy, które zostały już utworzone, współdziałają.
Nazwy, których należy unikać: