Guida allo stile AIDL

Le migliori pratiche qui descritte servono come 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 devono interfacciarsi 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 nella pratica, vedere AIDL per HAL e AIDL stabile .

Controllo delle versioni

Ogni snapshot compatibile con le versioni precedenti di un'API AIDL corrisponde a una versione. Per acquisire uno snapshot, esegui m <module-name>-freeze-api . Ogni volta che un client o un server dell'API viene rilasciato (ad esempio in un treno della linea principale), è necessario scattare uno snapshot e creare una nuova versione. Per le API system-to-vendor, ciò dovrebbe avvenire 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 dell'API

Generale

1. Documenta tutto

  • Documenta ogni metodo per la sua semantica, argomenti, uso di eccezioni integrate, eccezioni specifiche del servizio e valore restituito.
  • Documentare ogni interfaccia per la sua semantica.
  • Documentare il significato semantico di enumerazioni e costanti.
  • Documentare tutto ciò che potrebbe non essere chiaro a un implementatore.
  • Fornire esempi ove pertinente.

2. Involucro

Utilizzare maiuscole e minuscole il cammello superiore per i tipi e maiuscole e minuscole il cammello inferiore per metodi, campi e argomenti. Ad esempio, MyParcelable per un tipo parcelable e anArgument per un argomento. Per gli acronimi, considerare l'acronimo una parola ( NFC -> Nfc ).

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

Interfacce

1. Denominazione

[-Winterface-name] Il nome di un'interfaccia dovrebbe iniziare con I like IFoo .

2. Evitare grandi interfacce con "oggetti" basati su ID

Preferire le sottointerfacce quando sono presenti molte chiamate relative a un'API specifica. Ciò offre i seguenti vantaggi: - Rende il codice client/server più semplice da comprendere - Semplifica il ciclo di vita degli oggetti - Sfrutta il fatto che i raccoglitori non sono falsificabili.

Non consigliato: 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: singole sottointerfacce

interface IManager {
    IFoo getFoo();
}

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

3. Non mescolare metodi unidirezionali con metodi bidirezionali

[-Wmixed-oneway] Non mescolare metodi unidirezionali con metodi non unidirezionali, perché rende complicata la comprensione del modello di threading per client e server. Nello specifico, quando si legge il codice client di una particolare interfaccia, è necessario cercare per ciascun metodo se tale metodo verrà bloccato o meno.

4. Evitare di restituire codici di stato

I metodi dovrebbero evitare i codici di stato come valori di ritorno, poiché tutti i metodi AIDL hanno un codice di ritorno di stato implicito. Vedere ServiceSpecificException o EX_SERVICE_SPECIFIC . Per convenzione, questi valori sono definiti come costanti in un'interfaccia AIDL. Informazioni più dettagliate si trovano nella sezione Gestione degli errori di AIDL Backends .

5. Array come parametri di output considerati dannosi

[-Wout-array] I metodi che hanno parametri di output dell'array, come void foo(out String[] ret) sono generalmente errati perché la dimensione dell'array di output deve essere dichiarata e allocata dal client in Java, quindi la dimensione dell'output dell'array non può essere scelto dal server. Questo comportamento indesiderato si verifica a causa del modo in cui funzionano gli array in Java (non possono essere riallocati). Preferisci invece API come String[] foo() .

6. Evitare parametri inout

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

7. Evitare parametri non-array out/inout @nullable

[-Wout-nullable] Poiché il backend Java non gestisce l'annotazione @nullable mentre altri backend lo fanno, out/inout @nullable T può 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.

Parcellabili strutturati

1. Quando usarlo

Utilizza pacchetti strutturati in cui hai più tipi di dati da inviare.

Oppure, quando attualmente disponi di un singolo tipo di dati ma prevedi che sarà necessario estenderlo in futuro. Ad esempio, non utilizzare String username . Utilizzare un parcelable estensibile, come il seguente:

parcelable User {
    String username;
}

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

parcelable User {
    String username;
    int id;
}

2. Fornire i valori predefiniti in modo esplicito

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

Parcellabili non strutturati

1. Quando usarlo

I parcelable non strutturati sono attualmente disponibili in Java con @JavaOnlyStableParcelable e nel backend NDK con @NdkOnlyStableParcelable . Di solito si tratta di lotti vecchi ed esistenti che non possono essere facilmente strutturati.

Costanti ed enumerazioni

1. I campi bit dovrebbero utilizzare campi costanti

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

2. Le enumerazioni dovrebbero essere insiemi chiusi.

Le enumerazioni dovrebbero 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. Ove possibile, si dovrebbe preferire la funzionalità upstreaming del fornitore. Tuttavia, in alcuni casi, potrebbero essere consentiti valori personalizzati del fornitore (tuttavia, i fornitori dovrebbero disporre di un meccanismo per verificarne la versione, forse AIDL stesso, non dovrebbero essere in grado di entrare in conflitto tra loro e questi valori non dovrebbero essere esposto ad app di terze parti).

3. Evita valori come "NUM_ELEMENTI"

Poiché le enumerazioni hanno una versione, i valori che indicano quanti valori sono presenti dovrebbero essere evitati. In C++, è possibile risolvere questo problema con enum_range<> . Per Rust, usa enum_values() . In Java non esiste ancora una soluzione.

Non consigliato: utilizzare valori numerati

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

4. Evitare prefissi e suffissi ridondanti

[-Wredundant-name] Evitare prefissi e suffissi ridondanti o ripetitivi nelle costanti e negli enumeratori.

Non consigliato: utilizzare un prefisso ridondante

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Consigliato: nominare direttamente l'enum

enum MyStatus {
    GOOD,
    BAD
}

DescrizioneFile

[-Wfile-descriptor] L'uso di FileDescriptor come argomento o il valore restituito di un metodo di interfaccia AIDL è altamente sconsigliato. In particolare, quando l'AIDL è implementato in Java, ciò potrebbe causare perdite di descrittori di file se non gestito con attenzione. Fondamentalmente, se accetti un FileDescriptor , devi chiuderlo manualmente quando non viene più utilizzato.

Per i backend nativi, sei al sicuro perché FileDescriptor è mappato su unique_fd che è chiudibile automaticamente. Ma indipendentemente dal linguaggio di backend che utilizzeresti, è saggio NON utilizzare affatto FileDescriptor perché ciò limiterà la tua libertà di cambiare il linguaggio di backend in futuro.

Utilizzare invece ParcelFileDescriptor , che è chiudibile automaticamente.

Unità variabili

Assicurati che le unità variabili siano incluse nel nome in modo che le loro unità siano ben definite e comprese senza la necessità di 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 loro riferimento

I timestamp (in effetti, tutte le unità!) devono indicare chiaramente le loro 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;