Android 11 introduce la possibilità di utilizzare AIDL per gli HAL in Android, il che consente di implementare parti di Android senza HIDL. Passare alle HAL che utilizzano AIDL esclusivamente, se possibile (quando le HAL a monte utilizzano HIDL, deve essere utilizzato HIDL).
Gli HAL che utilizzano AIDL per comunicare tra i componenti del framework, come quelli in system.img
, e i componenti hardware, come quelli in vendor.img
, devono utilizzare AIDL stabile. Tuttavia, per comunicare all'interno di una partizione, ad esempio da un HAL all'altro, non ci sono limitazioni sul meccanismo IPC da utilizzare.
Motivazione
AIDL è disponibile da più tempo di HIDL e viene utilizzato in molti altri luoghi, ad esempio tra i componenti del framework Android o nelle app. Ora che AIDL supporta la stabilità, è possibile implementare un intero stack con un unico runtime IPC. AIDL ha anche un sistema di gestione delle versioni migliore di HIDL. Ecco alcuni vantaggi di AIDL:
- L'utilizzo di un unico linguaggio IPC significa avere un solo elemento da imparare, eseguire il debug, ottimizzare e proteggere.
- AIDL supporta il controllo della versione in-place per i proprietari di un'interfaccia:
- I proprietari possono aggiungere metodi alla fine delle interfacce o campi ai parcelable. Ciò significa che è più facile gestire le versioni del codice nel corso degli anni e anche il costo annuale è inferiore (i tipi possono essere modificati in situ e non è necessaria la presenza di librerie aggiuntive per ogni versione dell'interfaccia).
- Le interfacce di estensione possono essere collegate in fase di esecuzione anziché nel sistema di tipo, quindi non è necessario eseguire il rebase delle estensioni a valle sulle versioni più recenti delle interfacce.
- Un'interfaccia AIDL esistente può essere utilizzata direttamente se il proprietario sceglie di stabilizzarla. In precedenza, era necessario creare un'intera copia dell'interfaccia in HIDL.
Esegui la compilazione in base al runtime AIDL
AIDL ha tre diversi backend: Java, NDK e CPP. Per utilizzare AIDL stabile,
utilizza sempre la copia di sistema di libbinder
in system/lib*/libbinder.so
e
parla su /dev/binder
. Per il codice nell'immagine vendor
, significa che non è possibile utilizzare
libbinder
(del VNDK): questa libreria ha un'API C++ e componenti interni instabili. Il codice del fornitore nativo deve invece utilizzare il backend NDK di AIDL, eseguire il collegamento a libbinder_ndk
(supportato dal sistema libbinder.so
) e alle librerie NDK create dalle voci aidl_interface
. Per i nomi esatti dei moduli, consulta le Regole per la denominazione dei moduli.
Scrivere un'interfaccia HAL AIDL
Affinché un'interfaccia AIDL possa essere utilizzata tra il sistema e il fornitore, è necessario apportare due modifiche:
- Ogni definizione di tipo deve essere annotata con
@VintfStability
. - La dichiarazione
aidl_interface
deve includerestability: "vintf",
.
Solo il proprietario di un'interfaccia può apportare queste modifiche.
Quando apporti queste modifiche, l'interfaccia deve essere nel
file manifest VINTF per funzionare. Testa questo requisito (e i relativi requisiti, ad esempio la verifica che le interfacce rilasciate siano bloccate) utilizzando il test VTS vts_treble_vintf_vendor_test
. Puoi utilizzare un'interfaccia @VintfStability
senza questi requisiti chiamando AIBinder_forceDowngradeToLocalStability
nel backend NDK, android::Stability::forceDowngradeToLocalStability
nel backend C++ o android.os.Binder#forceDowngradeToSystemStability
nel backend Java su un oggetto binder prima che venga inviato a un altro processo.
Inoltre, per la massima portabilità del codice e per evitare potenziali problemi come librerie aggiuntive non necessarie, disattiva il backend CPP.
Tieni presente che l'utilizzo di backends
nell'esempio di codice seguente è corretto, poiché esistono tre backend (Java, NDK e CPP). Il codice mostra come disattivare un backend CPP:
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Trovare le interfacce HAL AIDL
Le interfacce AIDL stabili di AOSP per le HAL si trovano nelle cartelle aidl
nelle stesse directory di base delle interfacce HIDL:
hardware/interfaces
è per le interfacce in genere fornite dall'hardware.frameworks/hardware/interfaces
è per le interfacce di alto livello fornite all'hardware.system/hardware/interfaces
è per le interfacce a basso livello fornite all'hardware.
Inserisci le interfacce di estensione in altre sottodirectory hardware/interfaces
in vendor
o hardware
.
Interfacce di estensione
Android ha un insieme di interfacce AOSP ufficiali con ogni release. Quando i partner Android vogliono aggiungere funzionalità a queste interfacce, non devono modificarle direttamente perché ciò rende il loro runtime Android incompatibile con il runtime Android AOSP. Evita di modificare queste interfacce in modo che l'immagine GSI possa continuare a funzionare.
Le estensioni possono registrarsi in due modi diversi:
- In fase di esecuzione; consulta Interfacce di estensioni collegate
- Come prodotto autonomo, registrato a livello globale e nel VINTF
Indipendentemente da come viene registrata un'estensione, quando i componenti specifici del fornitore (ovvero non facenti parte dell'AOSP a monte) utilizzano l'interfaccia, i conflitti di unione non sono possibili. Tuttavia, quando vengono apportate modifiche a valle ai componenti AOSP a monte, possono verificarsi conflitti di unione e si consigliano le seguenti strategie:
- Eseguire l'upstream delle aggiunte all'interfaccia in AOSP nella prossima release.
- Aggiunta di interfacce a monte che consentono una maggiore flessibilità (senza conflitti di unione) nella prossima release.
Parcelable dell'estensione: ParcelableHolder
ParcelableHolder
è un'istanza dell'interfaccia Parcelable
che può contenere un'altra istanza di Parcelable
.
Il caso d'uso principale di ParcelableHolder
è rendere Parcelable
estensibile.
Ad esempio, immagina che gli implementatori dei dispositivi si aspettino di poter estendere un Parcelable
, AospDefinedParcelable
definito da AOSP, per includere le loro funzionalità con valore aggiunto.
Utilizza l'interfaccia ParcelableHolder
per estendere Parcelable
con le tue funzionalità di valore aggiunto. L'interfaccia ParcelableHolder
contiene un'istanza di
Parcelable
. Se provi ad aggiungere campi direttamente a Parcelable
, viene visualizzato un errore:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Come mostrato nel codice precedente, questa pratica non è valida perché i campi aggiunti dall'implementatore del dispositivo potrebbero avere un conflitto quando Parcelable
viene rivisto nelle release successive di Android.
Utilizzando ParcelableHolder
, il proprietario di un parcelable può definire un punto di estensione in un'istanza di Parcelable
:
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Gli implementatori del dispositivo possono quindi definire la propria istanza Parcelable
per la propria estensione:
parcelable OemDefinedParcelable {
String x;
int[] y;
}
La nuova istanza Parcelable
può essere collegata all'Parcelable
originale con il campo ParcelableHolder
:
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Nomi delle istanze del server HAL AIDL
Per convenzione, i servizi HAL AIDL hanno un nome istanza del formato
$package.$type/$instance
. Ad esempio, un'istanza dell'HAL del vibratore è registrata come android.hardware.vibrator.IVibrator/default
.
Scrivere un server HAL AIDL
@VintfStability
I server AIDL devono essere dichiarati nel file manifest VINTF, ad esempio:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
In caso contrario, deve registrare un servizio AIDL normalmente. Quando esegui test VTS, è previsto che tutti gli HAL AIDL dichiarati siano disponibili.
Scrivere un client AIDL
I client AIDL devono dichiararsi nella matrice di compatibilità, ad esempio:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Convertire un HAL esistente da HIDL ad AIDL
Utilizza lo strumento hidl2aidl
per convertire un'interfaccia HIDL in AIDL.
Funzionalità di hidl2aidl
:
- Crea file AIDL (
.aidl
) in base ai file HAL (.hal
) per il pacchetto in questione. - Crea regole di compilazione per il pacchetto AIDL appena creato con tutti i backend attivati.
- Crea metodi di traduzione nei backend Java, CPP e NDK per la traduzione dai tipi HIDL ai tipi AIDL.
- Crea regole di compilazione per le librerie di traduzione con le dipendenze richieste.
- Crea assert statici per assicurarti che gli enumeratori HIDL e AIDL abbiano gli stessi valori nei backend CPP e NDK.
Per convertire un pacchetto di file HAL in file AIDL:
Crea lo strumento in
system/tools/hidl/hidl2aidl
.La creazione di questo strumento dall'origine più recente offre l'esperienza più completa. Puoi utilizzare la versione più recente per convertire le interfacce dei branch precedenti delle release precedenti:
m hidl2aidl
Esegui lo strumento con una directory di output seguita dal pacchetto da convertire.
Se vuoi, utilizza l'argomento
-l
per aggiungere i contenuti di un nuovo file di licenza nella parte superiore di tutti i file generati. Assicurati di utilizzare la licenza e la data corrette:hidl2aidl -o <output directory> -l <file with license> <package>
Ad esempio:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Esamina i file generati e risolvi eventuali problemi di conversione:
conversion.log
contiene eventuali problemi non gestiti da risolvere per primi.- I file AIDL generati potrebbero contenere avvisi e suggerimenti che richiedono un'azione. Questi commenti iniziano con
//
. - Pulisci e apporta miglioramenti al pacchetto.
- Controlla l'annotazione
@JavaDerive
per le funzionalità che potrebbero essere necessarie, ad esempiotoString
oequals
.
Crea solo i target di cui hai bisogno:
- Disattiva i backend che non verranno utilizzati. Preferisci il backend NDK rispetto al backend CPP; consulta Eseguire il build in base al runtime AIDL.
- Rimuovi le librerie di traduzione o qualsiasi codice generato che non verrà utilizzato.
Consulta le principali differenze tra AIDL e HIDL:
- L'utilizzo di
Status
e delle eccezioni integrate di AIDL in genere migliora l'interfaccia e elimina la necessità di un altro tipo di stato specifico per l'interfaccia. - Gli argomenti dell'interfaccia AIDL nei metodi non sono
@nullable
per impostazione predefinita come accadeva in HIDL.
- L'utilizzo di
SEPolicy per HAL AIDL
Un tipo di servizio AIDL visibile al codice del fornitore deve avere l'attributohal_service_type
. In caso contrario, la configurazione di sepolicy è la stessa di qualsiasi altro servizio AIDL (anche se esistono attributi speciali per le HAL). Ecco un'esempio di definizione di un contesto di servizio HAL:
type hal_foo_service, service_manager_type, hal_service_type;
Per la maggior parte dei servizi definiti dalla piattaforma, è già stato aggiunto un contesto di servizio con il tipo corretto (ad esempio, android.hardware.foo.IFoo/default
è già contrassegnato come hal_foo_service
). Tuttavia, se un client del framework supporta più nomi di istanze, è necessario aggiungere altri nomi di istanze nei file service_contexts
specifici del dispositivo:
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Quando crei un nuovo tipo di HAL, devi aggiungere gli attributi HAL. Un attributo HAL specifico può essere associato a più tipi di servizi (ciascuno dei quali può avere più istanze, come appena discusso). Per un HAL, foo
, è presente
hal_attribute(foo)
. Questa macro definisce gli attributi hal_foo_client
e
hal_foo_server
. Per un determinato dominio, le macro hal_client_domain
e
hal_server_domain
associano un dominio a un determinato attributo HAL. Ad esempio, il server di sistema che è un client di questo HAL corrisponde al criteriohal_client_domain(system_server, hal_foo)
. Analogamente, un server HAL include
hal_server_domain(my_hal_domain, hal_foo)
.
In genere, per un determinato attributo HAL, crea anche un dominio come
hal_foo_default
per HAL di riferimento o di esempio. Tuttavia, alcuni dispositivi utilizzano questi domini per i propri server. Distinguere i domini per più server è importante solo se sono presenti più server che forniscono la stessa interfaccia e richiedono un insieme di autorizzazioni diverso nelle loro implementazioni.
In tutte queste macro, hal_foo
non è un oggetto sepolicy. Questo token viene invece utilizzato da queste macro per fare riferimento al gruppo di attributi associati a una coppia di server client.
Tuttavia, finora hal_foo_service
e hal_foo
(la coppia di attributi di
hal_attribute(foo)
) non sono associati. Un attributo HAL è associato ai servizi HAL AIDL utilizzando la macro hal_attribute_service
(i HAL HIDL utilizzano la macro hal_attribute_hwservice
), ad esempio hal_attribute_service(hal_foo, hal_foo_service)
. Ciò significa che
hal_foo_client
i processi possono acquisire l'HAL e hal_foo_server
i processi possono registrare l'HAL. L'applicazione di queste regole di registrazione è svolta dal gestore del contesto (servicemanager
).
I nomi dei servizi potrebbero non corrispondere sempre agli attributi HAL, ad esempio
hal_attribute_service(hal_foo, hal_foo2_service)
. In generale, poiché questo implica che i servizi vengono sempre utilizzati insieme, puoi rimuovere hal_foo2_service
e utilizzare hal_foo_service
per tutti i contesti del servizio. Quando gli HAL impostano più istanze di hal_attribute_service
, significa che il nome dell'attributo HAL originale non è abbastanza generico e non può essere modificato.
Mettendo tutto insieme, un HAL di esempio ha il seguente aspetto:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Interfacce di estensioni collegate
Un'estensione può essere collegata a qualsiasi interfaccia del binder, che si tratti di un'interfaccia di primo livello registrata direttamente con il gestore del servizio o di un'interfaccia secondaria. Quando ricevi un'estensione, devi verificare che il tipo di estensione sia come previsto. Puoi impostare le estensioni solo dal processo che pubblica un binder.
Utilizza le estensioni allegate ogni volta che un'estensione modifica la funzionalità di un HAL esistente. Quando è necessaria una funzionalità completamente nuova, questo meccanismo non è necessario e puoi registrare un'interfaccia di estensione direttamente con il gestore del servizio. Le interfacce di estensione collegate hanno più senso quando sono collegate a sottointerfacce, perché queste gerarchie possono essere profonde o con più istanze. L'utilizzo di un'estensione globale per rispecchiare la gerarchia dell'interfaccia del binder di un altro servizio richiede una contabilità estesa per fornire funzionalità equivalenti alle estensioni collegate direttamente.
Per impostare un'estensione su un binder, utilizza le seguenti API:
- Backend NDK:
AIBinder_setExtension
- Backend Java:
android.os.Binder.setExtension
- Backend CPP:
android::Binder::setExtension
- Backend Rust:
binder::Binder::set_extension
Per ottenere un'estensione per un binder, utilizza le seguenti API:
- Backend NDK:
AIBinder_getExtension
- Backend Java:
android.os.IBinder.getExtension
- Backend CPP:
android::IBinder::getExtension
- Backend Rust:
binder::Binder::get_extension
Puoi trovare ulteriori informazioni su queste API nella documentazione della funzione getExtension
nel backend corrispondente. Un esempio di come utilizzare le estensioni è disponibile in hardware/interfaces/tests/extension/vibrator
.
Differenze principali tra AIDL e HIDL
Quando utilizzi HAL AIDL o interfacce HAL AIDL, tieni presente le differenze rispetto alla scrittura di HAL HIDL.
- La sintassi del linguaggio AIDL è più simile a Java. La sintassi HIDL è simile a quella di C++.
- Tutte le interfacce AIDL hanno stati di errore incorporati. Anziché creare tipi di stato personalizzati, crea interi di stato costanti nei file di interfaccia e utilizza
EX_SERVICE_SPECIFIC
nei backend CPP e NDK eServiceSpecificException
nel backend Java. Consulta la sezione Gestione degli errori. - AIDL non avvia automaticamente i pool di thread quando vengono inviati oggetti binder. Devi avviarli manualmente (vedi Gestione dei thread).
- AIDL non viene interrotto in caso di errori di trasporto non controllati (HIDL
Return
si interrompe in caso di errori non controllati). - AIDL può dichiarare un solo tipo per file.
- Gli argomenti AIDL possono essere specificati come
in
,out
oinout
oltre al parametro di output (non sono presenti callback sincroni). - AIDL utilizza
fd
come tipo primitivo anzichéhandle
. - HIDL utilizza le versioni principali per le modifiche incompatibili e le versioni secondarie per quelle compatibili. In AIDL, le modifiche compatibili con le versioni precedenti vengono eseguite in blocco.
AIDL non ha un concetto esplicito di versioni principali, ma questo è incorporato nei nomi dei pacchetti. Ad esempio, AIDL potrebbe utilizzare il nome del pacchetto
bluetooth2
. - AIDL non eredita la priorità in tempo reale per impostazione predefinita. La funzione
setInheritRt
deve essere utilizzata per ogni binder per attivare l'eredità della priorità in tempo reale.
Test della suite di test del fornitore (VTS) per gli HAL
Android si basa sulla Vendor Test Suite (VTS) per verificare le implementazioni HAL previste. I VTS contribuiscono a garantire che Android possa essere compatibile con le implementazioni precedenti dei fornitori. Le implementazioni che non superano il VTS presentano problemi di compatibilità noti che potrebbero impedirne il funzionamento con le future versioni del sistema operativo.
I VTS per gli HAL sono composti da due parti principali.
1. Verifica che gli HAL sul dispositivo siano noti e previsti da Android
Questo insieme di test è disponibile in
test/vts-testcase/hal/treble/vintf
.
Questi test hanno lo scopo di verificare:
- Ogni interfaccia
@VintfStability
dichiarata in un manifest VINTF viene bloccata in una versione rilasciata nota. In questo modo, entrambi i lati dell'interfaccia concordano sulla definizione esatta della versione dell'interfaccia. Questo è necessario per il funzionamento di base. - Tutti gli HAL dichiarati in un manifest VINTF sono disponibili su quel dispositivo. Qualsiasi client con autorizzazioni sufficienti per utilizzare un servizio HAL dichiarato deve essere in grado di ottenere e utilizzare questi servizi in qualsiasi momento.
- Tutti gli HAL dichiarati in un file manifest VINTF pubblicano la versione dell'interfaccia dichiarata nel file manifest.
- Non sono presenti HAL ritirati su un dispositivo. Android non supporta più le versioni precedenti delle interfacce HAL, come descritto nel ciclo di vita di FCM.
- Sul dispositivo sono presenti gli HAL richiesti. Alcuni HAL sono obbligatori per il corretto funzionamento di Android.
2. Verifica il comportamento previsto di ogni HAL
Ogni interfaccia HAL ha i propri test VTS per verificare il comportamento previsto dei suoi client. I casi di test vengono eseguiti su ogni istanza di un'interfaccia HAL dichiarata e applicano un comportamento specifico in base alla versione dell'interfaccia implementata.
Questi test tentano di coprire ogni aspetto dell'implementazione dell'HAL su cui si basa o potrebbe basarsi in futuro il framework Android.
Questi test includono la verifica del supporto delle funzionalità, la gestione degli errori e qualsiasi altro comportamento che un client potrebbe aspettarsi dal servizio.
Traguardi VTS per lo sviluppo di HAL
I test VTS devono essere mantenuti aggiornati quando si creano o modificano le interfacce HAL di Android.
I test VTS devono essere completati e pronti per verificare le implementazioni dei fornitori prima di essere bloccati per le release dell'API Android for Vendor. Devono essere pronti prima che le interfacce vengano bloccate in modo che gli sviluppatori possano creare le proprie implementazioni, verificarle e fornire feedback agli sviluppatori dell'interfaccia HAL.
VTS su Cuttlefish
Quando l'hardware non è disponibile, Android utilizza Cuttlefish come veicolo di sviluppo per le interfacce HAL. Ciò consente di eseguire test di verifica della validità e di integrazione scalabili di Android.
hal_implementation_test
verifica che Cuttlefish abbia implementazioni delle ultime versioni dell'interfaccia HAL per assicurarsi che Android sia pronto a gestire le nuove interfacce e che i test VTS siano pronti a testare le implementazioni dei nuovi fornitori non appena saranno disponibili nuovi hardware e dispositivi.