Un backend AIDL è una destinazione per la generazione di codice stub. Quando utilizzi i file AIDL, li usi sempre in una determinata lingua con un runtime specifico. A seconda del contesto, devi utilizzare backend AIDL diversi.
Nella tabella seguente, la stabilità dell'interfaccia API si riferisce alla possibilità di compilare il codice in base a questa interfaccia in modo che possa essere fornito indipendentemente dal file binario system.img
libbinder.so
.
AIDL ha i seguenti backend:
Backend | Lingua | API surface | Sistemi di compilazione |
---|---|---|---|
Java | Java | SDK/SystemApi (stabile*) | tutti |
NDK | C++ | libbinder_ndk (stabile*) | interfaccia_aidl |
CPP | C++ | libbinder (instabile) | tutti |
Rust | Rust | libbinder_rs (stabile*) | aidl_interface |
- Queste API sono stabili, ma molte, ad esempio quelle per la gestione dei servizi, sono riservate all'uso interno della piattaforma e non sono disponibili per le app. Per scoprire di più su come utilizzare AIDL nelle app, consulta la documentazione per gli sviluppatori.
- Il backend Rust è stato introdotto in Android 12; il backend NDK è disponibile a partire da Android 10.
- Il pacchetto Rust è basato su
libbinder_ndk
, il che gli consente di essere stabile e trasportabile. Gli APEX usano la cassa di legatura come fa chiunque altro sul lato del sistema. La parte Rust viene raggruppata in un APEX e inviata al suo interno. Dipende dallibbinder_ndk.so
nella partizione di sistema.
Sistemi di compilazione
A seconda del backend, esistono due modi per compilare AIDL in codice stub. Per maggiori dettagli sui sistemi di compilazione, consulta il riferimento ai moduli Soong.
Sistema di compilazione di base
In qualsiasi modulo Android.bp cc_
o java_
(o nei relativi equivalenti Android.mk
),
i file .aidl
possono essere specificati come file di origine. In questo caso, vengono utilizzati i backend Java/C++ di AIDL (non il backend NDK) e le classi per utilizzare i file AIDL corrispondenti vengono aggiunte automaticamente al modulo. Opzioni come local_include_dirs
, che indica al sistema di compilazione il percorso principale dei file AIDL nel modulo, possono essere specificate in questi moduli in un gruppo aidl:
. Tieni presente che il backend Rust può essere utilizzato solo 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, consulta
l'esempio di Rust AIDL.
aidl_interface
I tipi utilizzati con questo sistema di compilazione devono essere strutturati. Per essere strutturati, gli elementi parcellabili devono contenere direttamente i campi e non essere dichiarazioni di tipi definiti direttamente nelle lingue di destinazione. Per informazioni su come l'AIDL strutturato si inserisce nell'AIDL stabile, consulta AIDL strutturato e AIDL stabile.
Tipi
Puoi considerare il compilatore aidl
come implementazione di riferimento per i tipi.
Quando crei un'interfaccia, richiama aidl --lang=<backend> ...
per visualizzare il
file di interfaccia risultante. Quando utilizzi il modulo aidl_interface
, puoi visualizzare
l'output in out/soong/.intermediates/<path to module>/
.
Tipo Java/AIDL | Tipo C++ | Tipo NDK | Tipo di ruggine |
---|---|---|---|
booleano | bool | bool | bool |
byte8 | int8_t | int8_t | i8 |
char | car16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
lunghi | int64_t | int64_t | i64 |
galleggiare | galleggiare | in virgola mobile | f32 |
doppio | doppio | doppio | f64 |
Stringa | android::String16 | std::string | Stringa |
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<uint8_t> | std::vector<int8_t>1 | In: &[u8] Out: Vec<u8> |
List<T> | std::vector<T>2 | std::vector<T>3 | In: &[T]4 Out: Vec<T> |
FileDescriptor | android::base::unique_fd | N/D | binder::parcel::ParcelFileDescriptor |
DescrittoreFileFile | 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 valori String
,
IBinder
, ParcelFileDescriptor
o parcelable. In Android 13 o versioni successive, T
può essere qualsiasi tipo non primitivo (inclusi i tipi di interfaccia), ad eccezione degli array. AOSP consiglia di
utilizzare tipi di array come T[]
, poiché funzionano in tutti i backend.
3. Il backend NDK supporta List<T>
, dove T
è uno di 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 dimensione fissa possono avere più dimensioni (ad es. int[3][4]
).
Nel backend Java, gli array a dimensione fissa 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\>
che viene gestito anche internamente, nel caso in cui il binder sia di proprietà di un altro
processo. La creazione dell'oggetto in altri modi causa la doppia proprietà.
8. Vedi anche il tipo Java/AIDL byte[]
.
Senso di marcia (in/out/inout)
Quando specifichi i tipi di argomenti per le funzioni, puoi specificarle come in
, out
o inout
. Questo controlla in quale direzione vengono trasmesse le informazioni per una chiamata IPC. in
è la direzione predefinita e indica che i dati vengono passati dall'autore della chiamata al chiamato. out
indica che i dati vengono passati dall'elemento chiamato all'elemento chiamante. inout
è la combinazione di entrambi. Tuttavia, il team di Android consiglia di evitare di utilizzare lo specificatore dell'argomento inout
.
Se utilizzi inout
con un'interfaccia con il controllo delle versioni e un chiamante meno recente, i
campi aggiuntivi presenti solo nel chiamante vengono ripristinati sui valori predefiniti. In Rust, un tipo inout
normale riceve &mut Vec<T>
e un tipo inout
elenco 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);
}
UTF8/UTF16
Con il backend CPP puoi scegliere se le stringhe sono in 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 ulteriori informazioni sull'annotazione utf8InCpp
, consulta Annotazioni in AIDL.
Supporto di valori Null
Puoi annotare i tipi che possono essere null con @nullable
.
Per ulteriori informazioni sull'annotazione nullable
, consulta
Annotazioni in AIDL.
Parcellable personalizzati
Un parcelable personalizzato è un elemento parcelable che viene implementato manualmente in un backend di destinazione. Utilizza i pacchetti personalizzati solo quando stai tentando di aggiungere il supporto ad altre lingue per un pacchetto personalizzato esistente che non può essere modificato.
Per dichiarare un parcelable personalizzato in modo che AIDL lo conosca, la dichiarazione di parcelable di AIDL ha il seguente aspetto:
package my.pack.age;
parcelable Foo;
Per impostazione predefinita, viene dichiarato un Java parcelable in cui my.pack.age.Foo
è una classe Java che implementa l'interfaccia Parcelable
.
Per una dichiarazione di un backend CPP personalizzato condivisibile in AIDL, utilizza cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
L'implementazione in C++ in my/pack/age/Foo.h
ha il seguente aspetto:
#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 una dichiarazione di un parcelable NDK personalizzato in AIDL, utilizza ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
L'implementazione NDK in android/pack/age/Foo.h
ha il seguente aspetto:
#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 Rust personalizzato
parcellabile in AIDL, utilizza rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
L'implementazione in Rust in rust_crate/src/lib.rs
è simile alla seguente:
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);
Quindi puoi utilizzare questo partibile come tipo nei file AIDL, ma non verrà
generato da AIDL. Fornisci operatori <
e ==
per i parcelable personalizzati del backend CPP/NDK per utilizzarli in union
.
Valori predefiniti
Gli oggetti parcelable strutturati possono dichiarare valori predefiniti per campo per elementi primitivi, 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 inizializzati predefiniti quando
i valori predefiniti non sono definiti. 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
sono inizializzati come campi con valore nullo.
Sindacati
Le unioni AIDL sono contrassegnate e le relative funzionalità sono simili in tutti i backend. Per impostazione predefinita, vengono costruiti in base al valore predefinito del primo campo e hanno un modo specifico per interagire con loro in base alla lingua.
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.setSringField("abc"); // setter
Esempio 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 ruggine
A Rust, i sindacati sono implementati come enum 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 errori integrati che i servizi possono utilizzare per segnalare gli errori. Vengono utilizzati da binder e possono essere utilizzati da qualsiasi servizio che implementa un'interfaccia binder. Il loro utilizzo è ben documentato nella definizione AIDL e non richiedono alcun tipo di stato o tipo di ritorno 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. Nello specifico, i parametri di output possono essere modificati se l'errore si verifica durante la separazione dei pacchetti e non durante l'elaborazione della transazione stessa. In generale, quando viene visualizzato un errore da una funzione AIDL, tutti i parametri inout
e out
, nonché il valore restituito (che agisce come parametro out
in alcuni backend), devono essere considerati in uno stato indefinito.
Quali valori di errore utilizzare
Molti dei valori di errore integrati possono essere utilizzati in qualsiasi interfaccia AIDL, ma alcuni vengono trattati in modo speciale. Ad esempio, i valori EX_UNSUPPORTED_OPERATION
e
EX_ILLEGAL_ARGUMENT
possono essere utilizzati quando descrivono la condizione di errore, ma
EX_TRANSACTION_FAILED
non devono essere utilizzati perché viene trattato come speciale
dall'infrastruttura sottostante. Controlla le definizioni specifiche del backend per
ulteriori informazioni su questi valori integrati.
Se l'interfaccia AIDL richiede valori di errore aggiuntivi non coperti dai tipi di errore integrati, è possibile utilizzare l'errore integrato specifico per il servizio speciale che consente l'inclusione di un valore di errore specifico per il servizio definito dall'utente. Questi errori specifici del servizio sono in genere definiti nell'interfaccia AIDL come enum
supportato da const int
o int
e non vengono analizzati da binder.
In Java, gli errori vengono mappati a 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, che rappresenta lo stato del metodo. Il backend Rust utilizza gli stessi valori di codice eccezione dell'NDK, ma li converte in errori Rust nativi (StatusCode
, ExceptionCode
) prima di inviarli 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,
consulta l'esempio di Rust AIDL
nella pagina Modelli di ruggine di Android.
Tipi di importazione
Che il tipo definito sia un'interfaccia, un Parcelable o union, puoi importarlo in Java:
import my.package.IFoo;
In alternativa, nel backend CPP:
#include <my/package/IFoo.h>
Nel backend NDK (nota lo spazio dei nomi aidl
aggiuntivo):
#include <aidl/my/package/IFoo.h>
In alternativa, nel backend Rust:
use my_package::aidl::my::package::IFoo;
Sebbene sia possibile importare un tipo nidificato in Java, nei backend CPP/NDK devi includere l'intestazione per il tipo root. Ad esempio, quando importi un tipo nidificato
Bar
definito in my/package/IFoo.aidl
(IFoo
è il tipo principale del
file), devi includere <my/package/IFoo.h>
per il backend CPP (o
<aidl/my/package/IFoo.h>
per il backend NDK).
Implementare i servizi
Per implementare un servizio, devi ereditare dalla classe stub nativa. Questa classe 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 estenderti da questa classe:
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 (notare 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(())
}
}
Registrati e ricevi servizi
I servizi nella piattaforma Android sono solitamente registrati con la procedura servicemanager
. Oltre alle API riportate di seguito, alcune API controllano il servizio (ovvero restituiscono immediatamente un risultato se il servizio non è disponibile).
Controlla l'interfaccia servicemanager
corrispondente per i dettagli esatti. Queste operazioni possono essere eseguite 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 (notare 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 un thread:
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
}
Una differenza importante rispetto alle altre opzioni è che non chiamiamo
join_thread_pool
quando utilizziamo Rust asincrono e un runtime a un thread. Questo accade perché devi fornire a Tokio un thread in cui può eseguire le attività generate. In questo
esempio, il thread principale servirà a questo scopo. Eventuali attività generate utilizzando
tokio::spawn
verranno eseguite nel thread principale.
Nel backend Rust asincrono, con un runtime multi-thread:
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 Tokio multithread, le attività generate non vengono eseguite nel
thread principale. Pertanto, ha più senso chiamare join_thread_pool
nel
thread principale in modo che il thread principale non sia semplicemente 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 binder si arresta in modo anomalo. In questo modo è possibile evitare la perdita dei proxy di callback o agevolare il ripristino degli errori. Esegui queste chiamate sugli oggetti proxy del binder.
- 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
e poi chiamamy_binder.link_to_death(&mut my_death_recipient)
. Tieni presente che, poichéDeathRecipient
è il proprietario del callback, devi mantenere attivo questo 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. Il PID (o ID processo) si riferisce all'ID processo Linux del processo che invia una transazione. L'UID (o User-ID) si riferisce all'ID utente Linux. Quando ricevi una chiamata unidirezionale, il PID chiamante è 0. Quando non si trovano all'interno del contesto di una transazione del 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 NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Nel backend Rust, quando implementi l'interfaccia, specifica quanto segue (anziché lasciare che sia predefinito):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Segnalazioni di bug e API di debug per i servizi
Quando vengono eseguite le segnalazioni di 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 di bug utilizzano il file binario dumpsys
su tutti i servizi registrati con il gestore dei servizi per scaricare le informazioni nel report di 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 i servizi vengono sottoposti a dump utilizzando argomenti aggiuntivi per addService
. Puoi anche utilizzare dumpsys --pid SERVICE
per ottenere il PID di un servizio durante il debug.
Per aggiungere un'uscita personalizzata al servizio, puoi sostituire il metodo dump
nell'oggetto server come se stessi implementando qualsiasi altro metodo IPC
definito in un file AIDL. A questo scopo, devi limitare il dumping all'autorizzazione android.permission.DUMP
dell'app 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 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é lasciare che sia predefinito):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Utilizzare i puntatori deboli
Puoi utilizzare un riferimento debole a un oggetto binder.
Sebbene Java supporti WeakReference
, non supporta i riferimenti ai binder deboli
a livello di 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, utilizzi WpIBinder
o Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Recuperare dinamicamente il descrittore dell'interfaccia
Il descrittore dell'interfaccia identifica il tipo di un'interfaccia. Questa opzione è utile per eseguire il debug o quando hai un binder sconosciuto.
In Java, puoi ottenere il descrittore dell'interfaccia con 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à.
Recuperare in modo statico il descrittore dell'interfaccia
A volte (ad esempio quando registri i servizi @VintfStability
), devi sapere qual è il descrittore dell'interfaccia in modo statico. In Java, puoi ottenere il descriptor aggiungendo codice come:
import my.package.IFoo;
... IFoo.DESCRIPTOR
Nel backend CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
Nel backend NDK (notare 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 di enum
Nei backend nativi, puoi eseguire l'iterazione dei possibili valori che un'enumerazione può assumere. Per motivi di dimensioni del codice, questa operazione non è supportata in Java.
Per un'enumerazione MyEnum
definita in AIDL, l'iterazione è fornita come segue.
Nel backend CPP:
::android::enum_range<MyEnum>()
Nel backend NDK:
::ndk::enum_range<MyEnum>()
Nel backend Rust:
MyEnum::enum_values()
Gestione dei thread
Ogni istanza di libbinder
in un processo gestisce un pool di thread. Per la maggior parte
degli utilizzi, deve essere presente esattamente un pool di thread, condiviso tra tutti i backend.
L'unica eccezione è quando il codice del fornitore potrebbe caricare un'altra copia di libbinder
per comunicare con /dev/vndbinder
. Poiché si trova su un nodo binder separato, il pool di thread non è condiviso.
Per il backend Java, il threadpool può solo aumentare di dimensioni (poiché è già 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 ulteriori informazioni, consulta la sezione sulla registrazione dei servizi.
Nomi riservati
C++, Java e Rust riservano alcuni nomi come parole chiave o per l'uso specifico
di un linguaggio. Sebbene AIDL non imponga limitazioni in base alle regole del linguaggio, l'utilizzo di nomi di campi o tipi corrispondenti a un nome riservato potrebbe comportare un errore di compilazione per C++ o Java. Per Rust, il campo o il tipo viene rinominato utilizzando la sintassi "identificatore non elaborato", accessibile tramite il prefisso r#
.
Ti consigliamo di evitare di utilizzare nomi riservati nelle definizioni AIDL se possibile per evitare associazioni non ergonomiche o errori di compilazione.
Se hai già nomi riservati nelle definizioni AIDL, puoi rinominare in sicurezza i campi mantenendo la compatibilità con il protocollo. Potresti dover aggiornare il codice per continuare a creare, ma tutti i programmi già creati continueranno a interoperare.
Nomi da evitare: