Le best practice qui descritte 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 piattaforme 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 pratici di AIDL, vedi 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 da sistema a fornitore, questo
dovrebbe verificarsi 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 verificare la semantica, gli argomenti, l'uso di eccezioni integrate, le eccezioni specifiche dei servizi e il valore restituito.
- Documenta ogni interfaccia per verificarne la semantica.
- Documenta il significato semantico di enumerazioni e costanti.
- Documenta ciò che potrebbe non essere chiaro per un implementatore.
- Fornisci esempi, ove pertinenti.
2. Involucro
Utilizza le maiuscole nella parte superiore per i tipi e quelle nella parte inferiore per metodi, campi e argomenti. Ad esempio, MyParcelable
per un tipo "parcelable" e anArgument
per un argomento. Per gli acronimi, considera l'acronimo una parola (NFC
-> Nfc
).
I valori e le costanti enum [-Wconst-name] devono essere ENUM_VALUE
e
CONSTANT_NAME
Interfacce
1. Denominazione
[-Winterface-name] 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 l'improbabilità dei raccoglitori.
Opzione sconsigliata: una singola 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 utilizzare metodi unidirezionali e bidirezionali
[-Wmixed-oneway] Non combinare metodi unidirezionali con metodi non unidirezionali, perché complica la comprensione del modello di thread 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. Evitare di restituire i 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. Vedi 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 dell'array, come
void foo(out String[] ret)
, di solito non sono validi perché la dimensione dell'array di output deve
essere dichiarata e allocata dal client in Java, pertanto la dimensione dell'output
dell'array non può essere scelta 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 parametri inout
[-Parametro Winout] Questo può confondere i client perché anche i parametri in
assomigliano ai parametri out
.
7. Evita parametri non array @nullable out e inout
[-Wout-nullable] Poiché il backend Java non gestisce l'annotazione @nullable
mentre gli altri backend lo fa, out/inout @nullable T
potrebbe condurre un comportamento incoerente
tra i backend. Ad esempio, i backend non Java possono impostare un parametro @nullable
su null (in C++, impostandolo come std::nullopt
), ma il client Java
non può leggerlo come nullo.
Lotti strutturati
1. Quando usare la funzionalità
Utilizza i lotti strutturati per i quali devi inviare più tipi di dati.
o quando hai un solo tipo di dati, ma prevedi che sarà necessario estenderlo in futuro. Ad esempio, non usare String username
. Utilizza un elemento "parcelable" estensibile, come questo:
parcelable User {
String username;
}
In questo modo, in futuro, potrai estenderlo come segue:
parcelable User {
String username;
int id;
}
2. Fornisci impostazioni predefinite in modo esplicito
[-Wexplicit-default, -Wenum-explicit-default] Fornisci valori predefiniti espliciti per i campi.
Parcelable non strutturati
1. Quando usare la funzionalità
I pacchetti non strutturati sono disponibili in Java con @JavaOnlyStableParcelable
e nel backend NDK con @NdkOnlyStableParcelable
. Di solito, si tratta di particella vecchi
ed esistenti che non possono essere strutturati.
Costanti ed enum
1. I bitfield devono utilizzare campi costanti
I bitfield devono utilizzare campi costanti (ad esempio, const int FOO = 3;
in un'interfaccia).
2. Le enum devono essere insiemi chiusi.
Le 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, dovrebbe essere preferibile la funzionalità upstream del fornitore. Tuttavia, in alcuni casi è possibile consentire valori personalizzati del fornitore (sebbene i fornitori debbano disporre di un meccanismo per eseguire la versione, ad esempio 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é le enum vengono sottoposte al controllo delle versioni, è necessario 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
}
Descriptor
[-Wfile-descriptor] Sconsigliamo vivamente di utilizzare FileDescriptor
come argomento o come valore restituito di un metodo di interfaccia AIDL. In particolare, quando l'AIDL è implementato in Java, ciò potrebbe causare la perdita del descrittore dei file se non viene gestito con attenzione. Fondamentalmente, se accetti un FileDescriptor
, devi
chiuderlo manualmente quando non viene più utilizzato.
Per i backend nativi, la tua sicurezza è garantita perché FileDescriptor
si mappa su unique_fd
, che può essere chiuso automaticamente. Tuttavia, indipendentemente dal linguaggio di backend che utilizzerai,
è consigliabile NON utilizzare affatto FileDescriptor
, perché questo limiterà la tua
libertà di cambiare il linguaggio di backend in futuro.
Utilizza invece ParcelFileDescriptor
, che può essere chiuso automaticamente.
Unità variabili
Assicurati che le unità variabili siano incluse nel nome, in modo che le relative unità siano ben definite e comprese, senza la necessità di consultare la 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;