Guida di stile per AIDL

Le best practice descritte qui fungono da guida per sviluppare interfacce AIDL in modo efficace e con attenzione alla flessibilità dell'interfaccia, in particolare quando AIDL viene utilizzato per definire un'API o interagire con le superfici API.

AIDL può essere utilizzato per definire un'API quando le app devono interfacciarsi tra loro in un processo in background o con il sistema. Per ulteriori informazioni sullo sviluppo di interfacce di programmazione nelle app con AIDL, consulta Android Interface Definition Language (AIDL). Per esempi di AIDL in pratica, vedi AIDL per HAL e AIDL stabile.

Versioni

Ogni snapshot compatibile con le versioni precedenti di un'API AIDL corrisponde a una versione. Per scattare una foto, esegui m <module-name>-freeze-api. Ogni volta che viene rilasciato un client o un server dell'API (ad esempio, in un treno Mainline), devi scattare uno snapshot e creare una nuova versione. Per le API da sistema a fornitore, questa operazione deve essere eseguita con la revisione annuale della piattaforma.

Per ulteriori dettagli e informazioni sul tipo di modifiche consentite, consulta Interfacce di controllo delle versioni.

Linee guida per la progettazione delle API

Generali

1. Documenta tutto

  • Documenta ogni metodo per la sua semantica, i suoi argomenti, l'utilizzo di eccezioni integrate, eccezioni specifiche del servizio e valore restituito.
  • Documenta ogni interfaccia per la sua semantica.
  • Documenta il significato semantico di enumerazioni e costanti.
  • Documenta tutto ciò che potrebbe non essere chiaro per un implementatore.
  • Fornisci esempi pertinenti.

2. Carcassa

Utilizza la notazione CamelCase maiuscola per i tipi e la notazione CamelCase minuscola per metodi, campi e argomenti. Ad esempio, MyParcelable per un tipo serializzabile e anArgument per un argomento. Per gli acronimi, considera l'acronimo una parola (NFC -> Nfc).

[-Wconst-name] I valori enum e le costanti devono essere ENUM_VALUE e CONSTANT_NAME

Interfacce

1. Nome

[-Winterface-name] Il nome di un'interfaccia deve iniziare con I, ad esempio IFoo.

2. Evita interfacce di grandi dimensioni con "oggetti" basati su ID

Preferisci le sottointerfacce quando ci sono molte chiamate correlate a un'API specifica. Questo offre i seguenti vantaggi:

  • Rende più facile la comprensione del codice client o server
  • Semplifica il ciclo di vita degli oggetti
  • Sfrutta l'impossibilità di falsificare i raccoglitori.

Sconsigliato:un'unica interfaccia di grandi dimensioni con oggetti basati su ID

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Consigliato:interfacce individuali

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Non combinare metodi unidirezionali e bidirezionali

[-Wmixed-oneway] Non combinare metodi unidirezionali con metodi non unidirezionali, perché rende la comprensione del modello di threading complicata per client e server. Nello specifico, quando leggi il codice client di una determinata interfaccia, devi cercare per ogni metodo se questo bloccherà o meno.

4. Evita di restituire codici di stato

I metodi devono evitare i codici di stato come valori restituiti, poiché tutti i metodi AIDL hanno un codice di stato restituito implicito. Consulta ServiceSpecificException o EX_SERVICE_SPECIFIC. Per convenzione, questi valori sono definiti come costanti in un'interfaccia AIDL. Per informazioni più dettagliate, consulta la sezione Gestione degli errori dei backend AIDL.

5. Gli array come parametri di output sono considerati dannosi

[-Wout-array] I metodi con parametri di output di array, come void foo(out String[] ret), di solito non sono consigliati perché le dimensioni dell'array di output devono essere dichiarate e allocate dal client in Java, quindi le dimensioni dell'array di output non possono essere scelte dal server. Questo comportamento indesiderato si verifica perché gli array in Java non possono essere riallocati. Preferisci invece API come String[] foo().

6. Evita i parametri inout

[-Winout-parameter] Ciò può confondere i clienti perché anche i parametri in sembrano parametri out.

7. Evita i parametri out e inout @nullable non array

[-Wout-nullable] Poiché il backend Java non gestisce l'annotazione @nullable mentre altri backend lo fanno, out/inout @nullable T potrebbe comportare un comportamento incoerente tra i backend. Ad esempio, i backend non Java possono impostare un parametro out @nullable su null (in C++, impostandolo come std::nullopt), ma il client Java non può leggerlo come null.

Parcelable strutturati

1. Quando usare la funzionalità

Utilizza parcelable strutturati quando devi inviare più tipi di dati.

Oppure, quando hai un solo tipo di dati, ma prevedi di doverlo estendere in futuro. Ad esempio, non usare String username. Utilizza un oggetto Parcelable estendibile, come il seguente:

parcelable User {
    String username;
}

In modo che, in futuro, tu possa estenderlo come segue:

parcelable User {
    String username;
    int id;
}

2. Fornisci valori predefiniti in modo esplicito

[-Wexplicit-default, -Wenum-explicit-default] Fornisci valori predefiniti espliciti per i campi.

Parcelable non strutturati

1. Quando usare la funzionalità

I parcelable non strutturati sono disponibili in Java con @JavaOnlyStableParcelable e nel backend NDK con @NdkOnlyStableParcelable. In genere, si tratta di parcelable vecchi ed esistenti che non possono essere strutturati.

Costanti ed enumerazioni

1. I campi di bit devono utilizzare campi costanti

I campi di bit devono utilizzare campi costanti (ad esempio, const int FOO = 3; in un'interfaccia).

2. Gli enum devono essere insiemi chiusi.

Gli enum devono essere insiemi chiusi. Nota: solo il proprietario dell'interfaccia può aggiungere elementi enum. Se i fornitori o gli OEM devono estendere questi campi, è necessario un meccanismo alternativo. Se possibile, è preferibile utilizzare la funzionalità del fornitore upstream. Tuttavia, in alcuni casi, i valori personalizzati del fornitore potrebbero essere consentiti (anche se i fornitori devono disporre di un meccanismo per il controllo delle versioni, magari AIDL stesso, non devono essere in conflitto tra loro e questi valori non devono essere esposti ad app di terze parti).

3. Evita valori come "NUM_ELEMENTS"

Poiché gli enum sono versionati, è consigliabile evitare i valori che indicano quanti valori sono presenti. In C++, questo problema può essere risolto con enum_range<>. Per Rust, utilizza enum_values(). In Java, non esiste ancora una soluzione.

Sconsigliato:utilizzo di valori numerati

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Evita prefissi e suffissi ridondanti

[-Wredundant-name] Evita prefissi e suffissi ridondanti o ripetitivi in costanti ed enumeratori.

Sconsigliato:utilizzo di un prefisso ridondante

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Consigliato: denominazione diretta dell'enumerazione

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] L'utilizzo di FileDescriptor come argomento o valore restituito di un metodo di interfaccia AIDL è fortemente sconsigliato. In particolare, quando l'AIDL viene implementato in Java, ciò potrebbe causare una perdita di descrittori di file, a meno che non venga gestita con attenzione. In sostanza, se accetti un FileDescriptor, devi chiuderlo manualmente quando non viene più utilizzato.

Per i backend nativi, non ci sono problemi perché FileDescriptor corrisponde a unique_fd che è chiudibile automaticamente. Indipendentemente dalla lingua del backend che utilizzerai, è consigliabile NON utilizzare FileDescriptor perché ciò limiterà la tua libertà di cambiare la lingua del backend in futuro.

Utilizza invece ParcelFileDescriptor, che può essere chiuso automaticamente.

Unità di variabile

Assicurati che le unità delle variabili siano incluse nel nome in modo che siano ben definite e comprensibili senza dover fare riferimento alla documentazione.

Esempi

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

I timestamp devono indicare il riferimento

I timestamp (in realtà, tutte le unità) devono indicare chiaramente le unità e i punti di riferimento.

Esempi

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;