Un backend AIDL è una destinazione per la generazione di codice stub. Utilizza sempre i file AIDL in una determinata lingua con un runtime specifico. A seconda del contesto, devi utilizzare backend AIDL diversi.
Nella tabella seguente, la stabilità della superficie API si riferisce alla possibilità
di compilare il codice in base a questa superficie API in modo che il codice possa essere
distribuito indipendentemente dal binario system.img
libbinder.so
.
AIDL ha i seguenti backend:
Backend | Lingua | Piattaforma API | Sistemi di compilazione |
---|---|---|---|
Java | Java | SDK o SystemApi (stabile*) |
Tutte |
NDK | C++ | libbinder_ndk (stabile*) |
aidl_interface |
CPP | C++ | libbinder (instabile) |
Tutte |
Rust | Rust | libbinder_rs (stabile*) |
aidl_interface |
- Queste superfici API sono stabili, ma molte delle API, come quelle per la gestione dei servizi, sono riservate all'uso interno della piattaforma e non sono disponibili per le app. Per saperne di più su come utilizzare AIDL nelle app, consulta Android Interface Definition Language (AIDL).
- Il backend Rust è stato introdotto in Android 12; il backend NDK è disponibile a partire da Android 10.
- Il crate Rust è basato su
libbinder_ndk
, il che lo rende stabile e portatile. Gli APEX utilizzano il binder crate nel modo standard sul lato sistema. La parte Rust è raggruppata in un APEX e spedita al suo interno. Questa parte dipende dalibbinder_ndk.so
sulla partizione di sistema.
Sistemi di compilazione
A seconda del backend, esistono due modi per compilare AIDL in codice stub. Per ulteriori dettagli sui sistemi di build, consulta Riferimento ai moduli Soong.
Sistema di compilazione principale
In qualsiasi cc_
o java_
Android.bp module
(o nei relativi equivalenti Android.mk
), puoi specificare i file AIDL (.aidl
) come file di origine. In questo caso, vengono utilizzati i backend Java o CPP di AIDL (non il backend NDK) e le classi per utilizzare i file AIDL corrispondenti vengono aggiunte automaticamente al modulo. Puoi specificare opzioni come local_include_dirs
(che indica al sistema di compilazione il percorso principale dei file AIDL in quel modulo) in questi moduli in un gruppo aidl:
.
Il backend Rust è destinato all'uso esclusivo con
Rust. I moduli rust_
vengono gestiti in modo diverso in quanto i file AIDL non sono
specificati come file di origine. Il modulo aidl_interface
produce invece un
rustlib
chiamato aidl_interface_name-rust
, a cui è possibile
collegarsi. Per maggiori dettagli, vedi l'esempio di Rust AIDL.
aidl_interface
I tipi utilizzati con il sistema di build aidl_interface
devono essere strutturati. Per essere strutturabili, i parcelable devono contenere campi direttamente e non essere dichiarazioni di tipi definiti direttamente nelle lingue di destinazione. Per scoprire in che modo
AIDL strutturato si integra con AIDL stabile, consulta AIDL strutturato e stabile.
Tipi
Considera il compilatore aidl
come implementazione di riferimento per i tipi.
Quando crei un'interfaccia, richiama aidl --lang=<backend> ...
per visualizzare
il file dell'interfaccia risultante. Quando utilizzi il modulo aidl_interface
, puoi visualizzare l'output in out/soong/.intermediates/<path to module>/
.
Tipo Java o AIDL | Tipo C++ | Tipo di NDK | Tipo di ruggine |
---|---|---|---|
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 |
In: &str Out: String |
android.os.Parcelable |
android::Parcelable |
N/D | N/D |
IBinder |
android::IBinder |
ndk::SpAIBinder |
binder::SpIBinder |
T[] |
std::vector<T> |
std::vector<T> |
In: &[T] Out: Vec<T> |
byte[] |
std::vector |
std::vector 1 |
In: &[u8] Out: Vec<u8> |
List<T> |
std::vector<T> 2 |
std::vector<T> 3 |
In: In: &[T] 4Out: Vec<T> |
FileDescriptor |
android::base::unique_fd |
N/D | N/D |
ParcelFileDescriptor |
android::os::ParcelFileDescriptor |
ndk::ScopedFileDescriptor |
binder::parcel::ParcelFileDescriptor |
Tipo di interfaccia (T ) |
android::sp<T> |
std::shared_ptr<T> 7 |
binder::Strong |
Tipo Parcelable (T ) |
T |
T |
T |
Tipo di unione (T )5 |
T |
T |
T |
T[N] 6 |
std::array<T, N> |
std::array<T, N> |
[T; N] |
1. In Android 12 o versioni successive, gli array di byte utilizzano
uint8_t
anziché int8_t
per motivi di compatibilità.
2. Il backend C++ supporta List<T>
, dove T
è uno dei seguenti valori:
String
, IBinder
, ParcelFileDescriptor
o parcelable. In Android
13 o versioni successive, T
può essere qualsiasi tipo non primitivo (inclusi i tipi di interfaccia)
tranne gli array. AOSP consiglia di utilizzare tipi di array come T[]
, perché
funzionano in tutti i backend.
3. Il backend NDK supporta List<T>
, dove T
è uno dei seguenti: String
,
ParcelFileDescriptor
o parcelable. In Android 13
o versioni successive, T
può essere qualsiasi tipo non primitivo, ad eccezione degli array.
4. I tipi vengono passati in modo diverso per il codice Rust a seconda che siano input (un argomento) o output (un valore restituito).
5. I tipi di unione sono supportati in Android 12 e versioni successive.
6. In Android 13 o versioni successive, gli array di dimensioni fisse sono
supportati. Gli array a dimensioni fisse possono avere più dimensioni (ad esempio int[3][4]
). Nel backend Java, gli array a dimensioni fisse sono rappresentati come tipi di array.
7. Per creare un'istanza di un oggetto binder SharedRefBase
, utilizza
SharedRefBase::make\<My\>(... args ...)
. Questa funzione crea un oggetto
std::shared_ptr\<T\>
, gestito anche internamente, nel caso in cui
il binder sia di proprietà di un altro processo. La creazione dell'oggetto in altri modi causa
una doppia proprietà.
8. Vedi anche il tipo Java o AIDL byte[]
.
Direzionalità (in, out e inout)
Quando specifichi i tipi di argomenti per le funzioni, puoi specificarli
come in
, out
o inout
. Controlla la direzione in cui vengono passate le informazioni per una chiamata IPC.
Lo specificatore di argomenti
in
indica che i dati vengono passati dal chiamante al chiamato. Lo specificatorein
è la direzione predefinita, ma se i tipi di dati possono essere ancheout
, devi specificare la direzione.Lo specificatore di argomento
out
indica che i dati vengono passati dal chiamato al chiamante.Lo specificatore di argomenti
inout
è la combinazione di entrambi. Tuttavia, ti consigliamo di evitare di utilizzare lo specificatore di argomentiinout
. Se utilizziinout
con un'interfaccia con controllo delle versioni e un chiamante precedente, i campi aggiuntivi presenti solo nel chiamante vengono reimpostati sui valori predefiniti. Per quanto riguarda Rust, un tipoinout
normale riceve&mut T
e un tipo di elencoinout
riceve&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 e UTF-16
Con il backend CPP, puoi scegliere se le stringhe sono UTF-8 o UTF-16.
Dichiara le stringhe come @utf8InCpp String
in AIDL per convertirle automaticamente
in UTF-8. I backend NDK e Rust utilizzano sempre stringhe UTF-8. Per saperne di più
sull'annotazione utf8InCpp
, consulta
utf8InCpp.
Supporto di valori Null
Puoi annotare i tipi che possono essere nulli con @nullable
.
Per ulteriori informazioni sull'annotazione nullable
, consulta
nullable.
Custom parcelables
Un parcelable personalizzato è un parcelable implementato manualmente in un backend di destinazione. Utilizza i parcelable personalizzati solo quando tenti di aggiungere il supporto di altre lingue per un parcelable personalizzato esistente che non può essere modificato.
Ecco un esempio di dichiarazione di AIDL parcelable:
package my.pack.age;
parcelable Foo;
Per impostazione predefinita, viene dichiarata una classe Java parcelable in cui my.pack.age.Foo
è una classe Java che implementa l'interfaccia Parcelable
.
Per una dichiarazione di un pacco di backend CPP personalizzato in AIDL, utilizza cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
L'implementazione C++ in my/pack/age/Foo.h
è simile alla seguente:
#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);
};
Per la dichiarazione di un tipo parcelable NDK personalizzato in AIDL, utilizza ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
L'implementazione dell'NDK in android/pack/age/Foo.h
è simile alla seguente:
#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);
};
In Android 15, per la dichiarazione di un parcelable Rust personalizzato in AIDL,
utilizza rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
L'implementazione di Rust in rust_crate/src/lib.rs
ha il seguente aspetto:
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);
Poi puoi utilizzare questo parcelable come tipo nei file AIDL, ma non verrà
generato da AIDL. Fornisci gli operatori <
e ==
per i parcelable personalizzati di backend CPP e NDK da utilizzare in union
.
Valori predefiniti
I parcelable strutturati possono dichiarare valori predefiniti per campo per primitive,
campi String
e array di questi tipi.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Nel backend Java, quando mancano i valori predefiniti, i campi vengono inizializzati come
valori zero per i tipi primitivi e null
per i tipi non primitivi.
In altri backend, i campi vengono inizializzati con valori predefiniti quando
non sono definiti valori predefiniti. Ad esempio, nel backend C++, i campi String
vengono inizializzati come stringa vuota e i campi List<T>
vengono inizializzati come
vector<T>
vuoto. I campi @nullable
vengono inizializzati come campi con valore nullo.
Sindacati
Le unioni AIDL sono taggate e le loro funzionalità sono simili in tutti i backend. Sono costruiti in base al valore predefinito del primo campo e hanno un modo specifico per interagire con loro:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Esempio Java
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setStringField("abc"); // setter
Esempio di C++ e 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)
Esempio di Rust
In Rust, le unioni sono implementate come enumerazioni e non hanno getter e setter espliciti.
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
Gestione degli errori
Il sistema operativo Android fornisce tipi di errore integrati che i servizi possono utilizzare per segnalare gli errori. Questi vengono utilizzati dai binder e possono essere utilizzati da qualsiasi servizio che implementi un'interfaccia binder. Il loro utilizzo è ben documentato nella definizione AIDL e non richiedono alcun tipo di stato o valore restituito definito dall'utente.
Parametri di output con errori
Quando una funzione AIDL segnala un errore, la funzione potrebbe non inizializzare o
modificare i parametri di output. In particolare, i parametri di output potrebbero essere modificati se
l'errore si verifica durante l'unparceling, anziché durante
l'elaborazione della transazione stessa. In generale, quando si riceve un errore da una
funzione AIDL, tutti i parametri inout
e out
, nonché il valore restituito
(che funge da parametro out
in alcuni backend), devono essere considerati
in uno stato indefinito.
Quali valori di errore utilizzare
Molti dei valori di errore incorporati possono essere utilizzati in qualsiasi interfaccia AIDL, ma alcuni
vengono trattati in modo speciale. Ad esempio, EX_UNSUPPORTED_OPERATION
e
EX_ILLEGAL_ARGUMENT
possono essere utilizzati quando descrivono la condizione di errore, ma
EX_TRANSACTION_FAILED
non deve essere utilizzato perché viene trattato in modo speciale dall'infrastruttura
sottostante. Per ulteriori informazioni su questi valori integrati, consulta le definizioni specifiche del backend.
Se l'interfaccia AIDL richiede valori di errore aggiuntivi non coperti dai tipi di errore integrati, può utilizzare l'errore integrato speciale specifico del servizio che consente l'inclusione di un valore di errore specifico del servizio definito dall'utente. Questi errori specifici del servizio sono in genere definiti
nell'interfaccia AIDL come const int
o int
-backed enum
e non vengono analizzati
da Binder.
In Java, gli errori vengono mappati alle eccezioni, ad esempio android.os.RemoteException
. Per le eccezioni specifiche del servizio, Java utilizza android.os.ServiceSpecificException
insieme all'errore definito dall'utente.
Il codice nativo in Android non utilizza eccezioni. Il backend CPP utilizza
android::binder::Status
. Il backend NDK utilizza ndk::ScopedAStatus
. Ogni
metodo generato da AIDL restituisce uno di questi valori, che rappresenta lo stato del
metodo. Il backend Rust utilizza gli stessi valori di codice di eccezione dell'NDK, ma
li converte in errori Rust nativi (StatusCode
, ExceptionCode
) prima di
restituirli all'utente. Per gli errori specifici del servizio, il valore Status
o ScopedAStatus
restituito utilizza EX_SERVICE_SPECIFIC
insieme all'errore definito dall'utente.
I tipi di errori integrati sono disponibili nei seguenti file:
Backend | Definizione |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
Utilizzare vari backend
Queste istruzioni sono specifiche per il codice della piattaforma Android. Questi esempi utilizzano un tipo definito, my.package.IFoo
. Per istruzioni su come utilizzare il backend Rust, vedi l'esempio di AIDL Rust in Android Rust Patterns.
Tipi di importazione
Indipendentemente dal fatto che il tipo definito sia un'interfaccia, un oggetto serializzabile o un'unione, puoi importarlo in Java:
import my.package.IFoo;
Oppure nel backend CPP:
#include <my/package/IFoo.h>
Oppure nel backend NDK (nota lo spazio dei nomi aidl
aggiuntivo):
#include <aidl/my/package/IFoo.h>
Oppure nel backend Rust:
use my_package::aidl::my::package::IFoo;
Sebbene sia possibile importare un tipo nidificato in Java, nei backend CPP e NDK
devi includere l'intestazione per il tipo di radice. Ad esempio, quando importi un tipo nidificato Bar
definito in my/package/IFoo.aidl
(IFoo
è il tipo di radice del file), devi includere <my/package/IFoo.h>
per il backend CPP (o <aidl/my/package/IFoo.h>
per il backend NDK).
Implementare un'interfaccia
Per implementare un'interfaccia, devi ereditare dalla classe stub nativa. Un'implementazione di un'interfaccia viene spesso chiamata servizio quando è registrata con il service manager o android.app.ActivityManager
e callback quando è registrata da un client di un servizio. Tuttavia, a seconda dell'utilizzo esatto, vengono utilizzati diversi nomi per descrivere le implementazioni dell'interfaccia. La classe stub legge i comandi dal driver binder ed esegue i
metodi che implementi. Immagina di avere un file AIDL come questo:
package my.package;
interface IFoo {
int doFoo();
}
In Java, devi estendere la classe Stub
generata:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
Nel backend CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
Nel backend NDK (nota lo spazio dei nomi aidl
aggiuntivo):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
Nel backend 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(())
}
}
Oppure con Rust asincrono:
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(())
}
}
Registrarsi e ottenere servizi
I servizi nella piattaforma Android vengono solitamente registrati con il processo servicemanager
. Oltre alle seguenti API, alcune API controllano il servizio (ovvero restituiscono immediatamente un risultato se il servizio non è disponibile).
Controlla l'interfaccia servicemanager
corrispondente per i dettagli esatti. Puoi
eseguire queste operazioni solo durante la compilazione per la piattaforma Android.
In Java:
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"));
Nel backend 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"));
Nel backend NDK (nota lo spazio dei nomi aidl
aggiuntivo):
#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")));
Nel backend 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()
}
Nel backend Rust asincrono, con un runtime a thread singolo:
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
}
Una differenza importante rispetto alle altre opzioni è che non chiami
join_thread_pool
quando utilizzi Rust asincrono e un runtime a thread singolo. Questo perché
devi fornire a Tokio un thread in cui eseguire le attività generate. Nell'esempio
seguente, il thread principale svolge questa funzione. Tutte le attività generate
utilizzando tokio::spawn
vengono eseguite sul thread principale.
Nel backend Rust asincrono, con un runtime multithread:
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();
});
}
Con il runtime multithread Tokio, le attività generate non vengono eseguite sul thread principale. Pertanto, è più sensato chiamare join_thread_pool
sul thread principale in modo che quest'ultimo non sia inattivo. Devi racchiudere la chiamata in
block_in_place
per uscire dal contesto asincrono.
Link alla morte
Puoi richiedere di ricevere una notifica quando un servizio che ospita un raccoglitore non è più disponibile. In questo modo, puoi evitare la divulgazione di proxy di callback o facilitare il recupero degli errori. Effettua queste chiamate sugli oggetti proxy del raccoglitore.
- In Java, utilizza
android.os.IBinder::linkToDeath
. - Nel backend CPP, utilizza
android::IBinder::linkToDeath
. - Nel backend NDK, utilizza
AIBinder_linkToDeath
. - Nel backend Rust, crea un oggetto
DeathRecipient
, quindi chiamamy_binder.link_to_death(&mut my_death_recipient)
. Tieni presente che, poichéDeathRecipient
è proprietario del callback, devi mantenere attivo l'oggetto finché vuoi ricevere notifiche.
Informazioni sul chiamante
Quando ricevi una chiamata del binder del kernel, le informazioni sul chiamante sono disponibili in diverse API. L'ID processo (PID) si riferisce all'ID processo Linux del processo che invia una transazione. L'ID utente (UI) si riferisce all'ID utente Linux. Quando ricevi una chiamata unidirezionale, il PID chiamante è 0. Al di fuori di un contesto di transazione binder, queste funzioni restituiscono il PID e l'UID del processo corrente.
Nel backend Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
Nel backend CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
Nel backend dell'NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Nel backend Rust, quando implementi l'interfaccia, specifica quanto segue (anziché consentire il valore predefinito):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
API per la segnalazione di bug e il debug per i servizi
Quando vengono eseguiti i report sui bug (ad esempio con adb bugreport
), vengono raccolte
informazioni da tutto il sistema per facilitare il debug di vari problemi.
Per i servizi AIDL, i report sui bug utilizzano il binario dumpsys
su tutti i servizi
registrati con il service manager per scaricare le informazioni nel
report sui bug. Puoi anche utilizzare dumpsys
sulla riga di comando per ottenere informazioni
da un servizio con dumpsys SERVICE [ARGS]
. Nei backend C++ e Java, puoi controllare l'ordine in cui vengono dumpati i servizi utilizzando argomenti aggiuntivi per addService
. Puoi anche utilizzare dumpsys --pid SERVICE
per ottenere il PID di un servizio durante il debug.
Per aggiungere un output personalizzato al servizio, esegui l'override del metodo dump
nell'oggetto server come se stessi implementando qualsiasi altro metodo IPC definito in un file AIDL. Quando lo fai, limita il dumping all'autorizzazione
dell'app android.permission.DUMP
o a UID specifici.
Nel backend Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
Nel backend CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
Nel backend dell'NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
Nel backend Rust, quando implementi l'interfaccia, specifica quanto segue (anziché consentire il valore predefinito):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Utilizzare puntatori deboli
Puoi mantenere un riferimento debole a un oggetto binder.
Sebbene Java supporti WeakReference
, non supporta i riferimenti deboli del binder
nel livello nativo.
Nel backend CPP, il tipo debole è wp<IFoo>
.
Nel backend NDK, utilizza ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
Nel backend Rust, utilizza WpIBinder
o Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Recupera dinamicamente il descrittore dell'interfaccia
Il descrittore dell'interfaccia identifica il tipo di interfaccia. Questa funzionalità è utile per il debug o quando hai un binder sconosciuto.
In Java, puoi ottenere il descrittore dell'interfaccia con un codice come:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
Nel backend CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
I backend NDK e Rust non supportano questa funzionalità.
Recupera staticamente il descrittore dell'interfaccia
A volte (ad esempio durante la registrazione dei servizi @VintfStability
) devi
conoscere staticamente il descrittore dell'interfaccia. In Java, puoi ottenere il
descrittore aggiungendo codice come:
import my.package.IFoo;
... IFoo.DESCRIPTOR
Nel backend CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
Nel backend NDK (nota lo spazio dei nomi aidl
aggiuntivo):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
Nel backend Rust:
aidl::my::package::BnFoo::get_descriptor()
Intervallo enum
Nei backend nativi, puoi scorrere i possibili valori che un enum può assumere. A causa delle considerazioni sulle dimensioni del codice, questa funzionalità non è supportata in Java.
Per un'enumerazione MyEnum
definita in AIDL, l'iterazione viene fornita come segue.
Nel backend CPP:
::android::enum_range<MyEnum>()
Nel backend dell'NDK:
::ndk::enum_range<MyEnum>()
Nel backend Rust:
MyEnum::enum_values()
Gestione dei thread
Ogni istanza di libbinder
in un processo mantiene un threadpool. Per la maggior parte
dei casi d'uso, deve essere esattamente un threadpool, condiviso tra tutti i backend.
L'unica eccezione è se il codice fornitore carica un'altra copia di libbinder
per comunicare con /dev/vndbinder
. Si trova su un nodo binder separato, quindi il
threadpool non è condiviso.
Per il backend Java, la dimensione del threadpool può solo aumentare (perché è già stato avviato):
BinderInternal.setMaxThreads(<new larger value>);
Per il backend CPP, sono disponibili le seguenti operazioni:
// 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();
Analogamente, nel backend NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
Nel backend Rust:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
Con il backend Rust asincrono, sono necessari due pool di thread: binder e Tokio.
Ciò significa che le app che utilizzano Rust asincrono richiedono considerazioni speciali,
soprattutto per quanto riguarda l'utilizzo di join_thread_pool
. Per saperne di più, consulta la sezione sulla registrazione dei servizi.
Nomi riservati
C++, Java e Rust riservano alcuni nomi come parole chiave o per un uso specifico della lingua. Sebbene AIDL non imponga restrizioni basate su regole di linguaggio, l'utilizzo di nomi di campi o tipi che corrispondono a un nome riservato può causare un errore di compilazione per C++ o Java. Per Rust, il campo o il tipo viene rinominato utilizzando la sintassi dell'identificatore non elaborato, accessibile utilizzando il prefisso r#
.
Ti consigliamo di evitare di utilizzare nomi riservati nelle definizioni AIDL ove possibile per evitare binding non ergonomici o errori di compilazione.
Se hai già nomi riservati nelle definizioni AIDL, puoi rinominare i campi in modo sicuro mantenendo la compatibilità del protocollo. Potresti dover aggiornare il codice per continuare a creare, ma tutti i programmi già creati continuano a interoperare.
Nomi da evitare: