Описанные здесь лучшие практики служат руководством по эффективной разработке интерфейсов AIDL с учетом гибкости интерфейса, особенно когда AIDL используется для определения API или взаимодействия с поверхностями API.
AIDL можно использовать для определения API, когда приложениям необходимо взаимодействовать друг с другом в фоновом режиме или с системой. Подробнее о разработке интерфейсов программирования в приложениях с использованием AIDL см. в статье Android Interface Definition Language (AIDL) . Примеры использования AIDL на практике см. в статьях AIDL для HAL и Stable AIDL .
Версионирование
Каждый обратно совместимый снимок API AIDL соответствует определённой версии. Чтобы сделать снимок, выполните команду m <module-name>-freeze-api
. При каждом выпуске клиента или сервера API (например, в поезде Mainline) необходимо сделать снимок и создать новую версию. Для API, взаимодействующих между системой и поставщиком, это следует делать при ежегодном обновлении платформы.
Более подробную информацию о типах разрешенных изменений см. в разделе Интерфейсы управления версиями .
Рекомендации по проектированию API
Общий
1. Все документируйте
- Документируйте каждый метод на предмет его семантики, аргументов, использования встроенных исключений, исключений, специфичных для сервиса, и возвращаемого значения.
- Документируйте семантику каждого интерфейса.
- Документируйте семантическое значение перечислений и констант.
- Задокументируйте все, что может быть неясно исполнителю.
- Приведите примеры, где это уместно.
2. Корпус
Используйте верхний регистр для типов и нижний — для методов, полей и аргументов. Например, MyParcelable
для типа Parcelable и anArgument
для аргумента. В качестве акронимов можно использовать акроним a word ( NFC
-> Nfc
).
[-Wconst-name] Значения перечисления и константы должны быть ENUM_VALUE
и CONSTANT_NAME
Интерфейсы
1. Нейминг
[-Winterface-name] Имя интерфейса должно начинаться с I
like IFoo
.
2. Избегайте громоздких интерфейсов с «объектами» на основе идентификаторов.
При большом количестве вызовов, связанных с конкретным API, предпочтительнее использовать подинтерфейсы. Это даёт следующие преимущества:
- Облегчает понимание клиентского или серверного кода
- Упрощает жизненный цикл объектов
- Использует тот факт, что папки невозможно подделать.
Не рекомендуется: единый большой интерфейс с объектами на основе идентификаторов.
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
}
Рекомендуется: индивидуальные интерфейсы
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. Не смешивайте односторонние и двусторонние методы.
[-Wmixed-oneway] Не смешивайте односторонние и неодносторонние методы, поскольку это усложняет понимание модели потоков для клиентов и серверов. В частности, при чтении клиентского кода конкретного интерфейса необходимо проверять, блокирует ли каждый метод этот метод выполнение или нет.
4. Избегайте возврата кодов статуса
Методы должны избегать кодов состояния в качестве возвращаемых значений, поскольку все методы AIDL имеют неявный код состояния. См. ServiceSpecificException
или EX_SERVICE_SPECIFIC
. По соглашению, эти значения определяются как константы в интерфейсе AIDL. Более подробная информация представлена в разделе «Обработка ошибок» книги «Бэкенды AIDL» .
5. Массивы как выходные параметры считаются вредными
[-Wout-array] Методы с выходными параметрами-массивами, такие как void foo(out String[] ret)
обычно неэффективны, поскольку размер выходного массива должен быть объявлен и выделен клиентом в Java, а размер выходного массива не может быть выбран сервером. Это нежелательное поведение обусловлено особенностями работы массивов в Java (их нельзя перераспределить). Вместо этого предпочитайте API, такие как String[] foo()
.
6. Избегайте входных параметров
[-Winout-parameter] Это может сбить с толку клиентов, поскольку даже in
параметры выглядят как out
параметры.
7. Избегайте параметров out и inout, которые не являются массивами и могут иметь значение @nullable.
[-Wout-nullable] Поскольку бэкенд Java не обрабатывает аннотацию @nullable
, в отличие от других бэкендов, out/inout @nullable T
может привести к несогласованному поведению между бэкендами. Например, бэкенды, не основанные на Java, могут установить параметр out @nullable
в значение null (в C++ это можно сделать как std::nullopt
), но клиент Java не сможет прочитать его как null.
Структурированные пакеты
1. Когда использовать
Используйте структурированные пакеты, если вам нужно отправить несколько типов данных.
Или, если у вас есть один тип данных, но вы ожидаете, что в будущем вам потребуется его расширить. Например, не используйте String username
. Используйте расширяемый тип parcelable, например:
parcelable User {
String username;
}
Чтобы в будущем вы могли расширить его следующим образом:
parcelable User {
String username;
int id;
}
2. Укажите значения по умолчанию явно
[-Wexplicit-default, -Wenum-explicit-default] Укажите явные значения по умолчанию для полей.
Неструктурированные парцелляты
1. Когда использовать
Неструктурированные объекты Parcelable доступны в Java с помощью @JavaOnlyStableParcelable
и в бэкенде NDK с помощью @NdkOnlyStableParcelable
. Обычно это старые и существующие объекты Parcelable, которые невозможно структурировать.
Константы и перечисления
1. Битовые поля должны использовать константные поля.
Битовые поля должны использовать константные поля (например, const int FOO = 3;
в интерфейсе).
2. Перечисления должны быть замкнутыми множествами.
Перечисления должны быть закрытыми. Примечание: добавлять элементы перечисления может только владелец интерфейса. Если поставщикам или OEM-производителям необходимо расширить эти поля, необходим альтернативный механизм. По возможности следует отдавать предпочтение функциональности поставщика, работающей в рамках восходящей линии. Однако в некоторых случаях допускается использование пользовательских значений поставщика (хотя у поставщиков должен быть механизм для версионирования этого, возможно, сам AIDL; они не должны конфликтовать друг с другом, и эти значения не должны предоставляться сторонним приложениям).
3. Избегайте значений типа «NUM_ELEMENTS»
Поскольку перечисления версионированы, следует избегать значений, указывающих количество имеющихся значений. В C++ это можно обойти с помощью enum_range<>
. В Rust используйте enum_values()
. В Java решения пока нет.
Не рекомендуется: использовать числовые значения.
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Избегайте лишних префиксов и суффиксов.
[-Wredundant-name] Избегайте избыточных или повторяющихся префиксов и суффиксов в константах и перечислителях.
Не рекомендуется: использовать избыточный префикс.
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Рекомендуется: непосредственное указание имени перечисления
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] Использование FileDescriptor
в качестве аргумента или возвращаемого значения метода интерфейса AIDL крайне не рекомендуется. Особенно при реализации AIDL на Java это может привести к утечке файлового дескриптора, если не соблюдать меры предосторожности. По сути, если вы принимаете FileDescriptor
, вам необходимо вручную закрыть его, когда он больше не используется.
Для нативных бэкендов вы в безопасности, поскольку FileDescriptor
сопоставлен с unique_fd
, который автоматически закрывается. Но независимо от используемого языка бэкенда, разумно вообще НЕ использовать FileDescriptor
, поскольку это ограничит вашу свободу изменения языка бэкенда в будущем.
Вместо этого используйте ParcelFileDescriptor
, который автоматически закрывается.
Переменные единицы
Убедитесь, что переменные единицы измерения включены в название, чтобы их единицы измерения были четко определены и понятны без необходимости обращения к документации.
Примеры
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
Метки времени должны указывать на свою ссылку
Метки времени (фактически, все единицы измерения!) должны четко указывать единицы измерения и точки отсчета.
Примеры
/**
* 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;