Zawarte tu sprawdzone metody służą jako przewodnik po skutecznym i elastycznym tworzeniu interfejsów AIDL, zwłaszcza gdy AIDL jest używany do definiowania interfejsu API lub interakcji z interfejsami API.
AIDL można użyć do zdefiniowania interfejsu API, gdy aplikacje muszą komunikować się ze sobą w ramach procesu w tle lub z systemem. Więcej informacji o tworzeniu interfejsów programowania w aplikacjach z użyciem AIDL znajdziesz w artykule Język definiowania interfejsu Androida (AIDL). Przykłady praktycznego zastosowania AIDL znajdziesz w artykułach AIDL dla HAL-i i Stabilny AIDL.
Obsługa wersji
Każdy zgodny wstecz snapshot interfejsu AIDL API odpowiada jednej wersji.
Aby zrobić zrzut, uruchom m <module-name>-freeze-api
. Za każdym razem, gdy klient lub serwer interfejsu API zostanie wydany (np. w głównej gałęzi), musisz utworzyć jego migawkę i nową wersję. W przypadku interfejsów API między systemem a dostawcą powinno to nastąpić w ramach corocznej aktualizacji platformy.
Więcej informacji o dozwolonych zmianach znajdziesz w artykule Wersje interfejsów.
Wytyczne dotyczące projektowania interfejsów API
Ogólne
1. Udokumentuj wszystko
- Udokumentuj każdą metodę pod kątem jej semantyki, argumentów, użycia wbudowanych wyjątków, wyjątków specyficznych dla usługi i wartości zwrotnej.
- Udokumentuj semantykę każdego interfejsu.
- Dokumentowanie semantycznego znaczenia typów wyliczeniowych i stałych.
- Udokumentuj wszystko, co może być niejasne dla implementatora.
- W razie potrzeby podaj przykłady.
2. Wielkość liter
W przypadku typów używaj wielkiej litery, a w przypadku metod, pól i argumentów – małej litery. Na przykład MyParcelable
dla typu parcelable i anArgument
dla argumentu. W przypadku skrótów traktuj je jak słowo (NFC
-> Nfc
).
[-Wconst-name] Wartości i stałe typu enum powinny być typu ENUM_VALUE
i CONSTANT_NAME
Interfejsy
1. Nazwa
[-Winterface-name] Nazwa interfejsu powinna zaczynać się od I
, np. IFoo
.
2. Unikaj dużych interfejsów z „obiektowymi” identyfikatorami
Preferuj podinterfejsy, gdy występuje wiele wywołań związanych z danym interfejsem API. Dzięki temu:
- ułatwia zrozumienie kodu klienta lub serwera;
- Sprawia, że cykl życia obiektów jest prostszy
- Wykorzystuje fakt, że skorore nie da się podrobić.
Niezalecane: jeden duży interfejs z obiektmi opartymi na identyfikatorach
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
}
Zalecane: poszczególne interfejsy
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. Nie mieszaj metod jednokierunkowych z dwukierunkowymi
[-Wmixed-oneway] Nie mieszaj metod jednokierunkowych z niejednokierunkowymi, ponieważ komplikuje to zrozumienie modelu wątków przez klientów i serwery. W szczególności podczas odczytywania kodu klienta danego interfejsu musisz sprawdzić, czy dana metoda będzie blokować czy nie.
4. Unikaj zwracania kodów stanu
W przypadku metod należy unikać kodów stanu jako wartości zwracanych, ponieważ wszystkie metody AIDL mają niejawny kod zwrotny stanu. Zobacz ServiceSpecificException
lub EX_SERVICE_SPECIFIC
. Zgodnie z konwencją te wartości są definiowane jako stałe w interfejsie AIDL. Więcej szczegółowych informacji znajdziesz w sekcji dotyczącej obsługi błędów w backendach AIDL.
5. Tablice jako parametry wyjściowe uznane za szkodliwe
[-Wout-array] Metody z parametrami wyjściowymi tablicowymi, takimi jak void foo(out String[] ret)
, są zwykle niewłaściwe, ponieważ rozmiar tablicy wyjściowej musi być zadeklarowany i przydzielony przez klienta w Javie, więc serwer nie może wybrać rozmiaru tablicy wyjściowej. To niepożądane działanie wynika ze sposobu działania tablic w języku Java (nie można ich przydzielić ponownie). Zamiast tego użyj interfejsu API, takiego jak String[] foo()
.
6. Unikaj parametrów inout
[-Winout-parameter] Może to wprowadzać klientów w błąd, ponieważ parametry in
wyglądają jak parametry out
.
7. Unikaj parametrów typu out i inout @nullable, które nie są tablicami
[-Wout-nullable] Ponieważ backend Java nie obsługuje adnotacji @nullable
, a inne backendy tak, out/inout @nullable T
może powodować niespójności w zarządzaniu backendami. Na przykład backendy inne niż w Javie mogą ustawić parametr out @nullable
na null (w C++ ustawiając go jako std::nullopt
), ale klient w Javie nie może odczytać go jako null.
Strukturowane obiekty parcelable
1. Kiedy używać
Używaj arkuszy zbiorczych, jeśli masz wiele typów danych do wysłania.
Z kolei jeśli masz 1 typ danych, ale spodziewasz się, że w przyszłości trzeba będzie go rozszerzyć. Nie używaj na przykład String username
. Użyj rozszerzalnego obiektu Parcelable, na przykład:
parcelable User {
String username;
}
W przyszłości możesz go przedłużyć w ten sposób:
parcelable User {
String username;
int id;
}
2. Podawaj domyślne wartości w prosty sposób.
[-Wexplicit-default, -Wenum-explicit-default] Podanie jawnych wartości domyślnych dla pól.
Nieuporządkowane obiekty do przesyłania
1. Kiedy używać
Obiekty parcelable o nieustrukturyzowanej strukturze są dostępne w Java za pomocą funkcji @JavaOnlyStableParcelable
, a w backendzie NDK za pomocą funkcji @NdkOnlyStableParcelable
. Zwykle są to stare i dotychczasowe obiekty, których nie można uporządkować.
Stałe i wartości w polu enum
1. Pola bitowe powinny używać pól stałych
Pola bitowe powinny używać pól stałych (na przykład const int FOO = 3;
w interfejsie).
2. Enumy powinny być zamkniętymi zbiorami.
Wartości typu enum powinny być zbiorami zamkniętymi. Uwaga: tylko właściciel interfejsu może dodawać elementy enum. Jeśli dostawcy lub producenci OEM muszą rozszerzyć te pola, potrzebny jest alternatywny mechanizm. W miarę możliwości należy preferować funkcję dostarczania danych od dostawcy. W niektórych przypadkach dozwolone mogą być jednak wartości niestandardowe dostawców (chociaż dostawcy powinni mieć mechanizm umożliwiający wersjonowanie, np. AIDL, który nie będzie ze sobą kolidować i nie będzie widoczny dla aplikacji innych firm).
3. Unikaj wartości takich jak „NUM_ELEMENTS”.
Wykazy są wersjonowane, dlatego należy unikać wartości, które wskazują, ile wartości jest obecnych. W C++ można to obejść, używając enum_range<>
. W przypadku Rust użyj enum_values()
. W przypadku Javy nie ma jeszcze rozwiązania.
Niezalecane: używanie wartości numerycznych
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Unikaj zbędnych prefiksów i sufiksów
[-Wredundant-name] Unikaj nadmiarowych lub powtarzających się prefiksów i suffiksów w konstantach i wyliczeniach.
Niezalecane: używanie zbędącego prefiksu
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Zalecany sposób: bezpośrednio nazwij typ enumeracji.
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] Zdecydowanie odradzamy stosowanie FileDescriptor
jako argumentu lub wartości zwrotnej metody interfejsu AIDL. Szczególnie, gdy AIDL jest implementowany w języku Java, może to spowodować wyciek deskryptorów plików, chyba że zostaną one odpowiednio potraktowane. Jeśli zaakceptujesz FileDescriptor
, musisz zamknąć go ręcznie, gdy nie będzie już używany.
W przypadku natywnych backendów nie ma problemu, ponieważ FileDescriptor
mapuje się na unique_fd
, który można zamknąć automatycznie. Jednak niezależnie od używanego języka backendu warto w ogóle NIE używać FileDescriptor
, ponieważ ograniczy to Twoją swobodę zmiany języka backendu w przyszłości.
Zamiast tego użyj interfejsu ParcelFileDescriptor
, który można zamknąć automatycznie.
Zmienne jednostki
Upewnij się, że nazwa zawiera jednostki zmienne, aby były one dobrze zdefiniowane i zrozumiałe bez konieczności odwoływania się do dokumentacji.
Przykłady
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
Sygnatury czasowe muszą wskazywać ich odniesienie
Czas stempla (właściwie wszystkie jednostki) musi wyraźnie wskazywać jednostki i punkty odniesienia.
Przykłady
/**
* 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;