Le best practice descritte qui servono da guida per lo sviluppo di interfacce AIDL in modo efficace e prestando attenzione alla flessibilità dell'interfaccia, in particolare quando AIDL viene utilizzato per definire un'API o interagire con le API.
AIDL può essere utilizzato per definire un'API quando le app devono interagire tra loro in un processo in background o devono interagire 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, consulta 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 viene rilasciato un client o un server dell'API (ad esempio in un treno Mainline), devi acquisire uno snapshot e creare una nuova versione. Per le API system-to-vendor, questo dovrebbe accadere con la revisione annuale della piattaforma.
Per ulteriori dettagli e informazioni sul tipo di modifiche consentite, consulta Interfacce di gestione delle versioni.
Linee guida per la progettazione delle API
Generali
1. Documenta tutto
- Documenta ogni metodo per la sua semantica, gli argomenti, l'utilizzo di eccezioni predefinite, eccezioni specifiche del servizio e il valore restituito.
- Documenta ogni interfaccia per la relativa semantica.
- Documenta il significato semantico di enumerazioni e costanti.
- Documenta ciò che potrebbe non essere chiaro per un implementatore.
- Fornisci esempi, ove pertinenti.
2. Telaio
Utilizza lettere maiuscole per i tipi e lettere minuscole per metodi, campi e argomenti. Ad esempio, MyParcelable
per un tipo di parcelable e anArgument
per un argomento. Per gli acronimi, considera l'acronimo come una parola (NFC
-> Nfc
).
[-Wconst-name] I valori e le costanti enum devono essere ENUM_VALUE
e
CONSTANT_NAME
Interfacce
1. Nome
[-Wnome-interfaccia] Il nome di un'interfaccia deve iniziare con I
, ad esempio IFoo
.
2. Evita grandi interfacce con "oggetti" basati su ID
Preferisci le sottointerfacce quando ci sono molte chiamate relative a un'API specifica. Ciò offre i seguenti vantaggi:
- Semplifica la comprensione del codice client o server
- Semplifica il ciclo di vita degli oggetti
- Sfrutta il fatto che i binder non possono essere contraffatti.
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 singole
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. Non combinare i metodi a senso unico con quelli a doppio senso
[-Wmixed-oneway] Non combinare metodi one-way con metodi non one-way, perché complica la comprensione del modello di threading per client e server. In particolare, quando si legge il codice client di una particolare interfaccia, è necessario cercare ogni metodo se viene bloccato o meno.
4. Evita di restituire codici di stato
I metodi dovrebbero evitare i codici di stato come valori restituiti, poiché tutti i metodi AIDL hanno un codice restituito dello stato 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 relativa alla gestione degli errori dei backend
AIDL.
5. Array come parametri di output considerati dannosi
[-Wout-array] I metodi con parametri di output di array, comevoid foo(out String[] ret)
, in genere non sono validi perché le dimensioni dell'array di output devono essere dichiarate e allocate dal client in Java, pertanto le dimensioni dell'array di output non possono essere scelte dal server. Questo comportamento indesiderato si verifica a causa del funzionamento degli array in Java (non possono essere riallocati). Preferisci invece API
come String[] foo()
.
6. Evita i parametri inout
[-Winout-parameter] Questo 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.
Parcellable strutturati
1. Quando usare la funzionalità
Utilizza parcelable strutturati se devi inviare più tipi di dati.
In alternativa, se hai un singolo tipo di dati, ma prevedi di doverli estendere in futuro. Ad esempio, non usare String username
. Utilizza un oggetto Parcelable espandibile, ad esempio il seguente:
parcelable User {
String username;
}
In modo da poterlo estendere in futuro, come segue:
parcelable User {
String username;
int id;
}
2. Fornisci i valori predefiniti in modo esplicito
[-Wexplicit-default, -Wenum-explicit-default] Fornisci valori predefiniti espliciti per i campi.
Parcelli non strutturate
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 elementi parcelable vecchi ed esistenti
che non possono essere strutturati.
Costanti ed enum
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 enumerati. Se i fornitori o gli OEM devono estendere questi campi, è necessario un meccanismo alternativo. Ove possibile, è preferibile eseguire l'upstreaming delle funzionalità del fornitore. Tuttavia, in alcuni casi, i valori dei fornitori personalizzati potrebbero essere consentiti (anche se i fornitori dovrebbero avere un meccanismo per eseguire la versione, forse l'AIDL stesso, non dovrebbero essere in grado di entrare 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++, è possibile risolvere il problema utilizzando enum_range<>
. In caso di ruggine, usa enum_values()
. Non esiste ancora una soluzione in Java.
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:utilizzare un prefisso ridondante
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Consigliato: assegna un nome diretto all'enumerazione
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] L'utilizzo di FileDescriptor
come argomento o come valore di ritorno di un metodo dell'interfaccia AIDL è vivamente sconsigliato. In particolare, quando l'AIDL è implementato in Java, ciò potrebbe causare la perdita del descrittore dei file se non viene gestito con attenzione. In sostanza, se accetti un FileDescriptor
, devi chiuderlo manualmente quando non è più in uso.
Per i backend nativi, la tua sicurezza è garantita perché FileDescriptor
viene mappato a unique_fd
, che può essere chiuso automaticamente. Tuttavia, indipendentemente dal linguaggio di backend che intendi utilizzare, è consigliabile NON utilizzare FileDescriptor
, in quanto ciò limiterà la tua libertà di modificare il linguaggio di backend in futuro.
Utilizza invece ParcelFileDescriptor
, che può essere chiuso automaticamente.
Unità variabili
Assicurati che le unità di misura 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 relativo riferimento
I timestamp (infatti, tutte le unità) devono indicare chiaramente le relative 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;