AIDL stabile

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 AIDL Foo definito in un pacchetto com.acme deve essere <base_path>/com/acme/Foo.aidl, dove <base_path> può essere qualsiasi directory correlata alla directory in cui si trova Android.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 moduli aidl_interface utilizzati. Se una delle tue interfacce AIDL utilizza un'interfaccia o un parcelable di un altro aidl_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 12
  • versions: Le versioni precedenti dell'interfaccia che sono bloccate in api_dir. A partire da Android 11, le versions sono bloccate in aidl_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 da versions_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 in aidl_api/IFACE/V. Questo campo è stato introdotto in Android 13 e non deve essere modificato direttamente in Android.bp. Il campo viene aggiunto o aggiornato richiamando *-update-api o *-freeze-api. Inoltre, i campi versions vengono migrati automaticamente a versions_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". Se stability non è impostato, il sistema di build verifica che l'interfaccia sia compatibile con le versioni precedenti, a meno che non sia specificato unstable. 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 in system.img e nelle partizioni correlate, o tutte le cose del fornitore, ad esempio le cose in vendor.img e nelle partizioni correlate). Se stability è 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 backend cpp e java.
  • host_supported: il flag facoltativo che, se impostato su true, 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 su true, 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 su true, indica che l'interfaccia non ha subito modifiche rispetto alla versione precedente. Ciò consente più controlli in fase di compilazione. Se impostato su false, significa che l'interfaccia è in fase di sviluppo e presenta nuove modifiche, quindi l'esecuzione di foo-freeze-api genera una nuova versione e modifica automaticamente il valore in true. 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 con ndk_header e cpp_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 quando backend.java.platform_apis è true.
  • backend.java.platform_apis: il flag facoltativo che deve essere impostato su true 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 campo owner:.
  • AIDL_FROZEN_OWNERS="aosp test" - la build richiede che tutte le interfacce AIDL stabili siano bloccate con il campo owner: 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 riceve STATUS_UNKNOWN_TRANSACTION.
  • Il backend java riceve android.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 modulo aidl_interface
  • version è uno dei seguenti valori:
    • Vversion-number per le versioni non aggiornabili
    • Vlatest-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 o ndk_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 solo ndk.
    • 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, diventa foo-V2-backend
  • foo-unstable-backend, che si riferiva alla versione ToT diventa foo-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.

  1. 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.

  2. 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).

  3. 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.