Android 10 aggiunge il supporto per Android Interface Definition Language (AIDL) stabile, un nuovo modo per tenere traccia dell'API (Application Programming Interface) e dell'ABI (Application Binary Interface) fornite dalle interfacce AIDL. Stable AIDL funziona esattamente come AIDL, ma il sistema di compilazione monitora la compatibilità dell'interfaccia e ci sono limitazioni a ciò che puoi fare:
- Le interfacce sono definite nel sistema di compilazione con
aidl_interfaces
. - Le interfacce possono contenere solo dati strutturati. I dati catastali che rappresentano i tipi preferiti vengono creati automaticamente in base alla relativa definizione AIDL e vengono sottoposti automaticamente a marshalling e non.
- Le interfacce possono essere dichiarate stabili (compatibili con le versioni precedenti). In questo caso, l'API viene monitorata e gestita in base alla versione in un file accanto all'interfaccia AIDL.
AIDL strutturato e stabile
AIDL strutturato si riferisce ai tipi definiti esclusivamente in AIDL. Ad esempio, una dichiarazione parcellable (un parcelable personalizzato) non è un AIDL strutturato. I parcelable con i relativi campi definiti in AIDL sono chiamati parcelable strutturati.
AIDL stabile richiede AIDL strutturato in modo che il sistema di compilazione e il compilatore possano capire se le modifiche apportate ai parcelable sono compatibili con le versioni precedenti.
Tuttavia, non tutte le interfacce strutturate sono stabili. Per essere stabile,
un'interfaccia deve utilizzare solo tipi strutturati, oltre alle seguenti
funzionalità di controllo delle versioni. Al contrario, un'interfaccia non è stabile se per crearla
viene utilizzato il sistema di compilazione principale o se è impostato unstable:true
.
Definire un'interfaccia AIDL
Una definizione di aidl_interface
ha il seguente aspetto:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions_with_info: [
{
version: "1",
imports: ["other-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
name
: il nome del modulo dell'interfaccia AIDL che identifica in modo univoco un'interfaccia AIDL.srcs
: l'elenco dei file di origine AIDL che compongono l'interfaccia. Il percorso per un tipo AIDLFoo
definito in un pacchettocom.acme
deve trovarsi in<base_path>/com/acme/Foo.aidl
, dove<base_path>
può essere qualsiasi directory correlata alla directory in cui si trovaAndroid.bp
. Nell'esempio precedente,<base_path>
èsrcs/aidl
.local_include_dir
: il percorso da dove inizia il nome del pacchetto. corrisponde a<base_path>
descritto sopra.imports
: un elenco dei moduliaidl_interface
utilizzati. Se una delle tue interfacce AIDL utilizza un'interfaccia o un parcelable di un altroaidl_interface
, inserisci il nome qui. Può essere il nome da solo per fare riferimento alla versione più recente o il nome con il suffisso della versione (ad esempio-V1
) per fare riferimento a una versione specifica. La specifica di una versione è supportata da Android 12versions
: le versioni precedenti dell'interfaccia bloccate inapi_dir
. A partire da Android 11, leversions
sono bloccate inaidl_api/name
. Se non esistono versioni bloccate di un'interfaccia, questo non deve essere specificato e non sono previsti controlli di compatibilità. Questo campo è stato sostituito conversions_with_info
per Android 13 e versioni successive.versions_with_info
: elenco di tuple, ciascuna delle quali contiene il nome di una versione bloccata e un elenco con importazioni di versione di altri moduli aidl_interface importati da questa versione di aidl_interface. La definizione della versione V di un'interfaccia AIDL IFACE si trova inaidl_api/IFACE/V
. Questo campo è stato introdotto in Android 13 e non dovrebbe essere modificato direttamente inAndroid.bp
. Il campo viene aggiunto o aggiornato richiamando*-update-api
o*-freeze-api
. Inoltre, viene eseguita la migrazione automatica dei campiversions
aversions_with_info
quando un utente richiama*-update-api
o*-freeze-api
.stability
: il flag facoltativo per la promessa di stabilità di questa interfaccia. Sono supportati solo"vintf"
. Se il criteriostability
non viene configurato, il sistema di build controlla che l'interfaccia sia compatibile con le versioni precedenti, a meno che non venga specificatounstable
. L'assenza di impostazione corrisponde a un'interfaccia con stabilitá all'interno di questo contesto di compilazione (quindi tutte le cose di sistema, ad esempio le cose insystem.img
e le partizioni correlate, o tutte le cose del fornitore, ad esempio le cose invendor.img
e le partizioni correlate). Se il criteriostability
viene impostato su"vintf"
, ciò corrisponde a una promessa di stabilità: l'interfaccia deve essere mantenuta stabile finché viene utilizzata.gen_trace
: il flag facoltativo per attivare o disattivare il monitoraggio. A partire da Android 14, il valore predefinito ètrue
per i backendcpp
ejava
.host_supported
: il flag facoltativo che, se impostato sutrue
, rende le librerie generate disponibili per l'ambiente host.unstable
: il flag facoltativo utilizzato per contrassegnare che questa interfaccia non deve essere stabile. Se questo valore è impostato sutrue
, il sistema di compilazione non crea il dump dell'API per l'interfaccia né ne richiede l'aggiornamento.frozen
: il flag facoltativo che indica che, se impostato sutrue
, l'interfaccia non ha subito modifiche rispetto alla versione precedente. Ciò consente più controlli in fase di build. Se impostato sufalse
, significa che l'interfaccia è in sviluppo e presenta nuove modifiche, quindi l'esecuzione difoo-freeze-api
genera una nuova versione e modifica automaticamente il valore intrue
. Introdotta in Android 14.backend.<type>.enabled
: questi flag attivano/disattivano ciascuno dei backend per i quali il compilatore AIDL genera codice. Sono supportati quattro backend: Java, C++, NDK e Rust. I backend Java, C++ e NDK sono attivati per impostazione predefinita. Se uno di questi tre backend non è necessario, deve essere disattivato esplicitamente. Rust è disattivata per impostazione predefinita fino ad Android 15.backend.<type>.apex_available
: l'elenco dei nomi APEX per i quali è disponibile la libreria di stub generata.backend.[cpp|java].gen_log
: il flag facoltativo che controlla se generare codice aggiuntivo per raccogliere informazioni sulla transazione.backend.[cpp|java].vndk.enabled
: il flag facoltativo per rendere questa interfaccia parte di VNDK. Il valore predefinito èfalse
.backend.[cpp|ndk].additional_shared_libraries
: introdotto in Android 14, questo flag aggiunge dipendenze alle librerie native. Questo flag è utile conndk_header
ecpp_header
.backend.java.sdk_version
: il flag facoltativo per specificare la versione dell'SDK su cui viene creata la libreria stub Java. Il valore predefinito è"system_current"
. Questo valore non deve essere impostato quandobackend.java.platform_apis
ètrue
.backend.java.platform_apis
: il flag facoltativo che deve essere impostato sutrue
quando le librerie generate devono essere compilate in base all'API della piattaforma piuttosto che all'SDK.
Per ogni combinazione di versioni e backend abilitati, viene creata una libreria di stub. Per sapere come fare riferimento alla versione specifica della libreria stub per un backend specifico, consulta Regole di denominazione dei moduli.
Scrivere file AIDL
Le interfacce in AIDL stabile sono simili a quelle tradizionali, con l'eccezione che non è consentito utilizzare elementi parcellizzabili non strutturati (in quanto non sono stabili, vedi AIDL strutturato e stabile). La differenza principale nell'AIDL stabile è il modo in cui vengono definiti i componenti parcellabili. In precedenza, i dati catastali venivano dichiarati in avanti; nel caso di AIDL stabile (e quindi strutturato), i campi e le variabili particellari vengono definiti in modo esplicito.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Un valore predefinito è supportato (ma non obbligatorio) per boolean
, char
,
float
, double
, byte
, int
, long
e String
. In Android
12, sono supportati anche i valori predefiniti per le enumerazioni definite dall'utente. Se non viene specificato un valore predefinito, viene utilizzato un valore simile a 0 o vuoto.
Le enumerazioni senza un valore predefinito vengono inizializzate a 0 anche se non esiste un enumeratore zero.
Utilizzare le librerie stub
Dopo aver aggiunto le librerie stub come dipendenza al modulo, puoi includerle nei file. Di seguito sono riportati alcuni esempi di librerie stub nel sistema di compilazione (Android.mk
può essere utilizzato anche per le definizioni dei moduli precedenti):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if your preference is to load a library and share
// it among multiple users or if you only need access to constants
static_libs: ["my-module-name-java"],
...
}
# or
rust_... {
name: ...,
rustlibs: ["my-module-name-rust"],
...
}
Esempio in C++:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
Esempio in Java:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
Esempio in Rust:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
Interfacce di controllo delle versioni
La dichiarazione di un modulo con il nome foo crea anche un target nel sistema di compilazione
che puoi utilizzare per gestire l'API del modulo. Al momento della compilazione, foo-freeze-api
aggiunge una nuova definizione di API in api_dir
o
aidl_api/name
, a seconda della versione di Android, e
aggiunge un file .hash
, entrambi che rappresentano la nuova versione bloccata dell'
interfaccia. foo-freeze-api aggiorna anche la proprietà versions_with_info
per riflettere la versione aggiuntiva e imports
per la versione. In sostanza,imports
in versions_with_info
viene copiato dal campo imports
. Tuttavia,
la versione stabile più recente è specificata in imports
in versions_with_info
per
l'importazione, che non ha una versione esplicita.
Dopo aver specificato la proprietà versions_with_info
, il sistema di compilazione esegue controlli di compatibilità tra le versioni bloccate e anche tra la versione principale (Top of Tree, ToT) e la versione bloccata più recente.
Inoltre, devi gestire la definizione dell'API della versione ToT. Ogni volta che un'API viene aggiornata, esegui foo-update-api per aggiornare aidl_api/name/current
, che contiene la definizione dell'API della versione di Text-to-Speech.
Per mantenere la stabilità di un'interfaccia, i proprietari possono aggiungere nuovi elementi:
- Metodi alla fine di un'interfaccia (o metodi con nuovi seriali definiti esplicitamente)
- Elementi alla fine di una particella (richiede l'aggiunta di un valore predefinito per ogni elemento)
- Valori costanti
- In Android 11, gli enumeratori
- In Android 12, i campi alla fine di un'unione
Non sono consentite altre azioni e nessun altro può modificare un'interfaccia (altrimenti rischia di entrare in conflitto con le modifiche apportate da un proprietario).
Per verificare che tutte le interfacce siano bloccate per la release, puoi eseguire la compilazione impostando le seguenti variabili di ambiente:
AIDL_FROZEN_REL=true m ...
: la compilazione richiede il blocco di tutte le interfacce AIDL stabili per le quali non è specificato alcun campoowner:
.AIDL_FROZEN_OWNERS="aosp test"
: la build richiede che tutte le interfacce AIDL stabili vengano bloccate con il campoowner:
specificato come "aosp" o "test".
Stabilità delle importazioni
L'aggiornamento delle versioni delle importazioni per le versioni bloccate di un'interfaccia è compatibile con le versioni precedenti a livello di livello AIDL stabile. Tuttavia, l'aggiornamento richiede l'aggiornamento di tutti i server e i client che utilizzano una versione precedente dell'interfaccia e alcune app potrebbero essere confuse se si mescolano versioni diverse di tipi. In genere, per i pacchetti solo di tipi o comuni, questa operazione è sicura perché il codice deve essere già scritto per gestire i tipi sconosciuti dalle transazioni IPC.
Il codice della piattaforma Android android.hardware.graphics.common
è l'esempio
più importante di questo tipo di upgrade della versione.
Utilizzare interfacce con versione
Metodi di interfaccia
In fase di esecuzione, quando si tenta di chiamare nuovi metodi su un vecchio server, i nuovi client ricevono un errore o un'eccezione, a seconda del backend.
- Il backend di
cpp
riceve::android::UNKNOWN_TRANSACTION
. - Il backend di
ndk
riceveSTATUS_UNKNOWN_TRANSACTION
. - Il backend di
java
riceveandroid.os.RemoteException
con un messaggio che indica che l'API non è implementata.
Per le strategie per gestire questo problema, consulta Eseguire query sulle versioni e Utilizzare i valori predefiniti.
Lotti da parte
Quando vengono aggiunti nuovi campi ai parcelable, i vecchi client e server li eliminano. Quando i nuovi client e server ricevono i vecchi parcelable, i valori predefiniti per i nuovi campi vengono compilati automaticamente. Ciò significa che i valori predefiniti devono essere specificati per tutti i nuovi campi in un parcelable.
I client non devono aspettarsi che i server utilizzino i nuovi campi, a meno che non sappiano che il server sta implementando la versione in cui è definito il campo (vedi Eseguire query sulle versioni).
Enum e costanti
Analogamente, i client e i server devono rifiutare o ignorare i valori costanti e gli enumeratori non riconosciuti, in quanto potrebbero essere aggiunti altri in futuro. Ad esempio, un server non deve interrompersi quando riceve un enumeratore che non conosce. Il server deve ignorare l'enumeratore o restituire qualcosa in modo che il client sappia che non è supportato in questa implementazione.
Sindacati
Il tentativo di inviare un'unione con un nuovo campo non va a buon fine se il destinatario è precedente e non conosce il campo. L'implementazione non vedrà mai
l'unione con il nuovo campo. L'errore viene ignorato se si tratta di una transazione unidirezionale; in caso contrario, l'errore è BAD_VALUE
(per il backend C++ o NDK) o IllegalArgumentException
(per il backend Java). L'errore viene ricevuto se il client invia un Union Set nel nuovo campo a un vecchio server o se si tratta di un vecchio client che riceve il Union Set da un nuovo server.
Gestire più versioni
Un namespace del linker in Android può avere una sola versione di un'interfaccia aidl
specifica per evitare situazioni in cui i tipi aidl
generati abbiano più definizioni. C++ ha la regola di definizione che richiede
una sola definizione di ogni simbolo.
La compilazione Android genera un errore quando un modulo dipende da versioni diverse della stessa libreria aidl_interface
. Il modulo potrebbe dipendere da queste librerie direttamente o indirettamente tramite le dipendenze delle loro dipendenze. Questi errori mostrano il grafico delle dipendenze dal modulo con errori alle versioni in conflitto della libreria aidl_interface
. Tutte le dipendenze devono essere aggiornate in modo da includere la stessa versione (di solito la più recente) di queste librerie.
Se la libreria di interfaccia viene utilizzata da molti moduli diversi, può essere utile creare cc_defaults
, java_defaults
e rust_defaults
per qualsiasi gruppo di librerie e processi che devono utilizzare la stessa versione. Quando si introduce una nuova versione dell'interfaccia, queste impostazioni predefinite possono essere aggiornate e tutti i moduli che le utilizzano vengono aggiornati insieme, assicurando che non utilizzino versioni diverse dell'interfaccia.
cc_defaults {
name: "my.aidl.my-process-group-ndk-shared",
shared_libs: ["my.aidl-V3-ndk"],
...
}
cc_library {
name: "foo",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
cc_binary {
name: "bar",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
Quando i moduli aidl_interface
importano altri moduli aidl_interface
, vengono create dipendenze aggiuntive che richiedono l'utilizzo congiunto di versioni specifiche. Questa situazione può diventare difficile da gestire quando esistono moduli aidl_interface
comuni che vengono importati in più moduli aidl_interface
utilizzati insieme nelle stesse procedure.
aidl_interfaces_defaults
può essere utilizzato per mantenere una definizione delle
versioni più recenti delle dipendenze per un aidl_interface
che può essere aggiornata in
un unico posto e utilizzata da tutti i moduli aidl_interface
che vogliono importare
l'interfaccia comune.
aidl_interface_defaults {
name: "android.popular.common-latest-defaults",
imports: ["android.popular.common-V3"],
...
}
aidl_interface {
name: "android.foo",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
aidl_interface {
name: "android.bar",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
Sviluppo basato su flag
Non è possibile utilizzare le interfacce in fase di sviluppo (non bloccate) sui dispositivi di rilascio perché non è garantita la compatibilità con le versioni precedenti.
AIDL supporta il fallback in fase di esecuzione per queste librerie di interfaccia non bloccate in modo che il codice possa essere scritto in base all'ultima versione non bloccata ed essere comunque utilizzato sui dispositivi di rilascio. Il comportamento compatibile con le versioni precedenti dei client è simile al comportamento esistente e, con il fallback, anche le implementazioni devono seguire questi comportamenti. Consulta Utilizzare le interfacce con controllo delle versioni.
Flag build AIDL
Il flag che controlla questo comportamento è RELEASE_AIDL_USE_UNFROZEN
definito in build/release/build_flags.bzl
. true
indica che la versione non bloccata
dell'interfaccia viene utilizzata in fase di esecuzione, mentre false
indica che le librerie delle
versioni sbloccate si comportano tutte come la loro ultima versione bloccata.
Puoi ignorare il flag impostandolo su true
per lo sviluppo locale, ma devi ripristinarlo su false
prima del rilascio. In genere, lo sviluppo viene eseguito con una configurazione in cui il flag è impostato su true
.
Matrice di compatibilità e manifest
Gli oggetti dell'interfaccia del fornitore (oggetti VINTF) definiscono quali versioni sono previste e quali versioni vengono fornite ai lati dell'interfaccia del fornitore.
La maggior parte dei dispositivi non Cuttlefish ha come target la matrice di compatibilità più recente solo dopo il blocco delle interfacce, pertanto non esiste alcuna differenza nelle librerie AIDL basate su RELEASE_AIDL_USE_UNFROZEN
.
Matrici
Le interfacce di proprietà del partner vengono aggiunte alle matrici di compatibilità specifiche per il dispositivo o per il prodotto che il dispositivo ha come target durante lo sviluppo. Pertanto, quando viene aggiunta a una matrice di compatibilità una nuova versione non bloccata di un'interfaccia, le versioni bloccate precedenti devono rimanere per RELEASE_AIDL_USE_UNFROZEN=false
. Puoi gestire questo problema utilizzando diversi file della matrice di compatibilità per configurazioni RELEASE_AIDL_USE_UNFROZEN
diverse o consentendo entrambe le versioni in un unico file della matrice di compatibilità utilizzato in tutte le configurazioni.
Ad esempio, quando aggiungi una versione 4 non bloccata, utilizza <version>3-4</version>
.
Quando la versione 4 è bloccata, puoi rimuovere la versione 3 dalla matrice di compatibilità
perché la versione bloccata 4 viene utilizzata quando RELEASE_AIDL_USE_UNFROZEN
è
false
.
Manifest
In Android 15, viene introdotta una modifica in libvintf
per
modificare i file manifest in fase di creazione in base al valore di
RELEASE_AIDL_USE_UNFROZEN
.
I manifest e i relativi frammenti dichiarano la versione di un'interfaccia implementata da un servizio. Quando utilizzi la versione più recente non bloccata di un'interfaccia, il manifest deve essere aggiornato in base a questa nuova versione. Quando
RELEASE_AIDL_USE_UNFROZEN=false
le voci del file manifest vengono modificate da
libvintf
per riflettere la modifica nella libreria AIDL generata. La versione viene modificata dalla versione non bloccata N
all'ultima versione bloccata N - 1
. Pertanto, gli utenti non devono gestire più manifest o frammenti manifest per ciascuno dei loro servizi.
Modifiche client HAL
Il codice client HAL deve essere compatibile con le versioni precedenti di ogni versione bloccata precedente supportata. Quando RELEASE_AIDL_USE_UNFROZEN
è false
, i servizi hanno sempre lo stesso aspetto dell'ultima versione bloccata o precedente (ad esempio, l'uso di nuovi metodi non bloccati restituisce UNKNOWN_TRANSACTION
o i nuovi campi parcelable
hanno i valori predefiniti). I client del framework Android devono essere compatibili con le versioni precedenti aggiuntive, ma questo è un nuovo dettaglio per i clienti dei fornitori e quelli delle interfacce di proprietà dei partner.
Modifiche all'implementazione di HAL
La principale differenza nello sviluppo dell'HAL rispetto allo sviluppo basato su flag è il requisito che le implementazioni HAL siano compatibili con le versioni precedenti dell'ultima versione bloccata, in modo che funzionino quando RELEASE_AIDL_USE_UNFROZEN
è false
.
La compatibilità con le versioni precedenti nelle implementazioni e nel codice del dispositivo è un nuovo
esercizio. Vedi Utilizzare le interfacce con controllo delle versioni.
Le considerazioni sulla compatibilità con le versioni precedenti sono in genere le stesse per i client e i server, nonché per il codice del framework e del fornitore, ma esistono differenze sottili che devi conoscere, poiché ora stai effettivamente implementando due versioni che utilizzano lo stesso codice sorgente (la versione corrente non bloccata).
Esempio: un'interfaccia ha tre versioni bloccate. L'interfaccia viene aggiornata con un nuovo metodo. Il client e il servizio vengono aggiornati per utilizzare la nuova libreria versione 4. Poiché la libreria V4 si basa su una versione sbloccata dell'interfaccia, si comporta come l'ultima versione bloccata, la versione 3, quando RELEASE_AIDL_USE_UNFROZEN
è false
e impedisce l'utilizzo del nuovo metodo.
Quando l'interfaccia è bloccata, tutti i valori di RELEASE_AIDL_USE_UNFROZEN
utilizzano la versione bloccata e il codice che gestisce la compatibilità con le versioni precedenti può essere rimosso.
Quando chiami i metodi sui callback, devi gestire in modo appropriato la situazione quando viene restituito UNKNOWN_TRANSACTION
. I client potrebbero implementare due diverse versioni di un callback in base alla configurazione della release, pertanto non puoi assumere che il client invii la versione più recente e i nuovi metodi potrebbero restituire questo valore. Questo è simile al modo in cui i client AIDL stabili mantengono la compatibilità con le versioni precedenti dei server descritta in Utilizzare interfacce con versioni.
// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
mMyCallback = cb;
// Get the version of the callback for later when we call methods on it
auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
return status;
}
// Example of using the callback later
void NotifyCallbackLater() {
// From the latest frozen version (V2)
mMyCallback->foo();
// Call this method from the unfrozen V3 only if the callback is at least V3
if (mMyCallbackVersion >= 3) {
mMyCallback->bar();
}
}
È possibile che i nuovi campi nei tipi esistenti (parcelable
, enum
, union
) non esistano o non contengano i valori predefiniti quando RELEASE_AIDL_USE_UNFROZEN
è false
e i valori dei nuovi campi che un servizio tenta di inviare vengono eliminati all'uscita dal processo.
I nuovi tipi aggiunti in questa versione non bloccata non possono essere inviati o ricevuti tramite l'interfaccia.
L'implementazione non riceve mai una chiamata per nuovi metodi da nessun client quando
RELEASE_AIDL_USE_UNFROZEN
è false
.
Fai attenzione a utilizzare i nuovi enumeratori solo con la versione in cui sono stati introdotti, e non con la versione precedente.
In genere, utilizzi foo->getInterfaceVersion()
per vedere quale versione è in uso nell'interfaccia remota. Tuttavia, con il supporto del controllo delle versioni basato su flag, stai implementando due versioni diverse, quindi ti consigliamo di ottenere la versione dell'interfaccia attuale. Puoi farlo ottenendo la versione dell'interfaccia dell'oggetto corrente, ad esempio this->getInterfaceVersion()
o gli altri metodi per my_ver
. Per ulteriori informazioni, consulta la sezione Eseguire query sulla versione dell'interfaccia dell'oggetto remoto.
Nuove interfacce VINTF stabili
Quando viene aggiunto un nuovo pacchetto di interfaccia AIDL, non esiste un'ultima versione bloccata, pertanto non è previsto alcun comportamento di riserva quando RELEASE_AIDL_USE_UNFROZEN
è false
. Non utilizzare queste interfacce. Quando RELEASE_AIDL_USE_UNFROZEN
è equale a
false
, Service Manager non consente al servizio di registrare l'interfaccia
e i client non la troveranno.
Puoi aggiungere i servizi in modo condizionale in base al valore del flag RELEASE_AIDL_USE_UNFROZEN
nel file make del dispositivo:
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
Se il servizio fa parte di un processo più ampio e quindi non puoi aggiungerlo al dispositivo in modo condizionale, puoi verificare se il servizio è stato dichiarato con IServiceManager::isDeclared()
. Se è stato dichiarato e la registrazione non è riuscita, interrompete la procedura. Se non è dichiarata, la registrazione non andrà a buon fine.
Cuttlefish come strumento di sviluppo
Ogni anno dopo il blocco del VINTF, modifichiamo la matrice di compatibilità del framework (FCM) target-level
e il PRODUCT_SHIPPING_API_LEVEL
di Cuttlefish in modo che riflettano i dispositivi lanciati con la release del prossimo anno. Modifichiamo target-level
e PRODUCT_SHIPPING_API_LEVEL
per assicurarci che i dispositivi in fase di lancio siano testati e soddisfino i nuovi requisiti per il rilascio del prossimo anno.
Quando RELEASE_AIDL_USE_UNFROZEN
è true
, Cuttlefish viene utilizzato per lo sviluppo di release Android future. Ha come target il livello FCM e PRODUCT_SHIPPING_API_LEVEL
della release di Android del prossimo anno e deve soddisfare i requisiti software del fornitore (VSR) della release successiva.
Quando RELEASE_AIDL_USE_UNFROZEN
è false
, Cuttlefish ha i valori target-level
e PRODUCT_SHIPPING_API_LEVEL
precedenti per riflettere un dispositivo di rilascio.
In Android 14 e versioni precedenti, questa differenziazione verrebbe
applicata a rami Git diversi che non rilevano la modifica a FCM
target-level
, il livello API di spedizione o qualsiasi altro codice destinato alla release
successiva.
Regole di denominazione dei moduli
In Android 11, per ogni combinazione di versioni e backend abilitati viene creato automaticamente un modulo della libreria stub. Per fare riferimento a un modulo specifico della libreria stub per il collegamento, non utilizzare il nome del modulo aidl_interface
, ma il nome del modulo della libreria stub, ovvero ifacename-version-backend, dove
ifacename
: nome del moduloaidl_interface
version
è una diVversion-number
per le versioni bloccateVlatest-frozen-version-number + 1
per la versione di punta dell'albero (non ancora bloccata)
backend
è una dijava
per il backend Java,cpp
per il backend C++.ndk
ondk_platform
per il backend NDK. Il primo è per le app, mentre il secondo è per l'utilizzo della piattaforma fino ad Android 13. In Android 13 e versioni successive, utilizza solondk
.rust
per il backend Rust.
Supponiamo che esista un modulo denominato foo e che la sua versione più recente sia 2, e che supporti sia NDK che C++. In questo caso, AIDL genera questi moduli:
- In base alla versione 1
foo-V1-(java|cpp|ndk|ndk_platform|rust)
- In base alla versione 2 (la versione stabile più recente)
foo-V2-(java|cpp|ndk|ndk_platform|rust)
- In base alla versione di ToT
foo-V3-(java|cpp|ndk|ndk_platform|rust)
Rispetto ad Android 11:
foo-backend
, che fa riferimento all'ultima versione stabile, diventafoo-V2-backend
foo-unstable-backend
, che faceva riferimento alla versione ToT, diventafoo-V3-backend
I nomi dei file di output sono sempre gli stessi dei nomi dei moduli.
- In base alla versione 1:
foo-V1-(cpp|ndk|ndk_platform|rust).so
- In base alla versione 2:
foo-V2-(cpp|ndk|ndk_platform|rust).so
- In base alla versione ToT:
foo-V3-(cpp|ndk|ndk_platform|rust).so
Tieni presente che il compilatore AIDL non crea un modulo di versione unstable
o un modulo senza versione per un'interfaccia AIDL stabile.
A partire da Android 12, il nome del modulo generato da un'interfaccia AIDL stabile include sempre la relativa versione.
Nuovi metodi dell'interfaccia meta
Android 10 aggiunge diversi metodi di meta-interfaccia per l'AIDL stabile.
Query sulla versione dell'interfaccia dell'oggetto remoto
I client possono eseguire query sulla versione e sull'hash dell'interfaccia implementata dall'oggetto remoto e confrontare i valori restituiti con quelli dell'interfaccia utilizzata dal client.
Esempio con il backend cpp
:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
Esempio con il backend ndk
(e ndk_platform
):
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
Esempio con il backend java
:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
Per il linguaggio Java, il lato remoto DEVE implementare getInterfaceVersion()
e
getInterfaceHash()
come segue (viene utilizzato super
anziché IFoo
per evitare errori di copia e incolla. L'annotazione @SuppressWarnings("static")
potrebbe essere necessaria per disattivare gli avvisi, a seconda della configurazione di javac
:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
Questo perché le classi generate (IFoo
, IFoo.Stub
e così via) sono condivise tra il client e il server (ad esempio, le classi possono trovarsi nel percorso di classe di avvio). Quando i corsi vengono condivisi, il server è collegato anche alla
versione più recente dei corsi, anche se potrebbe essere stato creato con una
versione precedente dell'interfaccia. Se questa metainterfaccia è implementata nella classe condivisa, restituisce sempre la versione più recente. Tuttavia, implementando il metodo
come sopra, il numero di versione dell'interfaccia è incorporato nel codice del server
(perché IFoo.VERSION
è un static final int
che è incorporato quando viene fatto riferimento)
e di conseguenza il metodo può restituire la versione esatta con cui è stato creato il server.
Gestire le interfacce precedenti
È possibile che un client sia aggiornato con la versione più recente di un'interfaccia AIDL, ma che il server utilizzi la vecchia interfaccia AIDL. In questi casi,
la chiamata di un metodo su un'interfaccia precedente restituisce UNKNOWN_TRANSACTION
.
Con AIDL stabile, i client hanno un maggiore controllo. Sul lato client puoi impostare un'implementazione predefinita per un'interfaccia AIDL. Un metodo nell'implementazione predefinita viene richiamato solo se non è implementato sul lato remoto (perché è stato creato con una versione precedente dell'interfaccia). Poiché i valori predefiniti sono impostati a livello globale, non devono essere utilizzati da contesti potenzιακά condivisi.
Esempio in C++ in Android 13 e versioni successive:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
Esempio in Java:
IFoo.Stub.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
Non è necessario fornire l'implementazione predefinita di tutti i metodi in un'interfaccia AIDL. I metodi che sono garantiti per essere implementati nel lato remoto
(perché hai la certezza che il telecomando sia stato creato quando i metodi erano nella
descrizione dell'interfaccia AIDL) non devono essere sostituiti nella classe impl
predefinita.
Convertire l'AIDL esistente in AIDL strutturato o stabile
Se hai già un'interfaccia AIDL e il codice che la utilizza, segui i passaggi riportati di seguito per convertire l'interfaccia in un'interfaccia AIDL stabile.
Identifica tutte le dipendenze dell'interfaccia. Per ogni pacchetto di cui dipende l'interfaccia, determina se il pacchetto è definito in AIDL stabile. Se non è definito, il pacchetto deve essere convertito.
Converti tutti i parcelable nell'interfaccia in parcelable stabili (i file di interfaccia stessi possono rimanere invariati). Per farlo, esprimi la loro struttura direttamente nei file AIDL. Le classi di gestione devono essere riscritte per utilizzare questi nuovi tipi. Questa operazione può essere eseguita prima di creare un
aidl_interface
package (di seguito).Crea un pacchetto
aidl_interface
(come descritto sopra) contenente il nome del modulo, le sue dipendenze e tutte le altre informazioni di cui hai bisogno. Per stabilizzarlo (non solo strutturarlo), deve essere anche sottoposto a controllo della versione. Per ulteriori informazioni, consulta la sezione Interfacce di controllo delle versioni.