Android 10 aggiunge il supporto per il linguaggio di definizione dell'interfaccia Android (AIDL) stabile, un nuovo modo per tenere traccia dell'interfaccia di programmazione dell'applicazione (API) e dell'interfaccia binaria dell'applicazione (ABI) fornita dalle interfacce AIDL. Stable AIDL funziona esattamente come AIDL, ma il sistema di build tiene traccia della compatibilità delle interfacce e ci sono limitazioni su ciò che puoi fare:
- Le interfacce sono definite nel sistema di build con
aidl_interfaces
. - Le interfacce possono contenere solo dati strutturati. I Parcelable che rappresentano i tipi preferiti vengono creati automaticamente in base alla definizione AIDL e vengono automaticamente sottoposti a marshalling e unmarshalling.
- Le interfacce possono essere dichiarate stabili (compatibili con le versioni precedenti). Quando ciò accade, la relativa API viene monitorata e versionata in un file accanto all'interfaccia AIDL.
AIDL strutturato e stabile
AIDL strutturato si riferisce ai tipi definiti puramente in AIDL. Ad esempio, una dichiarazione parcelable (un parcelable personalizzato) non è AIDL strutturato. I Parcelable con i relativi campi definiti in AIDL sono chiamati Parcelable strutturati.
Stable AIDL 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 e anche le seguenti
funzionalità di controllo delle versioni. Al contrario, un'interfaccia non è stabile se viene utilizzata per crearla
il sistema di compilazione principale o se è impostato unstable:true
.
Definisci 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 sorgente AIDL che compongono l'interfaccia. Il percorso per un tipo AIDLFoo
definito in un pacchettocom.acme
deve essere<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 cui inizia il nome del pacchetto. Corrisponde a<base_path>
spiegato sopra.imports
: un elenco dei moduliaidl_interface
utilizzati. Se una delle tue interfacce AIDL utilizza un'interfaccia o un parcelable di un altroaidl_interface
, inseriscine il nome qui. Può essere solo il nome, per fare riferimento all'ultima versione, oppure il nome con il suffisso della versione (ad esempio-V1
) per fare riferimento a una versione specifica. La specifica di una versione è supportata a partire da Android 12versions
: Le versioni precedenti dell'interfaccia che sono bloccate inapi_dir
. A partire da Android 11, leversions
sono bloccate inaidl_api/name
. Se non sono presenti versioni bloccate di un'interfaccia, non è necessario specificare questo campo e non verranno eseguiti controlli di compatibilità. Questo campo è stato sostituito daversions_with_info
per Android 13 e versioni successive.versions_with_info
: elenco di tuple, ognuna delle quali contiene il nome di una versione bloccata e un elenco con le importazioni di versioni 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 deve essere modificato direttamente inAndroid.bp
. Il campo viene aggiunto o aggiornato richiamando*-update-api
o*-freeze-api
. Inoltre, i campiversions
vengono migrati automaticamente aversions_with_info
quando un utente richiama*-update-api
o*-freeze-api
.stability
: Il flag facoltativo per la promessa di stabilità di questa interfaccia. Questa opzione supporta solo"vintf"
. Sestability
non è impostato, il sistema di build verifica che l'interfaccia sia compatibile con le versioni precedenti, a meno che non sia specificatounstable
. L'annullamento 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 nelle partizioni correlate, o tutte le cose del fornitore, ad esempio le cose invendor.img
e nelle partizioni correlate). Sestability
è 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 la tracciatura. 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 indicare che questa interfaccia non deve essere stabile. Se impostato sutrue
, il sistema di compilazione non crea il dump dell'API per l'interfaccia né richiede che venga aggiornato.frozen
: Il flag facoltativo che, se impostato sutrue
, indica che l'interfaccia non ha subito modifiche rispetto alla versione precedente. Ciò consente più controlli in fase di compilazione. Se impostato sufalse
, significa che l'interfaccia è in fase di 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 cui il compilatore AIDL genera codice. Sono supportati quattro backend: Java, C++, NDK e Rust. I backend Java, C++ e NDK sono attivi per impostazione predefinita. Se uno di questi tre backend non è necessario, deve essere disattivato in modo esplicito. Rust è disattivato per impostazione predefinita fino ad Android 15.backend.<type>.apex_available
: l'elenco dei nomi APEX per cui è disponibile la libreria 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 è basata 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 anziché all'SDK.
Per ogni combinazione delle versioni e dei backend abilitati, viene creata una libreria stub. Per informazioni su 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 alle interfacce tradizionali, con l'eccezione che non possono utilizzare parcelable non strutturati (perché non sono stabili. Vedi AIDL strutturato e stabile). La differenza principale in AIDL stabile è il modo in cui vengono definiti i parcelable. In precedenza, i parcelable erano dichiarati in avanti; in AIDL stabile (e quindi strutturato), i campi e le variabili parcelable sono definiti in modo esplicito.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
È supportato (ma non obbligatorio) un valore predefinito 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. Quando 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 tuoi file. Ecco alcuni esempi di librerie stub nel sistema di build (Android.mk
può essere utilizzato anche per le definizioni dei moduli legacy).
Tieni presente che in questi esempi la versione non è presente, quindi rappresenta l'utilizzo
di un'interfaccia instabile, ma i nomi delle interfacce con le versioni includono
informazioni aggiuntive. Per maggiori dettagli, consulta Versioni delle interfacce.
cc_... {
name: ...,
// use `shared_libs:` to load your library and its transitive dependencies
// dynamically
shared_libs: ["my-module-name-cpp"],
// use `static_libs:` to include the library in this binary and drop
// transitive dependencies
static_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// use `static_libs:` to add all jars and classes to this jar
static_libs: ["my-module-name-java"],
// use `libs:` to make these classes available during build time, but
// not add them to the jar, in case the classes are already present on the
// boot classpath (such as if it's in framework.jar) or another jar.
libs: ["my-module-name-java"],
// use `srcs:` with `-java-sources` if you want to add classes in this
// library jar directly, but you get transitive dependencies from
// somewhere else, such as the boot classpath or another jar.
srcs: ["my-module-name-java-source", ...],
...
}
# 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 una destinazione nel sistema di build
che puoi utilizzare per gestire l'API del modulo. Una volta compilato, foo-freeze-api
aggiunge una nuova definizione API in api_dir
o
aidl_api/name
, a seconda della versione di Android, e
aggiunge un file .hash
, entrambi rappresentanti 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. Fondamentalmente,
imports
in versions_with_info
viene copiato dal campo imports
. Tuttavia, l'ultima versione stabile è 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 build esegue
controlli di compatibilità tra le versioni bloccate e anche tra la versione Top of Tree (ToT)
e l'ultima versione bloccata.
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 ToT.
Per mantenere la stabilità di un'interfaccia, i proprietari possono aggiungere nuovi:
- Metodi alla fine di un'interfaccia (o metodi con nuovi seriali definiti in modo esplicito)
- Elementi alla fine di un oggetto Parcelable (richiede l'aggiunta di un valore predefinito per ogni elemento)
- Valori costanti
- In Android 11, gli enumeratori
- In Android 12, i campi fino alla fine di un'unione
Non sono consentite altre azioni e nessun altro può modificare un'interfaccia (altrimenti si rischia di entrare in conflitto con le modifiche apportate da un proprietario).
Per verificare che tutte le interfacce siano bloccate per il rilascio, puoi creare la build con le seguenti variabili di ambiente impostate:
AIDL_FROZEN_REL=true m ...
- build richiede che tutte le interfacce AIDL stabili siano bloccate e non abbiano specificato il campoowner:
.AIDL_FROZEN_OWNERS="aosp test"
- la build richiede che tutte le interfacce AIDL stabili siano 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 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 confondersi quando si combinano diverse versioni di tipi. In genere, per i pacchetti solo di tipi o comuni, questa operazione è sicura perché il codice deve già essere scritto per gestire i tipi sconosciuti dalle transazioni IPC.
Nel codice della piattaforma Android android.hardware.graphics.common
è l'esempio
più grande di questo tipo di upgrade della versione.
Utilizzare interfacce con controllo delle versioni
Metodi di interfaccia
In fase di runtime, quando si tenta di chiamare nuovi metodi su un server precedente, i nuovi client ricevono un errore o un'eccezione, a seconda del backend.
- Il backend
cpp
riceve::android::UNKNOWN_TRANSACTION
. - Il backend
ndk
riceveSTATUS_UNKNOWN_TRANSACTION
. - Il backend
java
riceveandroid.os.RemoteException
con un messaggio che indica che l'API non è implementata.
Per strategie per gestire questo problema, vedi query sulle versioni e utilizzo dei valori predefiniti.
Parcelable
Quando vengono aggiunti nuovi campi ai parcelable, i client e i server precedenti li eliminano. Quando nuovi client e server ricevono parcelable precedenti, 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 oggetto serializzabile.
I client non devono aspettarsi che i server utilizzino i nuovi campi a meno che non sappiano che il server implementa la versione in cui è definito il campo (vedi query sulle versioni).
Enum e costanti
Allo stesso modo, client e server devono rifiutare o ignorare costanti ed enumeratori non riconosciuti, a seconda dei casi, poiché in futuro potrebbero essere aggiunti altri valori. Ad esempio, un server non deve interrompersi quando riceve un enumeratore che non conosce. Il server deve ignorare l'enumeratore o restituire un valore 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'unione impostata sul nuovo campo a un server
precedente o quando un client precedente riceve l'unione da un nuovo server.
Gestire più versioni
Uno spazio dei nomi 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 unica che richiede una sola definizione
di ogni simbolo.
La build Android fornisce 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
dipendenze. Questi errori mostrano il grafico delle dipendenze dal modulo non riuscito alle versioni in conflitto della libreria aidl_interface
. Tutte le
dipendenze devono essere aggiornate per includere la stessa versione (di solito l'ultima)
di queste librerie.
Se la libreria di interfacce 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 viene introdotta una
nuova versione dell'interfaccia, questi valori predefiniti possono essere aggiornati e tutti i moduli
che li utilizzano vengono aggiornati insieme, garantendo 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 di versioni specifiche. Questa
situazione può diventare difficile da gestire quando ci sono moduli aidl_interface
comuni importati in più moduli aidl_interface
utilizzati
insieme negli stessi processi.
aidl_interfaces_defaults
può essere utilizzato per mantenere una definizione delle versioni più recenti delle dipendenze per un aidl_interface
che può essere aggiornato in un unico posto e utilizzato da tutti i moduli aidl_interface
che vogliono importare questa 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
Le interfacce in fase di sviluppo (sbloccate) non possono essere utilizzate sui dispositivi di rilascio, perché non è garantita la compatibilità con le versioni precedenti.
AIDL supporta il fallback in fase di runtime per queste librerie di interfacce scongelate in modo che il codice possa essere scritto in base all'ultima versione scongelata e possa comunque essere 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. Vedi Utilizzare interfacce con controllo delle versioni.
Flag di compilazione AIDL
Il flag che controlla questo comportamento è RELEASE_AIDL_USE_UNFROZEN
definito in build/release/build_flags.bzl
. true
indica che la versione scongelata dell'interfaccia viene utilizzata in fase di runtime e false
indica che le librerie delle versioni scongelate si comportano tutte come l'ultima versione congelata.
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 fornitore (oggetti VINTF) definiscono le versioni previste e quelle fornite su entrambi i lati dell'interfaccia fornitore.
La maggior parte dei dispositivi non Cuttlefish ha come target la matrice di compatibilità più recente
solo dopo il blocco delle interfacce, quindi non ci sono differenze nelle librerie
AIDL in base a RELEASE_AIDL_USE_UNFROZEN
.
Matrici
Le interfacce di proprietà del partner vengono aggiunte alle matrici di compatibilità specifiche per dispositivo o prodotto a cui il dispositivo fa riferimento durante lo sviluppo. Pertanto, quando una
nuova versione non bloccata di un'interfaccia viene aggiunta a una matrice di compatibilità,
le versioni bloccate precedenti devono rimanere per
RELEASE_AIDL_USE_UNFROZEN=false
. Puoi gestire questo problema utilizzando diversi file della matrice di compatibilità per diverse configurazioni di RELEASE_AIDL_USE_UNFROZEN
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 4 bloccata 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 compilazione in base al valore di
RELEASE_AIDL_USE_UNFROZEN
.
I manifest e i relativi frammenti dichiarano quale versione di un'interfaccia
implementa un servizio. Quando utilizzi l'ultima versione scongelata di un'interfaccia,
il manifest deve essere aggiornato per riflettere questa nuova versione. Quando
RELEASE_AIDL_USE_UNFROZEN=false
le voci del manifest vengono modificate da
libvintf
per riflettere la modifica nella libreria AIDL generata. La versione
è modificata rispetto alla versione non aggiornabile N
fino all'ultima versione non aggiornabile N - 1
. Pertanto, gli utenti non devono gestire più manifest o frammenti di manifest per ciascuno dei loro servizi.
Modifiche al client HAL
Il codice client HAL deve essere compatibile con le versioni precedenti di ogni versione precedente supportata. Quando RELEASE_AIDL_USE_UNFROZEN
è false
, i servizi hanno sempre
l'aspetto dell'ultima versione bloccata o di una precedente (ad esempio, la chiamata di nuovi metodi sbloccati 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 client dei fornitori e i client delle interfacce di proprietà dei partner.
Modifiche all'implementazione dell'HAL
La differenza più grande nello sviluppo HAL con lo sviluppo basato su flag è il
requisito che le implementazioni HAL siano compatibili con la versione
congelata più recente per funzionare quando RELEASE_AIDL_USE_UNFROZEN
è false
.
Tenere conto della compatibilità con le versioni precedenti nelle implementazioni e nel codice del dispositivo è un nuovo
esercizio. Consulta Utilizzare interfacce
con controllo delle versioni.
Le considerazioni sulla compatibilità con le versioni precedenti sono generalmente le stesse per i client e i server, nonché per il codice del framework e del fornitore, ma esistono differenze sottili di cui devi essere a conoscenza, in quanto ora stai implementando in modo efficace due versioni che utilizzano lo stesso codice sorgente (la versione attuale, non bloccata).
Esempio: un'interfaccia ha tre versioni bloccate. L'interfaccia viene aggiornata con un
nuovo metodo. Il client e il servizio vengono entrambi aggiornati per utilizzare la nuova libreria versione 4. Poiché la libreria V4 si basa su una versione non bloccata 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 metodi sui callback, devi gestire correttamente il caso in cui viene restituito UNKNOWN_TRANSACTION
. I client potrebbero implementare due versioni diverse di un callback in base alla configurazione della release, pertanto non puoi presumere 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 descritto in Utilizzare interfacce
con controllo delle 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();
}
}
I nuovi campi nei tipi esistenti (parcelable
, enum
, union
) potrebbero
non esistere o contenere i valori predefiniti quando RELEASE_AIDL_USE_UNFROZEN
è
false
e i valori dei nuovi campi che un servizio tenta di inviare vengono eliminati
durante l'uscita dal processo.
I nuovi tipi aggiunti in questa versione non congelata 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 utilizza l'interfaccia
remota. Tuttavia, con il supporto del controllo delle versioni basato sui flag, implementi due versioni diverse, quindi potresti voler 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 Esecuzione di query sulla versione dell'interfaccia dell'oggetto remoto.
Nuove interfacce stabili VINTF
Quando viene aggiunto un nuovo pacchetto di interfacce AIDL, non esiste un'ultima versione bloccata, quindi
non è possibile eseguire il fallback a un comportamento quando RELEASE_AIDL_USE_UNFROZEN
è
false
. Non utilizzare queste interfacce. Quando RELEASE_AIDL_USE_UNFROZEN
è
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 makefile 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, quindi non puoi aggiungerlo al dispositivo
in modo condizionale, puoi verificare se è dichiarato con
IServiceManager::isDeclared()
. Se è dichiarato e la registrazione non è riuscita, interrompi la procedura. Se non viene dichiarato, la registrazione non andrà a buon fine.
Nuove interfacce di estensione stabili VINTF
Le nuove interfacce di estensione
non hanno una versione precedente a cui fare riferimento e, poiché non sono
registrate in ServiceManager
o dichiarate nei manifest VINTF,
IServiceManager::isDeclared()
non può essere utilizzato per determinare quando collegare l'interfaccia di estensione a un'altra interfaccia.
La variabile RELEASE_AIDL_USE_UNFROZEN
può essere utilizzata per determinare se
collegare la nuova interfaccia dell'estensione scongelata a quella esistente per evitare
di utilizzarla sui dispositivi rilasciati. L'interfaccia deve essere bloccata per essere utilizzata sui
dispositivi rilasciati.
I test VTS vts_treble_vintf_vendor_test
e vts_treble_vintf_framework_test
rilevano quando un'interfaccia di estensione non bloccata viene utilizzata in un dispositivo
rilasciato e generano un errore.
Se l'interfaccia dell'estensione non è nuova e ha una versione precedente bloccata, viene ripristinata quella versione precedente bloccata e non sono necessari passaggi aggiuntivi.
Cuttlefish come strumento di sviluppo
Ogni anno, dopo il blocco di 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 dell'anno successivo. Modifichiamo
target-level
e PRODUCT_SHIPPING_API_LEVEL
per assicurarci che ci sia un
dispositivo di lancio testato e che soddisfi i nuovi requisiti per la release del prossimo
anno.
Quando RELEASE_AIDL_USE_UNFROZEN
è true
, Cuttlefish viene
utilizzato per lo sviluppo delle future versioni di Android. Ha come target il livello FCM della release di Android
del prossimo anno e PRODUCT_SHIPPING_API_LEVEL
, pertanto deve soddisfare
i requisiti software del fornitore (VSR) della release successiva.
Quando RELEASE_AIDL_USE_UNFROZEN
è false
, Cuttlefish ha il precedente
target-level
e PRODUCT_SHIPPING_API_LEVEL
per riflettere un dispositivo di rilascio.
In Android 14 e versioni precedenti, questa differenziazione veniva
ottenuta con rami Git diversi che non rilevano la modifica a FCM
target-level
, al livello API di spedizione o a qualsiasi altro codice che ha come target la versione
successiva.
Regole di denominazione dei moduli
In Android 11, per ogni combinazione delle versioni e dei backend abilitati, viene creato automaticamente un modulo di libreria stub. Per fare riferimento
a un modulo di libreria stub specifico per il collegamento, non utilizzare il nome del
modulo aidl_interface
, ma il nome del modulo di libreria stub, ovvero
ifacename-version-backend, dove
ifacename
: nome del moduloaidl_interface
version
è uno dei seguenti valori:Vversion-number
per le versioni non aggiornabiliVlatest-frozen-version-number + 1
per la versione tip-of-tree (ancora da bloccare)
backend
è uno dei seguenti valori:java
per il backend Java,cpp
per il backend C++,ndk
ondk_platform
per il backend NDK. Il primo riguarda le app, mentre il secondo 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 con il nome foo e che la sua ultima versione 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 (l'ultima versione stabile)
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 faceva riferimento all'ultima versione stabile, diventafoo-V2-backend
foo-unstable-backend
, che si riferiva alla versione ToT diventafoo-V3-backend
I nomi dei file di output sono sempre uguali ai nomi dei moduli.
- Basata sulla 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 di ToT:
foo-V3-(cpp|ndk|ndk_platform|rust).so
Tieni presente che il compilatore AIDL non crea un modulo di versione unstable
né 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 di interfaccia meta
Android 10 aggiunge diversi metodi di meta-interfaccia per l'AIDL stabile.
Esegui una 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 i valori 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, la parte remota DEVE implementare getInterfaceVersion()
e
getInterfaceHash()
nel seguente modo (super
viene utilizzato al posto di IFoo
per evitare
errori di copia e incolla. A seconda della configurazione di javac
, potrebbe essere necessario
l'attributo @SuppressWarnings("static")
per disattivare gli avvisi:
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 client e server (ad esempio, le classi possono trovarsi nel classpath di avvio). Quando le classi vengono condivise, il server viene collegato anche alla
versione più recente delle classi, anche se potrebbe essere stato creato con una versione
precedente dell'interfaccia. Se questa meta interfaccia viene implementata nella classe condivisa, restituisce sempre la versione più recente. Tuttavia, implementando il metodo
come sopra, il numero di versione dell'interfaccia viene incorporato nel codice del server
(perché IFoo.VERSION
è un static final int
che viene incorporato quando viene fatto riferimento)
e quindi il metodo può restituire la versione esatta con cui è stato creato il server.
Gestire interfacce precedenti
È possibile che un client venga aggiornato con la versione più recente di un'interfaccia AIDL, ma 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. Nel lato client, puoi impostare un'implementazione predefinita per un'interfaccia AIDL. Un metodo nell'implementazione predefinita viene richiamato solo quando il metodo non è implementato nel 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 potenzialmente 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 vengono implementati sicuramente nella parte remota
(perché hai la certezza che la parte remota è stata creata quando i metodi erano nella
descrizione dell'interfaccia AIDL) non devono essere sottoposti a override nella classe impl
predefinita.
Convertire l'AIDL esistente in AIDL strutturato o stabile
Se hai un'interfaccia AIDL e un codice esistenti che la utilizzano, segui questi passaggi per convertire l'interfaccia in un'interfaccia AIDL stabile.
Identifica tutte le dipendenze dell'interfaccia. Per ogni pacchetto da 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 pacchetto
aidl_interface
(di seguito).Crea un pacchetto
aidl_interface
(come descritto sopra) che contenga il nome del modulo, le relative dipendenze e qualsiasi altra informazione necessaria. Per essere stabilizzato (non solo strutturato), deve anche essere sottoposto al controllo delle versioni. Per ulteriori informazioni, consulta Interfacce di controllo delle versioni.