Die hier beschriebenen Best Practices dienen als Leitfaden für die effektive Entwicklung von AIDL-Schnittstellen, wobei insbesondere auf die Flexibilität der Schnittstelle geachtet wird, wenn AIDL zum Definieren einer API oder zur Interaktion mit API-Oberflächen verwendet wird.
AIDL kann verwendet werden, um eine API zu definieren, wenn Apps in einem Hintergrundprozess oder mit dem System interagieren müssen. Weitere Informationen zum Entwickeln von Programmierschnittstellen in Apps mit AIDL finden Sie unter Android Interface Definition Language (AIDL). Praktische Beispiele für AIDL finden Sie unter AIDL für HALs und Stabile AIDL.
Versionsverwaltung
Jeder abwärtskompatible Snapshot einer AIDL-API entspricht einer Version.
Führen Sie m <module-name>-freeze-api aus, um einen Snapshot zu erstellen. Immer wenn ein Client oder Server der API veröffentlicht wird (z. B. in einem Mainline-Train), müssen Sie einen Snapshot erstellen und eine neue Version erstellen. Bei System-zu-Anbieter-APIs sollte dies mit der jährlichen Plattformrevision erfolgen.
Weitere Informationen und Details zu den zulässigen Änderungen finden Sie unter Versionsverwaltung von Schnittstellen.
Richtlinien für das API-Design
Allgemein
1. Alles dokumentieren
- Dokumentieren Sie jede Methode hinsichtlich ihrer Semantik, Argumente, Verwendung integrierter Ausnahmen, dienstspezifischer Ausnahmen und des Rückgabewerts.
- Dokumentieren Sie die Semantik jeder Schnittstelle.
- Dokumentieren Sie die semantische Bedeutung von Enums und Konstanten.
- Dokumentieren Sie alles, was für einen Implementierer unklar sein könnte.
- Geben Sie gegebenenfalls Beispiele an.
2. Gehäuse
Verwenden Sie UpperCamelCase für Typen und LowerCamelCase für Methoden, Felder und Argumente. Beispiel: MyParcelable für einen Parcelable-Typ und anArgument für ein Argument. Bei Akronymen wird das Akronym als Wort betrachtet (NFC -> Nfc).
[-Wconst-name] Enum-Werte und Konstanten sollten ENUM_VALUE und CONSTANT_NAME sein.
Schnittstellen
1. Namen
[-Winterface-name] Ein Schnittstellenname sollte mit I wie IFoo beginnen.
2. Vermeiden Sie große Schnittstellen mit ID-basierten „Objekten“.
Verwenden Sie lieber untergeordnete Schnittstellen, wenn viele Aufrufe im Zusammenhang mit einer bestimmten API stehen. Das bietet folgende Vorteile:
- Client- oder Servercode ist leichter verständlich
- Vereinfacht den Lebenszyklus von Objekten
- Nutzt die Unfälschbarkeit von Bindern.
Nicht empfohlen:Eine einzelne, große Schnittstelle mit ID-basierten Objekten
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
}
Empfohlen:Einzelne Schnittstellen
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. Unidirektionale und bidirektionale Methoden nicht mischen
[-Wmixed-oneway] Mischen Sie keine One-Way-Methoden mit Nicht-One-Way-Methoden, da dies das Threading-Modell für Clients und Server kompliziert macht. Wenn Sie Clientcode einer bestimmten Schnittstelle lesen, müssen Sie für jede Methode nachsehen, ob sie blockiert wird oder nicht.
4. Statuscodes vermeiden
Methoden sollten Statuscodes als Rückgabewerte vermeiden, da alle AIDL-Methoden einen impliziten Statusrückgabecode haben. Weitere Informationen finden Sie unter ServiceSpecificException oder EX_SERVICE_SPECIFIC. Konventionsgemäß werden diese Werte als Konstanten in einer AIDL-Schnittstelle definiert. Ausführlichere Informationen finden Sie im Abschnitt zur Fehlerbehandlung in AIDL-Back-Ends.
5. Arrays als Ausgabeparameter gelten als schädlich
[-Wout-array] Methoden mit Array-Ausgabeparametern wie void foo(out String[] ret) sind in der Regel schlecht, da die Größe des Ausgabearrays vom Client in Java deklariert und zugewiesen werden muss. Die Größe der Array-Ausgabe kann also nicht vom Server ausgewählt werden. Dieses unerwünschte Verhalten ist darauf zurückzuführen, wie Arrays in Java funktionieren (sie können nicht neu zugewiesen werden). Verwenden Sie stattdessen APIs wie String[] foo().
6. inout-Parameter vermeiden
[-Winout-parameter] Das kann Clients verwirren, da auch in-Parameter wie out-Parameter aussehen.
7. Vermeiden Sie OUT- und INOUT-Parameter, die nicht nullable und keine Arrays sind.
[-Wout-nullable] Da das Java-Backend die @nullable-Annotation nicht verarbeitet, andere Backends jedoch schon, kann out/inout @nullable T zu inkonsistentem Verhalten zwischen den Backends führen. Beispielsweise können Nicht-Java-Backends einen OUT-@nullable-Parameter auf „null“ setzen (in C++ wird er als std::nullopt festgelegt), aber der Java-Client kann ihn nicht als „null“ lesen.
Strukturierte Parcelables
1. Anwendung
Verwenden Sie strukturierte Parcelables, wenn Sie mehrere Datentypen senden müssen.
Oder wenn Sie einen einzelnen Datentyp haben, aber davon ausgehen, dass Sie ihn in Zukunft erweitern müssen. Verwende zum Beispiel nicht String username. Verwenden Sie ein erweiterbares Parcelable wie das folgende:
parcelable User {
String username;
}
So können Sie sie in Zukunft so erweitern:
parcelable User {
String username;
int id;
}
2. Standardwerte explizit angeben
[-Wexplicit-default, -Wenum-explicit-default] Geben Sie explizite Standardwerte für Felder an.
Nicht strukturierte Parcelables
1. Anwendung
Nicht strukturierte Parcelables sind in Java mit @JavaOnlyStableParcelable und im NDK-Backend mit @NdkOnlyStableParcelable verfügbar. Normalerweise handelt es sich dabei um alte und vorhandene Parcelables, die nicht strukturiert werden können.
Konstanten und Enums
1. Bitfelder sollten konstante Felder verwenden
Für Bitfelder sollten konstante Felder verwendet werden (z. B. const int FOO = 3; in einer Schnittstelle).
2. Enums sollten geschlossene Mengen sein.
Enums sollten geschlossene Mengen sein. Hinweis: Nur der Schnittstelleninhaber kann Enum-Elemente hinzufügen. Wenn Anbieter oder OEMs diese Felder erweitern müssen, ist ein alternativer Mechanismus erforderlich. Nach Möglichkeit sollte die Upstreaming-Funktionalität des Anbieters bevorzugt werden. In einigen Fällen sind jedoch benutzerdefinierte Anbietervariablen zulässig. Anbieter sollten jedoch einen Mechanismus zur Versionsverwaltung haben, z. B. AIDL selbst. Sie dürfen nicht miteinander in Konflikt stehen und diese Werte dürfen nicht für Drittanbieter-Apps verfügbar sein.
3. Vermeiden Sie Werte wie „NUM_ELEMENTS“.
Da Enums versioniert werden, sollten Werte, die angeben, wie viele Werte vorhanden sind, vermieden werden. In C++ kann dies mit enum_range<> umgangen werden. Verwenden Sie für Rust enum_values(). In Java gibt es noch keine Lösung.
Nicht empfohlen:Verwendung nummerierter Werte
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Redundante Präfixe und Suffixe vermeiden
[-Wredundant-name] Vermeiden Sie redundante oder sich wiederholende Präfixe und Suffixe in Konstanten und Enumeratoren.
Nicht empfohlen:Verwenden eines redundanten Präfixes
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Empfohlen:Die Enumeration direkt benennen
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] Die Verwendung von FileDescriptor als Argument oder Rückgabewert einer AIDL-Schnittstellenmethode wird dringend abgeraten. Insbesondere wenn die AIDL in Java implementiert wird, kann dies zu einem Leck von Dateideskriptoren führen, wenn nicht sorgfältig darauf geachtet wird. Wenn Sie ein FileDescriptor akzeptieren, müssen Sie es manuell schließen, wenn es nicht mehr verwendet wird.
Bei nativen Back-Ends sind Sie auf der sicheren Seite, da FileDescriptor unique_fd entspricht, was automatisch geschlossen werden kann. Unabhängig von der Backend-Sprache, die Sie verwenden, ist es jedoch ratsam, FileDescriptor NICHT zu verwenden, da dies Ihre Möglichkeiten einschränkt, die Backend-Sprache in Zukunft zu ändern.
Verwenden Sie stattdessen ParcelFileDescriptor, das automatisch geschlossen werden kann.
Variableneinheiten
Achten Sie darauf, dass die Variableneinheiten im Namen enthalten sind, damit sie ohne Bezug auf die Dokumentation klar definiert und verständlich sind.
Beispiele
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
Zeitstempel müssen ihren Bezug angeben
Bei Zeitstempeln (und allen anderen Einheiten) müssen die Einheiten und Bezugspunkte klar angegeben werden.
Beispiele
/**
* 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;