AIDL stabile

Android 10 aggiunge il supporto per Android Interface Definition Language (AIDL) stabile, un nuovo modo per tenere traccia dell'interfaccia del programma applicativo (API)/interfaccia binaria dell'applicazione (ABI) fornita dalle interfacce AIDL. L'AIDL stabile presenta le seguenti differenze chiave rispetto all'AIDL:

  • Le interfacce sono definite nel sistema di compilazione con aidl_interfaces .
  • Le interfacce possono contenere solo dati strutturati. I parcelable che rappresentano i tipi desiderati vengono creati automaticamente in base alla relativa 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 loro API viene tracciata e con versione in un file accanto all'interfaccia AIDL.

AIDL strutturato e stabile

L'AIDL strutturato si riferisce ai tipi definiti esclusivamente in AIDL. Ad esempio, una dichiarazione parcelable (una dichiarazione parcelable personalizzata) non è AIDL strutturato. I parcellabili con i loro campi definiti in AIDL sono chiamati parcellabili strutturati .

L'AIDL stabile richiede un 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 deve inoltre utilizzare le seguenti funzionalità di controllo delle versioni. Al contrario, un'interfaccia non è stabile se per crearla viene utilizzato il sistema di compilazione principale o se è impostato unstable:true .

Definizione di un'interfaccia AIDL

Una definizione di aidl_interface è simile alla seguente:

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 dei file sorgente AIDL che compongono l'interfaccia. Il percorso per un Foo di tipo AIDL definito in un pacchetto com.acme dovrebbe essere in <base_path>/com/acme/Foo.aidl , dove <base_path> potrebbe essere qualsiasi directory correlata alla directory in cui si trova Android.bp . Nell'esempio sopra, <base_path> è srcs/aidl .
  • local_include_dir : il percorso da cui inizia il nome del pacchetto. Corrisponde al <base_path> spiegato sopra.
  • imports : un elenco di moduli aidl_interface utilizzati da questo. Se una delle tue interfacce AIDL utilizza un'interfaccia o un parcelable da un'altra aidl_interface , inserisci qui il suo nome. Può essere il nome stesso, per fare riferimento alla versione più recente, o il nome con il suffisso della versione (come -V1 ) per fare riferimento a una versione specifica. La specifica di una versione è supportata 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 ci sono versioni bloccate di un'interfaccia, questo non dovrebbe essere specificato e non ci saranno controlli di compatibilità. Questo campo è stato sostituito versions_with_info per 13 e versioni successive.
  • versions_with_info : elenco di tuple, ognuna delle quali contiene il nome di una versione congelata e un elenco con le versioni importate di altri moduli helpl_interface importati da questa versione di helpl_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 automaticamente migrati in versions_with_info quando un utente invoca *-update-api o *-freeze-api .
  • stability : il flag opzionale per la promessa di stabilità di questa interfaccia. Attualmente supporta solo "vintf" . Se non è impostato, corrisponde a un'interfaccia con stabilità all'interno di questo contesto di compilazione (quindi un'interfaccia caricata qui può essere utilizzata solo con cose compilate insieme, ad esempio su system.img). Se è impostato su "vintf" , ciò corrisponde a una promessa di stabilità: l'interfaccia deve essere mantenuta stabile finché viene utilizzata.
  • gen_trace : il flag opzionale per attivare o disattivare la traccia. A partire da Android 14, l'impostazione predefinita è 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 opzionale utilizzato per contrassegnare che non è necessario che questa interfaccia sia stabile. Quando è impostato su true , il sistema di compilazione non crea il dump API per l'interfaccia né ne richiede l'aggiornamento.
  • frozen : il flag opzionale che, se impostato su true , indica che l'interfaccia non ha modifiche rispetto alla versione precedente dell'interfaccia. 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 genererà una nuova versione e cambierà automaticamente il valore in true . Introdotto in Android 14.
  • backend.<type>.enabled : questi flag attivano/disattivano ciascuno dei backend per i quali il compilatore AIDL genera codice. Attualmente sono supportati quattro backend: Java, C++, NDK e Rust. I backend Java, C++ e NDK sono abilitati per impostazione predefinita. Se uno qualsiasi di questi tre backend non è necessario, deve essere disabilitato esplicitamente. Rust è disabilitato per impostazione predefinita.
  • backend.<type>.apex_available : l'elenco dei nomi APEX per cui è disponibile la libreria stub generata.
  • backend.[cpp|java].gen_log : il flag opzionale che controlla se generare codice aggiuntivo per raccogliere informazioni sulla transazione.
  • backend.[cpp|java].vndk.enabled : il flag opzionale per rendere questa interfaccia una parte di VNDK. L'impostazione predefinita è 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 non dovrebbe essere impostato quando backend.java.platform_apis è vero.
  • backend.java.platform_apis : il flag facoltativo che deve essere impostato su true quando le librerie generate devono essere compilate con l'API della piattaforma anziché con l'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, vedere Regole di denominazione dei moduli .

Scrittura di file AIDL

Le interfacce in AIDL stabile sono simili alle interfacce tradizionali, con l'eccezione che non è consentito utilizzare parcelable non strutturati (perché questi non sono stabili! vedere AIDL strutturato e AIDL stabile ). La differenza principale nell'AIDL stabile è il modo in cui vengono definiti i parcelable. In precedenza, i beni parcellabili venivano dichiarati in anticipo ; nell'AIDL stabile (e quindi strutturato), i campi e le variabili parcellabili sono definiti esplicitamente.

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

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

Attualmente è supportato (ma non richiesto) un valore predefinito per boolean , char , float , double , byte , int , long e String . In Android 12 sono supportate anche le impostazioni predefinite 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 su 0 anche se non è presente un enumeratore zero.

Utilizzo delle librerie stub

Dopo aver aggiunto le librerie stub come dipendenza al tuo modulo, puoi includerle nei tuoi file. Ecco alcuni esempi di librerie stub nel sistema di compilazione ( Android.mk può essere utilizzato anche 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

Dichiarare un modulo con nome foo crea anche una destinazione nel sistema di compilazione che puoi utilizzare per gestire l'API del modulo. Una volta creato, 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 versione appena 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 vengono copiate dal campo imports . Ma l'ultima versione stabile è specificata in imports versions_with_info per l'importazione che non ha una versione esplicita. Una volta specificata la proprietà versions_with_info , il sistema di compilazione esegue controlli di compatibilità tra le versioni congelate e anche tra Top of Tree (ToT) e l'ultima versione congelata.

Inoltre, è necessario gestire la definizione 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 API della versione ToT.

Per mantenere la stabilità di un'interfaccia, i proprietari possono aggiungere nuovi:

  • Metodi fino alla fine di un'interfaccia (o metodi con nuovi seriali definiti esplicitamente)
  • Elementi fino alla fine di un parcelable (richiede l'aggiunta di un valore predefinito per ciascun elemento)
  • Valori costanti
  • In Android 11, 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 rischiano di entrare in collisione con le modifiche apportate da un proprietario).

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

  • AIDL_FROZEN_REL=true m ... - la build richiede il congelamento di tutte le interfacce AIDL stabili che non hanno owner: campo specificato.
  • AIDL_FROZEN_OWNERS="aosp test" - la build richiede che tutte le interfacce AIDL stabili siano congelate con il owner: campo specificato come "aosp" o "test".

Stabilità delle importazioni

L'aggiornamento delle versioni delle importazioni per le versioni congelate di un'interfaccia è compatibile con le versioni precedenti al livello AIDL stabile. Tuttavia, l'aggiornamento di questi richiede l'aggiornamento di tutti i server e client che utilizzano la vecchia versione dell'interfaccia e alcune applicazioni potrebbero essere confuse quando si mescolano diverse versioni di tipi. In genere, per i pacchetti di soli tipi o comuni, questo è sicuro perché è necessario che il codice sia già stato scritto per gestire i tipi sconosciuti dalle transazioni IPC.

Nel codice della piattaforma Android android.hardware.graphics.common è il più grande esempio di questo tipo di aggiornamento della versione.

Utilizzo di interfacce con versione

Metodi di interfaccia

In fase di esecuzione, quando si tenta di chiamare nuovi metodi su un vecchio server, i nuovi client ricevono un errore o un'eccezione, a seconda del backend.

  • Il backend cpp ottiene ::android::UNKNOWN_TRANSACTION .
  • Il backend ndk ottiene STATUS_UNKNOWN_TRANSACTION .
  • Il backend java ottiene android.os.RemoteException con un messaggio che dice che l'API non è implementata.

Per le strategie per gestire questo problema vedere interrogazione delle versioni e utilizzo dei valori predefiniti .

Parcellabili

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

I client non devono aspettarsi che i server utilizzino i nuovi campi a meno che non sappiano che il server sta implementando la versione in cui è definito il campo (vedere query sulle versioni ).

Enumerazioni e costanti

Allo stesso modo, client e server dovrebbero rifiutare o ignorare valori costanti ed enumeratori non riconosciuti, a seconda dei casi, poiché altri potrebbero essere aggiunti in futuro. Ad esempio, un server non dovrebbe 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

Il tentativo di inviare un'unione con un nuovo campo fallisce se il destinatario è vecchio e non conosce il campo. L'implementazione non vedrà mai l'unione con il nuovo settore. 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 sta inviando un set di unione nel nuovo campo a un vecchio server o quando è un vecchio client a ricevere l'unione da un nuovo server.

Sviluppo basato su flag

Le interfacce in sviluppo (sbloccate) non possono essere usate sui dispositivi di rilascio perché non è garantita la compatibilità con le versioni precedenti.

AIDL supporta il fallback in fase di esecuzione per queste librerie di interfaccia non congelate in modo che il codice possa essere scritto rispetto all'ultima versione non congelata e possa essere comunque utilizzato sui dispositivi di rilascio. Il comportamento compatibile con le versioni precedenti dei client è simile al comportamento esistente e con il fallback anche le implementazioni devono seguire tali comportamenti. Vedere Utilizzo delle interfacce con versione .

Flag di creazione AIDL

Il flag che controlla questo comportamento è RELEASE_AIDL_USE_UNFROZEN definito in build/release/build_flags.bzl . true significa che la versione non bloccata dell'interfaccia viene utilizzata in fase di esecuzione e false significa che le librerie delle versioni non bloccate si comportano tutte come l'ultima versione bloccata. È possibile sovrascrivere il flag su true per lo sviluppo locale, ma è necessario ripristinarlo su false prima del rilascio. In genere lo sviluppo viene eseguito con una configurazione con il flag impostato su true .

Matrice di compatibilità e manifesti

Gli oggetti dell'interfaccia del fornitore (oggetti VINTF) definiscono quali versioni sono previste e quali versioni sono fornite su entrambi i lati dell'interfaccia del fornitore.

La maggior parte dei dispositivi non Cuttlefish prendono di mira la matrice di compatibilità più recente solo dopo che le interfacce sono state congelate, quindi non c'è differenza nelle librerie AIDL basate su RELEASE_AIDL_USE_UNFROZEN .

Matrici

Le interfacce di proprietà del partner vengono aggiunte alle matrici di compatibilità specifiche del dispositivo o del prodotto a cui il dispositivo si rivolge 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 . È possibile gestire questa situazione utilizzando diversi file della matrice di compatibilità per diverse configurazioni 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, è possibile rimuovere la versione 3 dalla matrice di compatibilità perché la versione 4 bloccata viene utilizzata quando RELEASE_AIDL_USE_UNFROZEN è false .

Manifesta

In Android 15 (AOSP sperimentale), 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 manifesti e i frammenti del manifesto dichiarano quale versione di un'interfaccia implementa un servizio. Quando si utilizza l'ultima versione sbloccata 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 viene modificata dalla versione non congelata, N , all'ultima versione congelata 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 ciascuna versione bloccata supportata in precedenza. Quando RELEASE_AIDL_USE_UNFROZEN è false , i servizi appaiono sempre come l'ultima versione congelata o precedente (ad esempio, la chiamata di nuovi metodi non congelati 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 client dei fornitori e i client delle interfacce di proprietà dei partner.

Modifiche all'implementazione dell'HAL

La differenza più grande tra lo sviluppo HAL e lo sviluppo basato su flag è il requisito che le implementazioni HAL siano retrocompatibili con l'ultima versione congelata per poter funzionare quando RELEASE_AIDL_USE_UNFROZEN è false . Considerare la compatibilità con le versioni precedenti nelle implementazioni e nel codice del dispositivo è un esercizio nuovo. Vedere Utilizzo delle interfacce con versione .

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 sottili differenze di cui devi essere consapevole, poiché ora stai effettivamente implementando due versioni che utilizzano lo stesso codice sorgente (la versione attuale, non congelata).

Esempio: un'interfaccia ha tre versioni congelate. L'interfaccia viene aggiornata con un nuovo metodo. Il client e il servizio sono entrambi aggiornati per utilizzare la nuova libreria versione 4. Poiché la libreria V4 è basata 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'uso del nuovo metodo.

Quando l'interfaccia è bloccata, tutti i valori di RELEASE_AIDL_USE_UNFROZEN utilizzano quella versione bloccata e il codice che gestisce la compatibilità con le versioni precedenti può essere rimosso.

Quando si chiamano metodi sui callback, è necessario gestire con garbo il caso in cui viene restituito UNKNOWN_TRANSACTION . I client potrebbero implementare due diverse versioni di un callback in base alla configurazione di rilascio, quindi non si può presumere che il client invierà la versione più recente e i nuovi metodi potrebbero restituirla. Questo è simile al modo in cui i client AIDL stabili mantengono la compatibilità con i server descritti in Utilizzo delle interfacce con versione .

// 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();
  }
}

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 all'uscita del 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 alcun client quando RELEASE_AIDL_USE_UNFROZEN è false .

Fare attenzione a utilizzare i nuovi enumeratori solo con la versione in cui sono stati introdotti e non con la versione precedente.

Normalmente, usi foo->getInterfaceVersion() per vedere quale versione sta utilizzando l'interfaccia remota. Tuttavia, con il supporto del controllo delle versioni basato su flag, stai implementando due versioni diverse, quindi potresti voler ottenere la versione dell'interfaccia corrente. Puoi farlo ottenendo la versione dell'interfaccia dell'oggetto corrente, ad esempio this->getInterfaceVersion() o gli altri metodi per my_ver . Per ulteriori informazioni, vedere Interrogazione sulla versione dell'interfaccia dell'oggetto remoto .

Nuove interfacce stabili VINTF

Quando viene aggiunto un nuovo pacchetto di interfaccia AIDL, non esiste l'ultima versione congelata, quindi non esiste alcun comportamento a cui ricorrere quando RELEASE_AIDL_USE_UNFROZEN è false . Non utilizzare queste interfacce. Quando RELEASE_AIDL_USE_UNFROZEN è false , Service Manager non consentirà al servizio di registrare l'interfaccia e i client non la troveranno.

È possibile 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 e quindi non è possibile aggiungerlo al dispositivo in modo condizionale, è possibile verificare se il servizio è dichiarato con IServiceManager::isDeclared() . Se viene dichiarato e la registrazione non è riuscita, interrompere il processo. Se non è dichiarato, è previsto che la registrazione non riesca.

La seppia come strumento di sviluppo

Ogni anno, dopo il congelamento del VINTF, adattiamo il target-level della matrice di compatibilità del framework (FCM) e il PRODUCT_SHIPPING_API_LEVEL di Cuttlefish in modo che riflettano il lancio dei dispositivi con il rilascio del prossimo anno. Modifichiamo target-level e PRODUCT_SHIPPING_API_LEVEL per assicurarci che ci sia qualche dispositivo di lancio testato e che soddisfi i nuovi requisiti per il rilascio del prossimo anno.

Quando RELEASE_AIDL_USE_UNFROZEN è true , Cuttlefish viene utilizzato per lo sviluppo di future versioni di Android. Ha come obiettivo il livello FCM e PRODUCT_SHIPPING_API_LEVEL della versione Android del prossimo anno, richiedendo che soddisfi i requisiti software del fornitore (VSR) della versione successiva.

Quando RELEASE_AIDL_USE_UNFROZEN è false , Cuttlefish ha il target-level precedente e PRODUCT_SHIPPING_API_LEVEL per riflettere un dispositivo di rilascio. In Android 14 e versioni precedenti, questa differenziazione verrebbe realizzata con diversi rami Git che non rilevano la modifica a FCM target-level , livello API di spedizione o qualsiasi altro codice destinato alla versione successiva.

Regole di denominazione dei moduli

In Android 11, per ogni combinazione di versioni e backend abilitati, viene creato automaticamente un modulo di 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, che è ifacename - version - backend , dove

  • ifacename : nome del modulo aidl_interface
  • version è una delle due
    • V version-number per le versioni congelate
    • V latest-frozen-version-number + 1 per la versione sulla punta dell'albero (ancora da congelare)
  • backend è uno dei due
    • java per il backend Java,
    • cpp per il backend C++,
    • ndk o ndk_platform per il backend NDK. Il primo è per le app e il secondo è per l'utilizzo della piattaforma,
    • rust per il backend Rust.

Supponiamo che esista un modulo con nome foo e che la sua ultima versione sia 2 e supporti sia NDK che C++. In questo caso, AIDL genera questi moduli:

  • Basato sulla versione 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Basato sulla versione 2 (l'ultima versione stabile)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Basato sulla versione ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Rispetto ad Android 11,

  • foo- backend , che si riferiva 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 gli stessi dei nomi dei moduli.

  • Basato sulla versione 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Basato sulla versione 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Basato sulla versione ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

Tieni presente che il compilatore AIDL non crea né un modulo con 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 di metainterfaccia

Android 10 aggiunge diversi metodi di metainterfaccia per AIDL stabile.

Interrogazione della versione dell'interfaccia dell'oggetto remoto

I client possono interrogare la versione e l'hash dell'interfaccia che l'oggetto remoto sta implementando 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, il lato remoto DEVE implementare getInterfaceVersion() e getInterfaceHash() come segue ( super viene utilizzato al posto di IFoo per evitare errori di copia/incolla. L'annotazione @SuppressWarnings("static") potrebbe essere necessaria per disabilitare gli avvisi, a seconda di la configurazione javac ):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

Questo perché le classi generate ( IFoo , IFoo.Stub , ecc.) sono condivise tra il client e il server (ad esempio, le classi possono trovarsi nel classpath di avvio). Quando le classi vengono condivise, il server è collegato anche alla versione più recente delle classi anche se potrebbe essere stata creata con una versione precedente dell'interfaccia. Se questa metainterfaccia è implementata nella classe condivisa, restituisce sempre la versione più recente. Tuttavia, implementando il metodo come sopra, il numero di versione dell'interfaccia è incorporato nel codice del server (perché IFoo.VERSION è un static final int che viene inline quando viene fatto riferimento) e quindi il metodo può restituire la versione esatta in cui è stato creato il server con.

Gestione delle interfacce meno recenti

È 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, chiamare un metodo su una vecchia interfaccia restituisce UNKNOWN_TRANSACTION .

Con AIDL stabile, i clienti hanno un maggiore controllo. Sul lato client, puoi impostare un'implementazione predefinita su un'interfaccia AIDL. Un metodo nell'implementazione predefinita viene richiamato solo quando il metodo non è implementato sul lato remoto (perché è stato creato con una versione precedente dell'interfaccia). Poiché i valori predefiniti sono impostati a livello globale, non dovrebbero 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 di cui è garantita l'implementazione sul lato remoto (perché sei sicuro che il remoto sia stato creato quando i metodi erano nella descrizione dell'interfaccia AIDL) non devono essere sovrascritti nella classe impl predefinita.

Conversione dell'AIDL esistente in AIDL strutturato/stabile

Se disponi di un'interfaccia AIDL esistente e di un codice che la utilizza, segui i passaggi seguenti per convertire l'interfaccia in un'interfaccia AIDL stabile.

  1. Identifica tutte le dipendenze della tua interfaccia. Per ogni pacchetto da cui dipende l'interfaccia, determinare se il pacchetto è definito in AIDL stabile. Se non definito, il pacchetto deve essere convertito.

  2. Converti tutti i parcelable nella tua interfaccia in parcelable stabili (i file dell'interfaccia stessi possono rimanere invariati). Fallo esprimendo la loro struttura direttamente nei file AIDL. Le classi di gestione devono essere riscritte per utilizzare questi nuovi tipi. Questo può essere fatto prima di creare un pacchetto aidl_interface (sotto).

  3. Crea un pacchetto aidl_interface (come descritto sopra) che contenga il nome del tuo modulo, le sue dipendenze e qualsiasi altra informazione di cui hai bisogno. Per renderlo stabilizzato (non solo strutturato), necessita anche di una versione. Per ulteriori informazioni, consulta Interfacce di controllo delle versioni .