AIDL stabile

Android 10 aggiunge il supporto per Android Interface Definition Language (AIDL) stabile, un nuovo modo per tenere traccia dell'API (Application Programming Interface) e dell'ABI (Application Binary Interface) fornite dalle interfacce AIDL. Stable AIDL funziona esattamente come AIDL, ma il sistema di compilazione monitora la compatibilità dell'interfaccia e ci sono limitazioni a ciò che puoi fare:

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

AIDL strutturato e stabile

AIDL strutturato si riferisce ai tipi definiti esclusivamente in AIDL. Ad esempio, una dichiarazione parcellable (un parcelable personalizzato) non è un AIDL strutturato. I parcelable con i relativi campi definiti in AIDL sono chiamati parcelable strutturati.

AIDL stabile richiede AIDL strutturato in modo che il sistema di compilazione e il compilatore possano capire se le modifiche apportate ai parcelable sono compatibili con le versioni precedenti. Tuttavia, non tutte le interfacce strutturate sono stabili. Per essere stabile, un'interfaccia deve utilizzare solo tipi strutturati, oltre alle seguenti funzionalità di controllo delle versioni. Al contrario, un'interfaccia non è stabile se per crearla viene utilizzato il sistema di compilazione principale o se è impostato unstable:true.

Definire un'interfaccia AIDL

Una definizione di aidl_interface ha il seguente aspetto:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name: il nome del modulo dell'interfaccia AIDL che identifica in modo univoco un'interfaccia AIDL.
  • srcs: l'elenco dei file di origine AIDL che compongono l'interfaccia. Il percorso per un tipo AIDL Foo definito in un pacchetto com.acme deve trovarsi in <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 dove inizia il nome del pacchetto. corrisponde a <base_path> descritto sopra.
  • imports: un elenco dei moduli aidl_interface utilizzati. Se una delle tue interfacce AIDL utilizza un'interfaccia o un parcelable di un altroaidl_interface, inserisci il nome qui. Può essere il nome da solo per fare riferimento alla versione più recente o il nome con il suffisso della versione (ad esempio -V1) per fare riferimento a una versione specifica. La specifica di una versione è supportata da Android 12
  • versions: le versioni precedenti dell'interfaccia bloccate in api_dir. A partire da Android 11, le versions sono 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 Android 13 e versioni successive.
  • versions_with_info: elenco di tuple, ciascuna delle quali contiene il nome di una versione bloccata e un elenco con importazioni di versione di altri moduli aidl_interface importati da questa versione di aidl_interface. La definizione della versione V di un'interfaccia AIDL IFACE si trova in aidl_api/IFACE/V. Questo campo è stato introdotto in Android 13 e non dovrebbe 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 la promessa di stabilità di questa interfaccia. Sono supportati 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. L'assenza di impostazione corrisponde a un'interfaccia con stabilitá all'interno di questo contesto di compilazione (quindi tutte le cose di sistema, ad esempio le cose in system.img e le partizioni correlate, o tutte le cose del fornitore, ad esempio le cose in vendor.img e le 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 monitoraggio. 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 questa interfaccia non deve essere stabile. Se questo valore è impostato su true, il sistema di compilazione non crea il dump dell'API per l'interfaccia né ne richiede l'aggiornamento.
  • frozen: il flag facoltativo che indica che, se impostato su true, l'interfaccia non ha subito modifiche rispetto alla versione precedente. Ciò consente più controlli in fase di build. Se impostato su false, significa che l'interfaccia è in 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 i quali il compilatore AIDL genera codice. Sono supportati quattro backend: Java, C++, NDK e Rust. I backend Java, C++ e NDK sono attivati per impostazione predefinita. Se uno di questi tre backend non è necessario, deve essere disattivato esplicitamente. Rust è disattivata per impostazione predefinita fino ad Android 15.
  • backend.<type>.apex_available: l'elenco dei nomi APEX per i quali è disponibile la libreria di stub generata.
  • backend.[cpp|java].gen_log: il flag facoltativo che controlla se generare codice aggiuntivo per raccogliere informazioni sulla transazione.
  • backend.[cpp|java].vndk.enabled: il flag facoltativo per rendere questa interfaccia parte di VNDK. Il valore predefinito è false.
  • backend.[cpp|ndk].additional_shared_libraries: introdotto in Android 14, questo flag aggiunge dipendenze alle librerie native. Questo flag è utile 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 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 piuttosto che all'SDK.

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

Scrivere file AIDL

Le interfacce in AIDL stabile sono simili a quelle tradizionali, con l'eccezione che non è consentito utilizzare elementi parcellizzabili non strutturati (in quanto non sono stabili, vedi AIDL strutturato e stabile). La differenza principale nell'AIDL stabile è il modo in cui vengono definiti i componenti parcellabili. In precedenza, i dati catastali venivano dichiarati in avanti; nel caso di AIDL stabile (e quindi strutturato), i campi e le variabili particellari vengono definiti in modo esplicito.

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

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

Un valore predefinito è supportato (ma non obbligatorio) per boolean, char, float, double, byte, int, long e String. In Android 12, sono supportati anche i valori predefiniti per le enumerazioni definite dall'utente. Se non viene specificato un valore predefinito, viene utilizzato un valore simile a 0 o vuoto. Le enumerazioni senza un valore predefinito vengono inizializzate a 0 anche se non esiste un enumeratore zero.

Utilizzare le librerie stub

Dopo aver aggiunto le librerie stub come dipendenza al modulo, puoi includerle nei file. Di seguito sono riportati alcuni esempi di librerie stub nel sistema di compilazione (Android.mk può essere utilizzato anche per le definizioni dei moduli precedenti):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

Esempio in C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Esempio in Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Esempio in Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

Interfacce di controllo delle versioni

La dichiarazione di un modulo con il nome foo crea anche un target nel sistema di compilazione che puoi utilizzare per gestire l'API del modulo. Al momento della compilazione, foo-freeze-api aggiunge una nuova definizione di API in api_dir o aidl_api/name, a seconda della versione di Android, e aggiunge un file .hash, entrambi che rappresentano la nuova versione bloccata dell' interfaccia. foo-freeze-api aggiorna anche la proprietà versions_with_info per riflettere la versione aggiuntiva e imports per la versione. In sostanza,imports in versions_with_info viene copiato dal campo imports. Tuttavia, la versione stabile più recente è specificata in imports in versions_with_info per l'importazione, che non ha una versione esplicita. Dopo aver specificato la proprietà versions_with_info, il sistema di compilazione esegue controlli di compatibilità tra le versioni bloccate e anche tra la versione principale (Top of Tree, ToT) e la versione bloccata più recente.

Inoltre, devi gestire la definizione dell'API della versione ToT. Ogni volta che un'API viene aggiornata, esegui foo-update-api per aggiornare aidl_api/name/current, che contiene la definizione dell'API della versione di Text-to-Speech.

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

  • Metodi alla fine di un'interfaccia (o metodi con nuovi seriali definiti esplicitamente)
  • Elementi alla fine di una particella (richiede l'aggiunta di un valore predefinito per ogni elemento)
  • Valori costanti
  • In Android 11, gli enumeratori
  • In Android 12, i campi alla fine di un'unione

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

Per verificare che tutte le interfacce siano bloccate per la release, puoi eseguire la compilazione impostando le seguenti variabili di ambiente:

  • AIDL_FROZEN_REL=true m ...: la compilazione richiede il blocco di tutte le interfacce AIDL stabili per le quali non è specificato alcun 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 livello AIDL stabile. Tuttavia, l'aggiornamento richiede l'aggiornamento di tutti i server e i client che utilizzano una versione precedente dell'interfaccia e alcune app potrebbero essere confuse se si mescolano versioni diverse di tipi. In genere, per i pacchetti solo di tipi o comuni, questa operazione è sicura perché il codice deve essere già scritto per gestire i tipi sconosciuti dalle transazioni IPC.

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

Utilizzare interfacce con versione

Metodi di interfaccia

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

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

Per le strategie per gestire questo problema, consulta Eseguire query sulle versioni e Utilizzare i valori predefiniti.

Lotti da parte

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

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

Enum e costanti

Analogamente, i client e i server devono rifiutare o ignorare i valori costanti e gli enumeratori non riconosciuti, in quanto potrebbero essere aggiunti altri in futuro. Ad esempio, un server non deve interrompersi quando riceve un enumeratore che non conosce. Il server deve ignorare l'enumeratore o restituire qualcosa in modo che il client sappia che non è supportato in questa implementazione.

Sindacati

Il tentativo di inviare un'unione con un nuovo campo non va a buon fine se il destinatario è precedente e non conosce il campo. L'implementazione non vedrà mai l'unione con il nuovo campo. L'errore viene ignorato se si tratta di una transazione unidirezionale; in caso contrario, l'errore è BAD_VALUE(per il backend C++ o NDK) o IllegalArgumentException(per il backend Java). L'errore viene ricevuto se il client invia un Union Set nel nuovo campo a un vecchio server o se si tratta di un vecchio client che riceve il Union Set da un nuovo server.

Gestire più versioni

Un namespace del linker in Android può avere una sola versione di un'interfaccia aidl specifica per evitare situazioni in cui i tipi aidl generati abbiano più definizioni. C++ ha la regola di definizione che richiede una sola definizione di ogni simbolo.

La compilazione Android genera un errore quando un modulo dipende da versioni diverse della stessa libreria aidl_interface. Il modulo potrebbe dipendere da queste librerie direttamente o indirettamente tramite le dipendenze delle loro dipendenze. Questi errori mostrano il grafico delle dipendenze dal modulo con errori alle versioni in conflitto della libreria aidl_interface. Tutte le dipendenze devono essere aggiornate in modo da includere la stessa versione (di solito la più recente) di queste librerie.

Se la libreria di interfaccia viene utilizzata da molti moduli diversi, può essere utile creare cc_defaults, java_defaults e rust_defaults per qualsiasi gruppo di librerie e processi che devono utilizzare la stessa versione. Quando si introduce una nuova versione dell'interfaccia, queste impostazioni predefinite possono essere aggiornate e tutti i moduli che le utilizzano vengono aggiornati insieme, assicurando che non utilizzino versioni diverse dell'interfaccia.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

Quando i moduli aidl_interface importano altri moduli aidl_interface, vengono create dipendenze aggiuntive che richiedono l'utilizzo congiunto di versioni specifiche. Questa situazione può diventare difficile da gestire quando esistono moduli aidl_interface comuni che vengono importati in più moduli aidl_interface utilizzati insieme nelle stesse procedure.

aidl_interfaces_defaults può essere utilizzato per mantenere una definizione delle versioni più recenti delle dipendenze per un aidl_interface che può essere aggiornata in un unico posto e utilizzata da tutti i moduli aidl_interface che vogliono importare l'interfaccia comune.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Sviluppo basato su flag

Non è possibile utilizzare le interfacce in fase di sviluppo (non bloccate) sui dispositivi di rilascio perché non è garantita la compatibilità con le versioni precedenti.

AIDL supporta il fallback in fase di esecuzione per queste librerie di interfaccia non bloccate in modo che il codice possa essere scritto in base all'ultima versione non bloccata ed essere comunque utilizzato sui dispositivi di rilascio. Il comportamento compatibile con le versioni precedenti dei client è simile al comportamento esistente e, con il fallback, anche le implementazioni devono seguire questi comportamenti. Consulta Utilizzare le interfacce con controllo delle versioni.

Flag build AIDL

Il flag che controlla questo comportamento è RELEASE_AIDL_USE_UNFROZEN definito in build/release/build_flags.bzl. true indica che la versione non bloccata dell'interfaccia viene utilizzata in fase di esecuzione, mentre false indica che le librerie delle versioni sbloccate si comportano tutte come la loro ultima versione bloccata. Puoi ignorare il flag impostandolo su true per lo sviluppo locale, ma devi ripristinarlo su false prima del rilascio. In genere, lo sviluppo viene eseguito con una configurazione in cui il flag è impostato su true.

Matrice di compatibilità e manifest

Gli oggetti dell'interfaccia del fornitore (oggetti VINTF) definiscono quali versioni sono previste e quali versioni vengono fornite ai lati dell'interfaccia del fornitore.

La maggior parte dei dispositivi non Cuttlefish ha come target la matrice di compatibilità più recente solo dopo il blocco delle interfacce, pertanto non esiste alcuna differenza nelle librerie AIDL basate su RELEASE_AIDL_USE_UNFROZEN.

Matrici

Le interfacce di proprietà del partner vengono aggiunte alle matrici di compatibilità specifiche per il dispositivo o per il prodotto che il dispositivo ha come target durante lo sviluppo. Pertanto, quando viene aggiunta a una matrice di compatibilità una nuova versione non bloccata di un'interfaccia, le versioni bloccate precedenti devono rimanere per RELEASE_AIDL_USE_UNFROZEN=false. Puoi gestire questo problema utilizzando diversi file della matrice di compatibilità per configurazioni RELEASE_AIDL_USE_UNFROZEN diverse o consentendo entrambe le versioni in un unico file della matrice di compatibilità utilizzato in tutte le configurazioni.

Ad esempio, quando aggiungi una versione 4 non bloccata, utilizza <version>3-4</version>.

Quando la versione 4 è bloccata, puoi rimuovere la versione 3 dalla matrice di compatibilità perché la versione bloccata 4 viene utilizzata quando RELEASE_AIDL_USE_UNFROZEN è false.

Manifest

In Android 15, viene introdotta una modifica in libvintf per modificare i file manifest in fase di creazione in base al valore di RELEASE_AIDL_USE_UNFROZEN.

I manifest e i relativi frammenti dichiarano la versione di un'interfaccia implementata da un servizio. Quando utilizzi la versione più recente non bloccata di un'interfaccia, il manifest deve essere aggiornato in base a questa nuova versione. Quando RELEASE_AIDL_USE_UNFROZEN=false le voci del file manifest vengono modificate da libvintf per riflettere la modifica nella libreria AIDL generata. La versione viene modificata dalla versione non bloccata N all'ultima versione bloccata N - 1. Pertanto, gli utenti non devono gestire più manifest o frammenti manifest per ciascuno dei loro servizi.

Modifiche client HAL

Il codice client HAL deve essere compatibile con le versioni precedenti di ogni versione bloccata precedente supportata. Quando RELEASE_AIDL_USE_UNFROZEN è false, i servizi hanno sempre lo stesso aspetto dell'ultima versione bloccata o precedente (ad esempio, l'uso di nuovi metodi non bloccati restituisce UNKNOWN_TRANSACTION o i nuovi campi parcelable hanno i valori predefiniti). I client del framework Android devono essere compatibili con le versioni precedenti aggiuntive, ma questo è un nuovo dettaglio per i clienti dei fornitori e quelli delle interfacce di proprietà dei partner.

Modifiche all'implementazione di HAL

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

Le considerazioni sulla compatibilità con le versioni precedenti sono in genere le stesse per i client e i server, nonché per il codice del framework e del fornitore, ma esistono differenze sottili che devi conoscere, poiché ora stai effettivamente implementando due versioni che utilizzano lo stesso codice sorgente (la versione corrente non bloccata).

Esempio: un'interfaccia ha tre versioni bloccate. L'interfaccia viene aggiornata con un nuovo metodo. Il client e il servizio vengono aggiornati per utilizzare la nuova libreria versione 4. Poiché la libreria V4 si basa su una versione sbloccata dell'interfaccia, si comporta come l'ultima versione bloccata, la versione 3, quando RELEASE_AIDL_USE_UNFROZEN è false e impedisce l'utilizzo del nuovo metodo.

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

Quando chiami i metodi sui callback, devi gestire in modo appropriato la situazione quando viene restituito UNKNOWN_TRANSACTION. I client potrebbero implementare due diverse versioni di un callback in base alla configurazione della release, pertanto non puoi assumere che il client invii la versione più recente e i nuovi metodi potrebbero restituire questo valore. Questo è simile al modo in cui i client AIDL stabili mantengono la compatibilità con le versioni precedenti dei server descritta in Utilizzare interfacce con versioni.

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

È possibile che i nuovi campi nei tipi esistenti (parcelable, enum, union) non esistano o non contengano i valori predefiniti quando RELEASE_AIDL_USE_UNFROZEN è false e i valori dei nuovi campi che un servizio tenta di inviare vengono eliminati all'uscita dal processo.

I nuovi tipi aggiunti in questa versione non bloccata non possono essere inviati o ricevuti tramite l'interfaccia.

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

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

In genere, utilizzi foo->getInterfaceVersion() per vedere quale versione è in uso nell'interfaccia remota. Tuttavia, con il supporto del controllo delle versioni basato su flag, stai implementando due versioni diverse, quindi ti consigliamo di ottenere la versione dell'interfaccia attuale. Puoi farlo ottenendo la versione dell'interfaccia dell'oggetto corrente, ad esempio this->getInterfaceVersion() o gli altri metodi per my_ver. Per ulteriori informazioni, consulta la sezione Eseguire query sulla versione dell'interfaccia dell'oggetto remoto.

Nuove interfacce VINTF stabili

Quando viene aggiunto un nuovo pacchetto di interfaccia AIDL, non esiste un'ultima versione bloccata, pertanto non è previsto alcun comportamento di riserva quando RELEASE_AIDL_USE_UNFROZEN è false. Non utilizzare queste interfacce. Quando RELEASE_AIDL_USE_UNFROZEN è equale a false, Service Manager non consente al servizio di registrare l'interfaccia e i client non la troveranno.

Puoi aggiungere i servizi in modo condizionale in base al valore del flag RELEASE_AIDL_USE_UNFROZEN nel file make del dispositivo:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

Se il servizio fa parte di un processo più ampio e quindi non puoi aggiungerlo al dispositivo in modo condizionale, puoi verificare se il servizio è stato dichiarato con IServiceManager::isDeclared(). Se è stato dichiarato e la registrazione non è riuscita, interrompete la procedura. Se non è dichiarata, la registrazione non andrà a buon fine.

Cuttlefish come strumento di sviluppo

Ogni anno dopo il blocco del VINTF, modifichiamo la matrice di compatibilità del framework (FCM) target-level e il PRODUCT_SHIPPING_API_LEVEL di Cuttlefish in modo che riflettano i dispositivi lanciati con la release del prossimo anno. Modifichiamo target-level e PRODUCT_SHIPPING_API_LEVEL per assicurarci che i dispositivi in fase di lancio siano testati e soddisfino i nuovi requisiti per il rilascio del prossimo anno.

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

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

Regole di denominazione dei moduli

In Android 11, per ogni combinazione di versioni e backend abilitati viene creato automaticamente un modulo della libreria stub. Per fare riferimento a un modulo specifico della libreria stub per il collegamento, non utilizzare il nome del modulo aidl_interface, ma il nome del modulo della libreria stub, ovvero ifacename-version-backend, dove

  • ifacename: nome del modulo aidl_interface
  • version è una di
    • Vversion-number per le versioni bloccate
    • Vlatest-frozen-version-number + 1 per la versione di punta dell'albero (non ancora bloccata)
  • backend è una di
    • java per il backend Java,
    • cpp per il backend C++.
    • ndk o ndk_platform per il backend NDK. Il primo è per le app, mentre il secondo è per l'utilizzo della piattaforma fino ad Android 13. In Android 13 e versioni successive, utilizza solo ndk.
    • rust per il backend Rust.

Supponiamo che esista un modulo denominato foo e che la sua versione più recente sia 2, e che supporti sia NDK che C++. In questo caso, AIDL genera questi moduli:

  • In base alla versione 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • In base alla versione 2 (la versione stabile più recente)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • In base alla versione di ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Rispetto ad Android 11:

  • foo-backend, che fa riferimento all'ultima versione stabile, diventa foo-V2-backend
  • foo-unstable-backend, che faceva 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 un modulo di versione unstable o un modulo senza versione per un'interfaccia AIDL stabile. A partire da Android 12, il nome del modulo generato da un'interfaccia AIDL stabile include sempre la relativa versione.

Nuovi metodi dell'interfaccia meta

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

Query sulla versione dell'interfaccia dell'oggetto remoto

I client possono eseguire query sulla versione e sull'hash dell'interfaccia implementata dall'oggetto remoto e confrontare i valori restituiti con quelli dell'interfaccia utilizzata dal client.

Esempio con il backend cpp:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Esempio con il backend ndk (e ndk_platform):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Esempio con il backend java:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Per il linguaggio Java, il lato remoto DEVE implementare getInterfaceVersion() e getInterfaceHash() come segue (viene utilizzato super anziché IFoo per evitare errori di copia e incolla. L'annotazione @SuppressWarnings("static") potrebbe essere necessaria per disattivare gli avvisi, a seconda della configurazione di javac:

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

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

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

Gestire le interfacce precedenti

È possibile che un client sia aggiornato con la versione più recente di un'interfaccia AIDL, ma che il server utilizzi la vecchia interfaccia AIDL. In questi casi, la chiamata di un metodo su un'interfaccia precedente restituisce UNKNOWN_TRANSACTION.

Con AIDL stabile, i client hanno un maggiore controllo. Sul lato client puoi impostare un'implementazione predefinita per un'interfaccia AIDL. Un metodo nell'implementazione predefinita viene richiamato solo se non è implementato sul lato remoto (perché è stato creato con una versione precedente dell'interfaccia). Poiché i valori predefiniti sono impostati a livello globale, non devono essere utilizzati da contesti potenzιακά condivisi.

Esempio in C++ in Android 13 e versioni successive:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Esempio in Java:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process

foo.anAddedMethod(...);

Non è necessario fornire l'implementazione predefinita di tutti i metodi in un'interfaccia AIDL. I metodi che sono garantiti per essere implementati nel lato remoto (perché hai la certezza che il telecomando sia stato creato quando i metodi erano nella descrizione dell'interfaccia AIDL) non devono essere sostituiti nella classe impl predefinita.

Convertire l'AIDL esistente in AIDL strutturato o stabile

Se hai già un'interfaccia AIDL e il codice che la utilizza, segui i passaggi riportati di seguito per convertire l'interfaccia in un'interfaccia AIDL stabile.

  1. Identifica tutte le dipendenze dell'interfaccia. Per ogni pacchetto di cui dipende l'interfaccia, determina se il pacchetto è definito in AIDL stabile. Se non è definito, il pacchetto deve essere convertito.

  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 aidl_interface package (di seguito).

  3. Crea un pacchetto aidl_interface (come descritto sopra) contenente il nome del modulo, le sue dipendenze e tutte le altre informazioni di cui hai bisogno. Per stabilizzarlo (non solo strutturarlo), deve essere anche sottoposto a controllo della versione. Per ulteriori informazioni, consulta la sezione Interfacce di controllo delle versioni.