AIDL stabile

Android 10 aggiunge il supporto per Android Interface Definition Language (AIDL) stabile, un nuovo modo per tenere traccia dell'API (Application Program Interface)/dell'interfaccia Application Binary Interface (ABI) fornita dalle interfacce AIDL. Il modello AIDL stabile presenta le seguenti differenze fondamentali rispetto all'AIDL:

  • Le interfacce sono definite nel sistema di compilazione con aidl_interfaces.
  • Le interfacce possono contenere solo dati strutturati. I pacchetti catastali che rappresentano i tipi desiderati vengono creati automaticamente in base alla relativa definizione AIDL e vengono sottoposti automaticamente a marshalling e non.
  • Le interfacce possono essere dichiarate come stabili (compatibili con le versioni precedenti). In questo caso, viene eseguito il tracciamento e il controllo delle versioni dell'API in un file accanto all'interfaccia AIDL.

AIDL strutturato e stabile a confronto

AIDL strutturato si riferisce ai tipi definiti esclusivamente in AIDL. Ad esempio, una dichiarazione "parcelable" (parcelabile personalizzata) non è un AIDL strutturato. I dati catastali con i rispettivi campi definiti in AIDL vengono chiamati parcelabili strutturati.

Stable AIDL richiede un AIDL strutturato in modo che il sistema di compilazione e il compilatore possano comprendere se le modifiche apportate ai partizionabili 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 build principale o se è impostato unstable:true.

Definizione di 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 di interfaccia AIDL che identifica in modo univoco un'interfaccia AIDL.
  • srcs: l'elenco di file di origine AIDL che compongono l'interfaccia. Il percorso per un tipo AIDL Foo definito in un pacchetto com.acme deve essere in <base_path>/com/acme/Foo.aidl, dove <base_path> potrebbe essere qualsiasi directory relativa 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 di aidl_interface moduli utilizzati. Se una delle interfacce AIDL utilizza un'interfaccia o un elemento parcelable di un altro aidl_interface, inserisci qui il suo nome. 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 12
  • versions: le versioni precedenti dell'interfaccia bloccate in api_dir, a partire da Android 11, le versions vengono bloccate in aidl_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 con versions_with_info per 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 della versione di altri moduli aidl_interface importati da questa versione di aidl_interface. La definizione della versione V di un'interfaccia IFACE AIDL si trova all'indirizzo 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, viene eseguita la migrazione automatica dei campi versions a versions_with_info quando un utente richiama *-update-api o *-freeze-api.
  • stability: il flag facoltativo per garantire la stabilità di questa interfaccia. Al momento questa opzione supporta solo "vintf". Se il criterio stability non viene configurato, il sistema di build controlla che l'interfaccia sia compatibile con le versioni precedenti, a meno che non venga specificato unstable. Se non viene configurato, corrisponde a un'interfaccia con stabilità all'interno di questo contesto di compilazione (quindi o tutti gli elementi del sistema, ad esempio elementi in system.img e nelle partizioni correlate, oppure tutti gli elementi del fornitore, ad esempio elementi in vendor.img e nelle partizioni correlate). Se il criterio stability 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 tracciamento. 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 contrassegnare che l'interfaccia non deve essere stabile. Se questo criterio viene impostato su true, il sistema di compilazione non crea il dump dell'API per l'interfaccia né richiede l'aggiornamento.
  • frozen: il flag facoltativo che indica che, se impostato su true, l'interfaccia non ha subito modifiche rispetto alla versione precedente. Ciò consente più controlli in fase di build. Se il criterio viene impostato su false, significa che l'interfaccia è in fase di sviluppo e presenta nuove modifiche, quindi l'esecuzione di foo-freeze-api genererà una nuova versione e cambierà 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 il codice. Attualmente sono supportati quattro backend: Java, C++, NDK e Rust. I backend Java, C++ e NDK sono abilitati per impostazione predefinita. Se uno di questi tre backend non è necessario, deve essere disabilitato esplicitamente. Rust è disattivato per impostazione predefinita fino ad Android 15 (sperimentale AOSP).
  • backend.<type>.apex_available: l'elenco di nomi APEX per cui è disponibile la libreria stub generata.
  • backend.[cpp|java].gen_log: il flag facoltativo che stabilisce se generare codice aggiuntivo per la raccolta di 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 viene creata la libreria stub Java. Il valore predefinito è "system_current". Questo valore non deve essere impostato se backend.java.platform_apis è impostato su true.
  • backend.java.platform_apis: il flag facoltativo che deve essere impostato su true quando le librerie generate devono essere create in base all'API della piattaforma anziché all'SDK.

Per ogni combinazione delle versioni e dei backend abilitati, viene creata una libreria stub. Per sapere come fare riferimento alla versione specifica della libreria stub per un backend specifico, consulta Regole di denominazione dei moduli.

Scrittura di file AIDL

Le interfacce nell'AIDL stabile sono simili alle interfacce tradizionali, con l'eccezione che non è consentito utilizzare partizioni non strutturati (perché non sono stabili. Consulta AIDL strutturato e stabile). La differenza principale in un AIDL stabile è il modo in cui vengono definiti i particellari. In precedenza, i dati catastali venivano dichiarati in avanti; nel caso di AIDL stabile (e quindi strutturato), i campi e le variabili catastali venivano definiti in modo esplicito.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

Al momento è 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 pari a 0 o vuoto. Le enumerazioni senza un valore predefinito vengono inizializzate a 0 anche se non è presente un enumeratore zero.

Utilizzo delle 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 compilazione (è possibile utilizzare anche Android.mk per le definizioni di moduli legacy):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire 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 una destinazione nel sistema di compilazione che puoi utilizzare per gestire l'API del modulo. Una volta creato, foo-free-api aggiunge una nuova definizione dell'API in api_dir o aidl_api/name, a seconda della versione di Android, e aggiunge un file .hash, entrambi che rappresentano la versione appena bloccata dell'interfaccia. foo-free-api aggiorna inoltre la proprietà versions_with_info in modo da 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. Una volta specificata la proprietà versions_with_info, il sistema di build esegue controlli di compatibilità tra le versioni bloccate e tra il 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 elementi:

  • Metodi alla fine di un'interfaccia (o metodi con nuovi numeri di serie esplicitamente definiti)
  • 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 sindacato

Non sono consentite altre azioni e nessun altro può modificare un'interfaccia (altrimenti rischiano di entrare in conflitto con le modifiche apportate da un proprietario).

Per verificare che tutte le interfacce siano bloccate per il rilascio, puoi creare con le seguenti variabili di ambiente impostate:

  • AIDL_FROZEN_REL=true m ...: la build richiede il blocco di tutte le interfacce AIDL stabili per le quali non è specificato un campo owner:.
  • AIDL_FROZEN_OWNERS="aosp test": la build richiede che tutte le interfacce AIDL stabili vengano 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 Stabile AIDL. Tuttavia, l'aggiornamento richiede l'aggiornamento di tutti i server e i client che utilizzano la versione precedente dell'interfaccia; alcune applicazioni potrebbero essere confuse quando si combinano versioni di tipi diversi. In genere, per i pacchetti comuni o solo di tipo, è sicuro perché il codice deve essere già scritto per gestire i tipi sconosciuti delle transazioni IPC.

Il codice della piattaforma Android android.hardware.graphics.common è l'esempio più importante di questo tipo di upgrade della versione.

Utilizzo delle interfacce con controllo delle versioni

Metodi di interfaccia

In fase di runtime, durante il tentativo di chiamare nuovi metodi su un vecchio server, 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 ottiene android.os.RemoteException con un messaggio che indica che l'API non è implementata.

Per le strategie per gestire questo problema, consulta Esecuzione di query sulle versioni e Utilizzo dei valori predefiniti.

Lotti da parte

Quando vengono aggiunti nuovi campi ai pacchetti, i vecchi client e server li eliminano. Quando i nuovi client e server ricevono vecchi pacchetti, i valori predefiniti per i nuovi campi vengono compilati automaticamente. Ciò significa che occorre specificare i valori predefiniti per tutti i nuovi campi in un

I client non dovrebbero aspettarsi che i server utilizzino i nuovi campi a meno che non sappiano che il server sta implementando la versione per cui è stato definito il campo (consulta la sezione Esecuzione di query sulle versioni).

Enum e costanti

Allo stesso modo, i client e i server devono rifiutare o ignorare valori costanti e enumeratori non riconosciuti, a seconda dei casi, poiché potrebbero essere aggiunti altri valori in futuro. Ad esempio, un server non deve interrompersi quando riceve un enumeratore di cui non è a conoscenza. Dovrebbe ignorarlo o restituire qualcosa in modo che il client sappia che non è supportato in questa implementazione.

Sindacati

L'invio di un Union con un nuovo campo non riesce se il destinatario è vecchio e non ne è a conoscenza. 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 nel nuovo campo a un vecchio server o se si tratta di un vecchio client che riceve il join da un nuovo server.

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 i fallback di runtime per queste librerie di interfacce sbloccate in modo che il codice possa essere scritto sull'ultima versione sbloccata e possa essere ancora utilizzato sui dispositivi di rilascio. Il comportamento compatibile con le versioni precedenti dei client è simile a quello esistente e, nel caso del fallback, anche le implementazioni devono seguire tali 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 in fase di esecuzione viene utilizzata la versione non bloccata dell'interfaccia, mentre false indica che le librerie delle versioni sbloccate si comportano tutte come la loro ultima versione bloccata. Puoi eseguire l'override del flag su true per lo sviluppo locale, ma devi ripristinare false prima del rilascio. In genere lo sviluppo viene eseguito con una configurazione con 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 su entrambi i 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, quindi non c'è differenza nelle librerie AIDL basate su RELEASE_AIDL_USE_UNFROZEN.

Matrici

Le interfacce di proprietà dei partner vengono aggiunte a matrici di compatibilità specifiche per dispositivo o prodotto che i dispositivi hanno scelto come target 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 delle matrici 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 sbloccata, 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 (AOSP sperimentale), 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 frammenti di manifest dichiarano la versione di un'interfaccia implementata da un servizio. Quando utilizzi l'ultima versione sbloccata di un'interfaccia, il file manifest deve essere aggiornato per riflettere la nuova versione. Quando RELEASE_AIDL_USE_UNFROZEN=false le voci del file manifest vengono regolate da libvintf per riflettere la modifica nella libreria AIDL generata. La versione viene modificata dalla versione sbloccata, N, all'ultima versione bloccata N - 1. Di conseguenza, gli utenti non devono gestire più manifest o frammenti di manifest per ciascuno dei loro servizi.

Modifiche client HAL

Il codice client HAL deve essere compatibile con le versioni precedenti con ogni versione bloccata precedente supportata. Quando RELEASE_AIDL_USE_UNFROZEN è false, i servizi appaiono sempre come l'ultima versione bloccata o una precedente (ad esempio, la chiamata di nuovi metodi sbloccati restituisce UNKNOWN_TRANSACTION o i nuovi campi parcelable hanno i loro 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 dell'HAL

La principale differenza nello sviluppo dell'HAL rispetto allo sviluppo basato su flag è il requisito che le implementazioni HAL devono essere compatibili con le versioni precedenti dell'ultima versione bloccata, in modo da poter funzionare quando RELEASE_AIDL_USE_UNFROZEN è false. La compatibilità con le versioni precedenti nelle implementazioni e nel codice del dispositivo è un nuovo esercizio. Consulta Utilizzare le interfacce con controllo delle versioni.

Le considerazioni sulla compatibilità con le versioni precedenti sono generalmente le stesse per client e server, nonché per il codice del framework e il codice del fornitore, ma ci sono lievi differenze di cui devi essere a conoscenza, poiché ora stai implementando in modo efficace 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 entrambi aggiornati per utilizzare la nuova libreria della 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 quella versione bloccata ed è possibile rimuovere il codice che gestisce la compatibilità con le versioni precedenti.

Quando chiami metodi sui callback, devi gestire agevolmente il caso quando viene restituito UNKNOWN_TRANSACTION. È possibile che i client implementino due versioni diverse di un callback in base alla configurazione della release, quindi non puoi presumere che il client invii la versione più recente e che i nuovi metodi potrebbero restituire questo risultato. Questo è simile a quello che i client AIDL stabili mantengono la compatibilità con le versioni precedenti con i server descritto in Utilizzo delle 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();
  }
}

È 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 sbloccata non possono essere inviati o ricevuti tramite l'interfaccia.

L'implementazione non riceve mai una chiamata per nuovi metodi da nessun client se RELEASE_AIDL_USE_UNFROZEN è false.

Fai attenzione a usare i nuovi enumeratori solo con la versione in cui sono introdotti e non con quella precedente.

Normalmente utilizzi foo->getInterfaceVersion() per vedere quale versione viene utilizzata dall'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 recuperando la versione dell'interfaccia dell'oggetto corrente, ad esempio this->getInterfaceVersion(), o gli altri metodi per my_ver. Per ulteriori informazioni, consulta Esecuzione di query sulla versione dell'interfaccia dell'oggetto remoto.

Nuove interfacce stabili VINTF

Quando viene aggiunto un nuovo pacchetto di interfaccia AIDL, non esiste un'ultima versione bloccata, quindi non c'è alcun comportamento a cui fare riferimento quando RELEASE_AIDL_USE_UNFROZEN è false. Non utilizzare queste interfacce. Se RELEASE_AIDL_USE_UNFROZEN è impostato su false, Service Manager non consentirà 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 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 e quindi non puoi aggiungerlo al dispositivo in modo condizionale, puoi verificare se il servizio viene dichiarato con IServiceManager::isDeclared(). Se viene dichiarato e la registrazione non è andata a buon fine, interrompi la procedura. Se non viene dichiarato, la registrazione potrebbe non riuscire.

Seppia come strumento di sviluppo

Ogni anno, dopo il congelamento del VINTF, modifichiamo la matrice di compatibilità del framework (FCM) target-level e il PRODUCT_SHIPPING_API_LEVEL di Seppia in modo che riflettano il lancio dei dispositivi in uscita il prossimo anno. Modifichiamo i criteri target-level e PRODUCT_SHIPPING_API_LEVEL per assicurarci che ci siano dispositivi di lancio testati e che soddisfino i nuovi requisiti per il rilascio del prossimo anno.

Quando il valore di RELEASE_AIDL_USE_UNFROZEN è true, Cuttlefish viene utilizzata per lo sviluppo di release future di Android. Ha come target il livello FCM e PRODUCT_SHIPPING_API_LEVEL della release Android del prossimo anno, che richiede quindi di soddisfare i requisiti software del fornitore (VSR) della release successiva.

Quando il valore di RELEASE_AIDL_USE_UNFROZEN è false, la seppia ha i valori target-level e PRODUCT_SHIPPING_API_LEVEL precedenti per riflettere un dispositivo di sgancio. In Android 14 e versioni precedenti, questa differenziazione verrebbe realizzata con diversi rami Git 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 uno specifico modulo 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 modulo aidl_interface
  • version è una di
    • Vversion-number per le versioni bloccate
    • Vlatest-frozen-version-number + 1 per la versione sulla punta dell'albero (ancora da congelare)
  • backend è una di
    • java per il backend Java,
    • cpp per il backend C++,
    • ndk o ndk_platform per il backend NDK. la prima riguarda le app e la seconda per l'utilizzo della piattaforma,
    • rust per il backend Rust.

Supponi che esista un modulo con il nome foo e che l'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 (la versione stabile più recente)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • In base alla versione ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Rispetto ad Android 11,

  • foo-backend, che fa riferimento all'ultima versione stabile, diventa foo-V2-backend
  • foo-unstable-backend, che fa riferimento alla versione ToT, diventa foo-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 né 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 sua versione.

Nuovi metodi della meta-interfaccia

Android 10 aggiunge diversi metodi di meta-interfaccia per l'AIDL stabile.

Esecuzione di query sulla versione dell'interfaccia dell'oggetto remoto

I client possono eseguire query sulla versione e sull'hash dell'interfaccia che l'oggetto remoto sta implementando e confrontare i valori restituiti con i valori dell'interfaccia utilizzati 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/incolla. A seconda della configurazione di javac, potrebbe essere necessaria l'annotazione @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 essere nel classpath di avvio). Quando i corsi sono condivisi, il server viene collegato anche alla versione più recente delle classi, anche se potrebbe essere stata creata con una versione precedente dell'interfaccia. Se questa meta-interfaccia è 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 meno recenti

È possibile che un client venga aggiornato con la versione più recente di un'interfaccia AIDL, ma il server stia utilizzando 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 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. Non è necessario eseguire l'override dei metodi remoti nella classe impl predefinita (perché hai la certezza che il telecomando viene creato quando i metodi erano nella descrizione dell'interfaccia AIDL).

Conversione di un AIDL esistente in AIDL strutturato/stabile

Se disponi di un'interfaccia AIDL esistente e di codice che la utilizza, 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 viene definito, il pacchetto deve essere convertito.

  2. Converti tutti i pacchetti "parcelable" nella tua interfaccia in "parcelables" stabili (i file dell'interfaccia possono rimanere invariati). Per farlo, esprime la loro struttura direttamente nei file AIDL. Le classi di gestione devono essere riscritte per usare 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 in precedenza) che contenga il nome del modulo, le sue dipendenze e tutte le altre informazioni necessarie. Per renderlo stabilizzato (non solo strutturato), deve anche essere sottoposto al controllo delle versioni. Per ulteriori informazioni, consulta la sezione Interfacce di controllo delle versioni.